fix(api): 🐛 Don't use URL in Versia entity schemas, fixes OpenAPI

This commit is contained in:
Jesse Wierzbinski 2025-04-16 16:35:17 +02:00
parent 0a712128a5
commit a2e907390f
No known key found for this signature in database
12 changed files with 131 additions and 102 deletions

View file

@ -86,11 +86,11 @@ export default apiRoute((app) =>
); );
const uriCollection = new VersiaEntities.URICollection({ const uriCollection = new VersiaEntities.URICollection({
author: note.author.uri, author: note.author.uri.href,
first: new URL( first: new URL(
`/notes/${note.id}/quotes?offset=0`, `/notes/${note.id}/quotes?offset=0`,
config.http.base_url, config.http.base_url,
), ).href,
last: last:
replyCount > limit replyCount > limit
? new URL( ? new URL(
@ -98,11 +98,11 @@ export default apiRoute((app) =>
replyCount - limit replyCount - limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: new URL( : new URL(
`/notes/${note.id}/quotes`, `/notes/${note.id}/quotes`,
config.http.base_url, config.http.base_url,
), ).href,
next: next:
offset + limit < replyCount offset + limit < replyCount
? new URL( ? new URL(
@ -110,7 +110,7 @@ export default apiRoute((app) =>
offset + limit offset + limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
previous: previous:
offset - limit >= 0 offset - limit >= 0
@ -119,10 +119,10 @@ export default apiRoute((app) =>
offset - limit offset - limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
total: replyCount, total: replyCount,
items: replies.map((reply) => reply.getUri()), items: replies.map((reply) => reply.getUri().href),
}); });
// If base_url uses https and request uses http, rewrite request to use https // If base_url uses https and request uses http, rewrite request to use https

View file

@ -84,11 +84,11 @@ export default apiRoute((app) =>
); );
const uriCollection = new VersiaEntities.URICollection({ const uriCollection = new VersiaEntities.URICollection({
author: note.author.uri, author: note.author.uri.href,
first: new URL( first: new URL(
`/notes/${note.id}/replies?offset=0`, `/notes/${note.id}/replies?offset=0`,
config.http.base_url, config.http.base_url,
), ).href,
last: last:
replyCount > limit replyCount > limit
? new URL( ? new URL(
@ -96,11 +96,11 @@ export default apiRoute((app) =>
replyCount - limit replyCount - limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: new URL( : new URL(
`/notes/${note.id}/replies`, `/notes/${note.id}/replies`,
config.http.base_url, config.http.base_url,
), ).href,
next: next:
offset + limit < replyCount offset + limit < replyCount
? new URL( ? new URL(
@ -108,7 +108,7 @@ export default apiRoute((app) =>
offset + limit offset + limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
previous: previous:
offset - limit >= 0 offset - limit >= 0
@ -117,10 +117,10 @@ export default apiRoute((app) =>
offset - limit offset - limit
}`, }`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
total: replyCount, total: replyCount,
items: replies.map((reply) => reply.getUri()), items: replies.map((reply) => reply.getUri().href),
}); });
// If base_url uses https and request uses http, rewrite request to use https // If base_url uses https and request uses http, rewrite request to use https

View file

@ -98,28 +98,28 @@ export default apiRoute((app) =>
first: new URL( first: new URL(
`/users/${uuid}/outbox?page=1`, `/users/${uuid}/outbox?page=1`,
config.http.base_url, config.http.base_url,
), ).href,
last: new URL( last: new URL(
`/users/${uuid}/outbox?page=${Math.ceil( `/users/${uuid}/outbox?page=${Math.ceil(
totalNotes / NOTES_PER_PAGE, totalNotes / NOTES_PER_PAGE,
)}`, )}`,
config.http.base_url, config.http.base_url,
), ).href,
total: totalNotes, total: totalNotes,
author: author.uri, author: author.uri.href,
next: next:
notes.length === NOTES_PER_PAGE notes.length === NOTES_PER_PAGE
? new URL( ? new URL(
`/users/${uuid}/outbox?page=${pageNumber + 1}`, `/users/${uuid}/outbox?page=${pageNumber + 1}`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
previous: previous:
pageNumber > 1 pageNumber > 1
? new URL( ? new URL(
`/users/${uuid}/outbox?page=${pageNumber - 1}`, `/users/${uuid}/outbox?page=${pageNumber - 1}`,
config.http.base_url, config.http.base_url,
) ).href
: null, : null,
items: notes.map((note) => note.toVersia()), items: notes.map((note) => note.toVersia()),
}); });

View file

@ -306,7 +306,7 @@ export class Instance extends BaseInterface<typeof Instances> {
logo: metadata.data.logo, logo: metadata.data.logo,
protocol, protocol,
publicKey: metadata.data.public_key, publicKey: metadata.data.public_key,
inbox: metadata.data.shared_inbox?.href ?? null, inbox: metadata.data.shared_inbox ?? null,
extensions: metadata.data.extensions ?? null, extensions: metadata.data.extensions ?? null,
}); });
} }
@ -333,7 +333,7 @@ export class Instance extends BaseInterface<typeof Instances> {
logo: metadata.data.logo, logo: metadata.data.logo,
protocol, protocol,
publicKey: metadata.data.public_key, publicKey: metadata.data.public_key,
inbox: metadata.data.shared_inbox?.href ?? null, inbox: metadata.data.shared_inbox ?? null,
extensions: metadata.data.extensions ?? null, extensions: metadata.data.extensions ?? null,
}); });

View file

@ -155,13 +155,14 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
author: User.getUri( author: User.getUri(
this.data.liker.id, this.data.liker.id,
this.data.liker.uri ? new URL(this.data.liker.uri) : null, this.data.liker.uri ? new URL(this.data.liker.uri) : null,
), ).href,
type: "pub.versia:likes/Like", type: "pub.versia:likes/Like",
created_at: new Date(this.data.createdAt).toISOString(), created_at: new Date(this.data.createdAt).toISOString(),
liked: this.data.liked.uri liked: this.data.liked.uri
? new URL(this.data.liked.uri) ? new URL(this.data.liked.uri).href
: new URL(`/notes/${this.data.liked.id}`, config.http.base_url), : new URL(`/notes/${this.data.liked.id}`, config.http.base_url)
uri: this.getUri(), .href,
uri: this.getUri().href,
}); });
} }
@ -177,9 +178,9 @@ export class Like extends BaseInterface<typeof Likes, LikeType> {
: this.data.liker.uri : this.data.liker.uri
? new URL(this.data.liker.uri) ? new URL(this.data.liker.uri)
: null, : null,
), ).href,
deleted_type: "pub.versia:likes/Like", deleted_type: "pub.versia:likes/Like",
deleted: this.getUri(), deleted: this.getUri().href,
}); });
} }
} }

View file

@ -449,14 +449,14 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
replies_to, replies_to,
subject, subject,
} = versiaNote.data; } = versiaNote.data;
const instance = await Instance.resolve(authorUrl); const instance = await Instance.resolve(new URL(authorUrl));
const author = await User.resolve(authorUrl); const author = await User.resolve(new URL(authorUrl));
if (!author) { if (!author) {
throw new Error("Entity author could not be resolved"); throw new Error("Entity author could not be resolved");
} }
const existingNote = await Note.fromSql(eq(Notes.uri, uri.href)); const existingNote = await Note.fromSql(eq(Notes.uri, uri));
const note = const note =
existingNote ?? existingNote ??
@ -464,7 +464,7 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
id: randomUUIDv7(), id: randomUUIDv7(),
authorId: author.id, authorId: author.id,
visibility: "public", visibility: "public",
uri: uri.href, uri,
createdAt: new Date(created_at).toISOString(), createdAt: new Date(created_at).toISOString(),
})); }));
@ -480,15 +480,22 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
const mentions = ( const mentions = (
await Promise.all( await Promise.all(
noteMentions?.map((mention) => User.resolve(mention)) ?? [], noteMentions?.map((mention) =>
User.resolve(new URL(mention)),
) ?? [],
) )
).filter((m) => m !== null); ).filter((m) => m !== null);
// TODO: Implement groups // TODO: Implement groups
const visibility = !group || group instanceof URL ? "direct" : group; const visibility =
!group || URL.canParse(group)
? "direct"
: (group as "public" | "followers" | "unlisted");
const reply = replies_to ? await Note.resolve(replies_to) : null; const reply = replies_to
const quote = quotes ? await Note.resolve(quotes) : null; ? await Note.resolve(new URL(replies_to))
: null;
const quote = quotes ? await Note.resolve(new URL(quotes)) : null;
const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined; const spoiler = subject ? await sanitizedHtmlStrip(subject) : undefined;
await note.update({ await note.update({
@ -694,9 +701,9 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
return new VersiaEntities.Delete({ return new VersiaEntities.Delete({
type: "Delete", type: "Delete",
id, id,
author: this.author.uri, author: this.author.uri.href,
deleted_type: "Note", deleted_type: "Note",
deleted: this.getUri(), deleted: this.getUri().href,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
}); });
} }
@ -711,8 +718,8 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
type: "Note", type: "Note",
created_at: new Date(status.createdAt).toISOString(), created_at: new Date(status.createdAt).toISOString(),
id: status.id, id: status.id,
author: this.author.uri, author: this.author.uri.href,
uri: this.getUri(), uri: this.getUri().href,
content: { content: {
"text/html": { "text/html": {
content: status.content, content: status.content,
@ -727,11 +734,11 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
replies: new URL( replies: new URL(
`/notes/${status.id}/replies`, `/notes/${status.id}/replies`,
config.http.base_url, config.http.base_url,
), ).href,
quotes: new URL( quotes: new URL(
`/notes/${status.id}/quotes`, `/notes/${status.id}/quotes`,
config.http.base_url, config.http.base_url,
), ).href,
}, },
attachments: status.attachments.map( attachments: status.attachments.map(
(attachment) => (attachment) =>
@ -740,21 +747,24 @@ export class Note extends BaseInterface<typeof Notes, NoteTypeWithRelations> {
>, >,
), ),
is_sensitive: status.sensitive, is_sensitive: status.sensitive,
mentions: status.mentions.map((mention) => mentions: status.mentions.map(
(mention) =>
User.getUri( User.getUri(
mention.id, mention.id,
mention.uri ? new URL(mention.uri) : null, mention.uri ? new URL(mention.uri) : null,
), ).href,
), ),
quotes: status.quote quotes: status.quote
? status.quote.uri ? status.quote.uri
? new URL(status.quote.uri) ? new URL(status.quote.uri).href
: new URL(`/notes/${status.quote.id}`, config.http.base_url) : new URL(`/notes/${status.quote.id}`, config.http.base_url)
.href
: null, : null,
replies_to: status.reply replies_to: status.reply
? status.reply.uri ? status.reply.uri
? new URL(status.reply.uri) ? new URL(status.reply.uri).href
: new URL(`/notes/${status.reply.id}`, config.http.base_url) : new URL(`/notes/${status.reply.id}`, config.http.base_url)
.href
: null, : null,
subject: status.spoilerText, subject: status.spoilerText,
// TODO: Refactor as part of groups // TODO: Refactor as part of groups

View file

@ -179,17 +179,18 @@ export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
} }
return new VersiaEntities.Reaction({ return new VersiaEntities.Reaction({
uri: this.getUri(config.http.base_url), uri: this.getUri(config.http.base_url).href,
type: "pub.versia:reactions/Reaction", type: "pub.versia:reactions/Reaction",
author: User.getUri( author: User.getUri(
this.data.authorId, this.data.authorId,
this.data.author.uri ? new URL(this.data.author.uri) : null, this.data.author.uri ? new URL(this.data.author.uri) : null,
), ).href,
created_at: new Date(this.data.createdAt).toISOString(), created_at: new Date(this.data.createdAt).toISOString(),
id: this.id, id: this.id,
object: this.data.note.uri object: this.data.note.uri
? new URL(this.data.note.uri) ? new URL(this.data.note.uri).href
: new URL(`/notes/${this.data.noteId}`, config.http.base_url), : new URL(`/notes/${this.data.noteId}`, config.http.base_url)
.href,
content: this.hasCustomEmoji() content: this.hasCustomEmoji()
? `:${this.data.emoji?.shortcode}:` ? `:${this.data.emoji?.shortcode}:`
: this.data.emojiText || "", : this.data.emojiText || "",
@ -232,7 +233,7 @@ export class Reaction extends BaseInterface<typeof Reactions, ReactionType> {
return Reaction.insert({ return Reaction.insert({
id: randomUUIDv7(), id: randomUUIDv7(),
uri: reactionToConvert.data.uri.href, uri: reactionToConvert.data.uri,
authorId: author.id, authorId: author.id,
noteId: note.id, noteId: note.id,
emojiId: emoji ? emoji.id : null, emojiId: emoji ? emoji.id : null,

View file

@ -247,9 +247,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return new VersiaEntities.Unfollow({ return new VersiaEntities.Unfollow({
type: "Unfollow", type: "Unfollow",
id, id,
author: this.uri, author: this.uri.href,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
followee: followee.uri, followee: followee.uri.href,
}); });
} }
@ -265,9 +265,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
const entity = new VersiaEntities.FollowAccept({ const entity = new VersiaEntities.FollowAccept({
type: "FollowAccept", type: "FollowAccept",
id: crypto.randomUUID(), id: crypto.randomUUID(),
author: this.uri, author: this.uri.href,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
follower: follower.uri, follower: follower.uri.href,
}); });
await deliveryQueue.add(DeliveryJobType.FederateEntity, { await deliveryQueue.add(DeliveryJobType.FederateEntity, {
@ -289,9 +289,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
const entity = new VersiaEntities.FollowReject({ const entity = new VersiaEntities.FollowReject({
type: "FollowReject", type: "FollowReject",
id: crypto.randomUUID(), id: crypto.randomUUID(),
author: this.uri, author: this.uri.href,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
follower: follower.uri, follower: follower.uri.href,
}); });
await deliveryQueue.add(DeliveryJobType.FederateEntity, { await deliveryQueue.add(DeliveryJobType.FederateEntity, {
@ -675,9 +675,9 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
uri, uri,
extensions, extensions,
} = versiaUser.data; } = versiaUser.data;
const instance = await Instance.resolve(versiaUser.data.uri); const instance = await Instance.resolve(new URL(versiaUser.data.uri));
const existingUser = await User.fromSql( const existingUser = await User.fromSql(
eq(Users.uri, versiaUser.data.uri.href), eq(Users.uri, versiaUser.data.uri),
); );
const user = const user =
@ -686,7 +686,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
username, username,
id: randomUUIDv7(), id: randomUUIDv7(),
publicKey: public_key.key, publicKey: public_key.key,
uri: uri.href, uri,
instanceId: instance.id, instanceId: instance.id,
})); }));
@ -727,13 +727,13 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
await user.update({ await user.update({
createdAt: new Date(created_at).toISOString(), createdAt: new Date(created_at).toISOString(),
endpoints: { endpoints: {
inbox: inbox.href, inbox,
outbox: collections.outbox.href, outbox: collections.outbox,
followers: collections.followers.href, followers: collections.followers,
following: collections.following.href, following: collections.following,
featured: collections.featured.href, featured: collections.featured,
likes: collections["pub.versia:likes/Likes"]?.href, likes: collections["pub.versia:likes/Likes"] ?? undefined,
dislikes: collections["pub.versia:likes/Dislikes"]?.href, dislikes: collections["pub.versia:likes/Dislikes"] ?? undefined,
}, },
avatarId: userAvatar?.id, avatarId: userAvatar?.id,
headerId: userHeader?.id, headerId: userHeader?.id,
@ -1097,7 +1097,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
return new VersiaEntities.User({ return new VersiaEntities.User({
id: user.id, id: user.id,
type: "User", type: "User",
uri: this.uri, uri: this.uri.href,
bio: { bio: {
"text/html": { "text/html": {
content: user.note, content: user.note,
@ -1113,29 +1113,30 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
featured: new URL( featured: new URL(
`/users/${user.id}/featured`, `/users/${user.id}/featured`,
config.http.base_url, config.http.base_url,
), ).href,
"pub.versia:likes/Likes": new URL( "pub.versia:likes/Likes": new URL(
`/users/${user.id}/likes`, `/users/${user.id}/likes`,
config.http.base_url, config.http.base_url,
), ).href,
"pub.versia:likes/Dislikes": new URL( "pub.versia:likes/Dislikes": new URL(
`/users/${user.id}/dislikes`, `/users/${user.id}/dislikes`,
config.http.base_url, config.http.base_url,
), ).href,
followers: new URL( followers: new URL(
`/users/${user.id}/followers`, `/users/${user.id}/followers`,
config.http.base_url, config.http.base_url,
), ).href,
following: new URL( following: new URL(
`/users/${user.id}/following`, `/users/${user.id}/following`,
config.http.base_url, config.http.base_url,
), ).href,
outbox: new URL( outbox: new URL(
`/users/${user.id}/outbox`, `/users/${user.id}/outbox`,
config.http.base_url, config.http.base_url,
), ).href,
}, },
inbox: new URL(`/users/${user.id}/inbox`, config.http.base_url), inbox: new URL(`/users/${user.id}/inbox`, config.http.base_url)
.href,
indexable: this.data.isIndexable, indexable: this.data.isIndexable,
username: user.username, username: user.username,
manually_approves_followers: this.data.isLocked, manually_approves_followers: this.data.isLocked,
@ -1148,7 +1149,7 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
display_name: user.displayName, display_name: user.displayName,
fields: user.fields, fields: user.fields,
public_key: { public_key: {
actor: new URL(`/users/${user.id}`, config.http.base_url), actor: new URL(`/users/${user.id}`, config.http.base_url).href,
key: user.publicKey, key: user.publicKey,
algorithm: "ed25519", algorithm: "ed25519",
}, },

View file

@ -219,8 +219,8 @@ export class InboxProcessor {
private static async processFollowRequest( private static async processFollowRequest(
follow: VersiaEntities.Follow, follow: VersiaEntities.Follow,
): Promise<void> { ): Promise<void> {
const author = await User.resolve(follow.data.author); const author = await User.resolve(new URL(follow.data.author));
const followee = await User.resolve(follow.data.followee); const followee = await User.resolve(new URL(follow.data.followee));
if (!author) { if (!author) {
throw new ApiError(404, "Author not found"); throw new ApiError(404, "Author not found");
@ -267,8 +267,10 @@ export class InboxProcessor {
private static async processFollowAccept( private static async processFollowAccept(
followAccept: VersiaEntities.FollowAccept, followAccept: VersiaEntities.FollowAccept,
): Promise<void> { ): Promise<void> {
const author = await User.resolve(followAccept.data.author); const author = await User.resolve(new URL(followAccept.data.author));
const follower = await User.resolve(followAccept.data.follower); const follower = await User.resolve(
new URL(followAccept.data.follower),
);
if (!author) { if (!author) {
throw new ApiError(404, "Author not found"); throw new ApiError(404, "Author not found");
@ -302,8 +304,10 @@ export class InboxProcessor {
private static async processFollowReject( private static async processFollowReject(
followReject: VersiaEntities.FollowReject, followReject: VersiaEntities.FollowReject,
): Promise<void> { ): Promise<void> {
const author = await User.resolve(followReject.data.author); const author = await User.resolve(new URL(followReject.data.author));
const follower = await User.resolve(followReject.data.follower); const follower = await User.resolve(
new URL(followReject.data.follower),
);
if (!author) { if (!author) {
throw new ApiError(404, "Author not found"); throw new ApiError(404, "Author not found");
@ -340,13 +344,13 @@ export class InboxProcessor {
const toDelete = delete_.data.deleted; const toDelete = delete_.data.deleted;
const author = delete_.data.author const author = delete_.data.author
? await User.resolve(delete_.data.author) ? await User.resolve(new URL(delete_.data.author))
: null; : null;
switch (delete_.data.deleted_type) { switch (delete_.data.deleted_type) {
case "Note": { case "Note": {
const note = await Note.fromSql( const note = await Note.fromSql(
eq(Notes.uri, toDelete.href), eq(Notes.uri, toDelete),
author ? eq(Notes.authorId, author.id) : undefined, author ? eq(Notes.authorId, author.id) : undefined,
); );
@ -361,7 +365,7 @@ export class InboxProcessor {
return; return;
} }
case "User": { case "User": {
const userToDelete = await User.resolve(toDelete); const userToDelete = await User.resolve(new URL(toDelete));
if (!userToDelete) { if (!userToDelete) {
throw new ApiError(404, "User to delete not found"); throw new ApiError(404, "User to delete not found");
@ -376,7 +380,7 @@ export class InboxProcessor {
} }
case "pub.versia:likes/Like": { case "pub.versia:likes/Like": {
const like = await Like.fromSql( const like = await Like.fromSql(
eq(Likes.uri, toDelete.href), eq(Likes.uri, toDelete),
author ? eq(Likes.likerId, author.id) : undefined, author ? eq(Likes.likerId, author.id) : undefined,
); );
@ -393,7 +397,7 @@ export class InboxProcessor {
default: { default: {
throw new ApiError( throw new ApiError(
400, 400,
`Deletion of object ${toDelete.href} not implemented`, `Deletion of object ${toDelete} not implemented`,
); );
} }
} }
@ -408,8 +412,8 @@ export class InboxProcessor {
private static async processLikeRequest( private static async processLikeRequest(
like: VersiaEntities.Like, like: VersiaEntities.Like,
): Promise<void> { ): Promise<void> {
const author = await User.resolve(like.data.author); const author = await User.resolve(new URL(like.data.author));
const likedNote = await Note.resolve(like.data.liked); const likedNote = await Note.resolve(new URL(like.data.liked));
if (!author) { if (!author) {
throw new ApiError(404, "Author not found"); throw new ApiError(404, "Author not found");
@ -419,7 +423,7 @@ export class InboxProcessor {
throw new ApiError(404, "Liked Note not found"); throw new ApiError(404, "Liked Note not found");
} }
await author.like(likedNote, like.data.uri); await author.like(likedNote, new URL(like.data.uri));
} }
/** /**

View file

@ -35,6 +35,16 @@ export const getPushWorker = (): Worker<PushJobData, void, PushJobType> =>
return; return;
} }
if (
!(
config.notifications.push.vapid_keys.private ||
config.notifications.push.vapid_keys.public
)
) {
await job.log("Push notifications are not configured");
return;
}
await job.log( await job.log(
`Sending push notification for note [${notificationId}]`, `Sending push notification for note [${notificationId}]`,
); );
@ -132,8 +142,9 @@ export const getPushWorker = (): Worker<PushJobData, void, PushJobType> =>
config.notifications.push.subject || config.notifications.push.subject ||
config.http.base_url.origin, config.http.base_url.origin,
privateKey: privateKey:
config.notifications.push.vapid_keys.private, config.notifications.push.vapid_keys.private ?? "",
publicKey: config.notifications.push.vapid_keys.public, publicKey:
config.notifications.push.vapid_keys.public ?? "",
}, },
contentEncoding: "aesgcm", contentEncoding: "aesgcm",
}, },

View file

@ -118,7 +118,9 @@ export class FederationRequester {
} }
} }
nextUrl = collection.data.next; nextUrl = collection.data.next
? new URL(collection.data.next)
: null;
limit -= collection.data.items.length; limit -= collection.data.items.length;
} }
@ -136,7 +138,7 @@ export class FederationRequester {
limit?: number; limit?: number;
}, },
): Promise<URL[]> { ): Promise<URL[]> {
const entities: URL[] = []; const entities: string[] = [];
let nextUrl: URL | null = url; let nextUrl: URL | null = url;
let limit = options?.limit ?? Number.POSITIVE_INFINITY; let limit = options?.limit ?? Number.POSITIVE_INFINITY;
@ -147,11 +149,13 @@ export class FederationRequester {
); );
entities.push(...collection.data.items); entities.push(...collection.data.items);
nextUrl = collection.data.next; nextUrl = collection.data.next
? new URL(collection.data.next)
: null;
limit -= collection.data.items.length; limit -= collection.data.items.length;
} }
return entities; return entities.map((u) => new URL(u));
} }
/** /**

View file

@ -11,7 +11,4 @@ export const u64 = z
.nonnegative() .nonnegative()
.max(2 ** 64 - 1); .max(2 ** 64 - 1);
export const url = z export const url = z.string().url();
.string()
.url()
.transform((z) => new URL(z));