From cc96b0ba72a9796c555fd04b2efdc535ea5fca06 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sat, 7 Oct 2023 21:39:22 +0200 Subject: [PATCH] [mastodon-client] Use ctx instead of ctx.user as arguments everywhere --- .../server/api/mastodon/converters/note.ts | 11 +- .../api/mastodon/converters/notification.ts | 11 +- .../server/api/mastodon/endpoints/account.ts | 46 ++++---- .../src/server/api/mastodon/endpoints/list.ts | 16 +-- .../server/api/mastodon/endpoints/media.ts | 13 +- .../src/server/api/mastodon/endpoints/misc.ts | 8 +- .../api/mastodon/endpoints/notifications.ts | 16 +-- .../server/api/mastodon/endpoints/search.ts | 2 +- .../server/api/mastodon/endpoints/status.ts | 111 +++++++++--------- .../server/api/mastodon/endpoints/timeline.ts | 16 +-- .../src/server/api/mastodon/helpers/list.ts | 35 ++++-- .../src/server/api/mastodon/helpers/media.ts | 36 ++++-- .../src/server/api/mastodon/helpers/misc.ts | 19 +-- .../src/server/api/mastodon/helpers/note.ts | 58 +++++---- .../api/mastodon/helpers/notification.ts | 22 ++-- .../src/server/api/mastodon/helpers/poll.ts | 14 ++- .../src/server/api/mastodon/helpers/search.ts | 15 ++- .../server/api/mastodon/helpers/timeline.ts | 17 ++- .../src/server/api/mastodon/helpers/user.ts | 75 +++++++----- 19 files changed, 312 insertions(+), 229 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters/note.ts b/packages/backend/src/server/api/mastodon/converters/note.ts index 0868234e2..3e965db06 100644 --- a/packages/backend/src/server/api/mastodon/converters/note.ts +++ b/packages/backend/src/server/api/mastodon/converters/note.ts @@ -21,7 +21,8 @@ import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js"; import { MastoContext } from "@/server/api/mastodon/index.js"; export class NoteConverter { - public static async encode(note: Note, user: ILocalUser | null, ctx: MastoContext, recurse: boolean = true): Promise { + public static async encode(note: Note, ctx: MastoContext, recurse: boolean = true): Promise { + const user = ctx.user as ILocalUser | null; const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx); if (!await Notes.isVisibleForMe(note, user?.id ?? null)) @@ -109,7 +110,7 @@ export class NoteConverter { account: Promise.resolve(noteUser).then(p => UserConverter.encode(p, ctx)), in_reply_to_id: note.replyId, in_reply_to_account_id: note.replyUserId, - reblog: Promise.resolve(renote).then(renote => recurse && renote && note.text === null ? this.encode(renote, user, ctx, false) : null), + reblog: Promise.resolve(renote).then(renote => recurse && renote && note.text === null ? this.encode(renote, ctx, false) : null), content: text.then(text => text !== null ? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers)) ?? escapeMFM(text) : ""), text: text, created_at: note.createdAt.toISOString(), @@ -135,13 +136,13 @@ export class NoteConverter { // Use emojis list to provide URLs for emoji reactions. reactions: [], //FIXME: this.mapReactions(n.emojis, n.reactions, n.myReaction), bookmarked: isBookmarked, - quote: Promise.resolve(renote).then(renote => recurse && renote && note.text !== null ? this.encode(renote, user, ctx, false) : null), + quote: Promise.resolve(renote).then(renote => recurse && renote && note.text !== null ? this.encode(renote, ctx, false) : null), edited_at: note.updatedAt?.toISOString() }); } - public static async encodeMany(notes: Note[], user: ILocalUser | null, ctx: MastoContext): Promise { - const encoded = notes.map(n => this.encode(n, user, ctx)); + public static async encodeMany(notes: Note[], ctx: MastoContext): Promise { + const encoded = notes.map(n => this.encode(n, ctx)); return Promise.all(encoded); } } diff --git a/packages/backend/src/server/api/mastodon/converters/notification.ts b/packages/backend/src/server/api/mastodon/converters/notification.ts index 26fc3ed92..58c99050d 100644 --- a/packages/backend/src/server/api/mastodon/converters/notification.ts +++ b/packages/backend/src/server/api/mastodon/converters/notification.ts @@ -11,7 +11,8 @@ import { MastoContext } from "@/server/api/mastodon/index.js"; type NotificationType = typeof notificationTypes[number]; export class NotificationConverter { - public static async encode(notification: Notification, localUser: ILocalUser, ctx: MastoContext): Promise { + public static async encode(notification: Notification, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; if (notification.notifieeId !== localUser.id) throw new Error('User is not recipient of notification'); const account = notification.notifierId @@ -28,8 +29,8 @@ export class NotificationConverter { if (notification.note) { const isPureRenote = notification.note.renoteId !== null && notification.note.text === null; const encodedNote = isPureRenote - ? getNote(notification.note.renoteId!, localUser).then(note => NoteConverter.encode(note, localUser, ctx)) - : NoteConverter.encode(notification.note, localUser, ctx); + ? getNote(notification.note.renoteId!, localUser).then(note => NoteConverter.encode(note, ctx)) + : NoteConverter.encode(notification.note, ctx); result = Object.assign(result, { status: encodedNote, }); @@ -45,8 +46,8 @@ export class NotificationConverter { return awaitAll(result); } - public static async encodeMany(notifications: Notification[], localUser: ILocalUser, ctx: MastoContext): Promise { - const encoded = notifications.map(u => this.encode(u, localUser, ctx)); + public static async encodeMany(notifications: Notification[], ctx: MastoContext): Promise { + const encoded = notifications.map(u => this.encode(u, ctx)); return Promise.all(encoded) .then(p => p.filter(n => n !== null) as MastodonEntity.Notification[]); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 59f6f01e5..3dd1d530a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -6,22 +6,20 @@ import { UserConverter } from "@/server/api/mastodon/converters/user.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { ListHelpers } from "@/server/api/mastodon/helpers/list.js"; -import { Files } from "formidable"; import { auth } from "@/server/api/mastodon/middleware/auth.js"; export function setupEndpointsAccount(router: Router): void { router.get("/v1/accounts/verify_credentials", auth(true, ['read:accounts']), async (ctx) => { - const acct = await UserHelpers.verifyCredentials(ctx.user, ctx); + const acct = await UserHelpers.verifyCredentials(ctx); ctx.body = convertAccountId(acct); } ); router.patch("/v1/accounts/update_credentials", auth(true, ['write:accounts']), async (ctx) => { - const files = (ctx.request as any).files as Files | undefined; - const acct = await UserHelpers.updateCredentials(ctx.user, (ctx.request as any).body as any, files, ctx); + const acct = await UserHelpers.updateCredentials(ctx); ctx.body = convertAccountId(acct) } ); @@ -57,8 +55,8 @@ export function setupEndpointsAccount(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const query = await UserHelpers.getUserCachedOr404(userId, ctx); const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); - const res = await UserHelpers.getUserStatuses(query, ctx.user, args.max_id, args.since_id, args.min_id, args.limit, args['only_media'], args['exclude_replies'], args['exclude_reblogs'], args.pinned, args.tagged); - const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await UserHelpers.getUserStatuses(query, args.max_id, args.since_id, args.min_id, args.limit, args['only_media'], args['exclude_replies'], args['exclude_reblogs'], args.pinned, args.tagged, ctx); + const tl = await NoteConverter.encodeMany(res.data, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -77,7 +75,7 @@ export function setupEndpointsAccount(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const query = await UserHelpers.getUserCachedOr404(userId, ctx); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserFollowers(query, ctx.user, args.max_id, args.since_id, args.min_id, args.limit); + const res = await UserHelpers.getUserFollowers(query, args.max_id, args.since_id, args.min_id, args.limit, ctx); const followers = await UserConverter.encodeMany(res.data, ctx); ctx.body = followers.map((account) => convertAccountId(account)); @@ -91,7 +89,7 @@ export function setupEndpointsAccount(router: Router): void { const userId = convertId(ctx.params.id, IdType.IceshrimpId); const query = await UserHelpers.getUserCachedOr404(userId, ctx); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserFollowing(query, ctx.user, args.max_id, args.since_id, args.min_id, args.limit); + const res = await UserHelpers.getUserFollowing(query, args.max_id, args.since_id, args.min_id, args.limit, ctx); const following = await UserConverter.encodeMany(res.data, ctx); ctx.body = following.map((account) => convertAccountId(account)); @@ -103,7 +101,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:lists"]), async (ctx) => { const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const results = await ListHelpers.getListsByMember(ctx.user, member); + const results = await ListHelpers.getListsByMember(member, ctx); ctx.body = results.map(p => convertListId(p)); }, ); @@ -113,7 +111,7 @@ export function setupEndpointsAccount(router: Router): void { async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); //FIXME: Parse form data - const result = await UserHelpers.followUser(target, ctx.user, true, false); + const result = await UserHelpers.followUser(target, true, false, ctx); ctx.body = convertRelationshipId(result); }, ); @@ -122,7 +120,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:follows"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.unfollowUser(target, ctx.user); + const result = await UserHelpers.unfollowUser(target, ctx); ctx.body = convertRelationshipId(result); }, ); @@ -131,7 +129,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:blocks"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.blockUser(target, ctx.user); + const result = await UserHelpers.blockUser(target, ctx); ctx.body = convertRelationshipId(result); }, ); @@ -140,7 +138,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:blocks"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.unblockUser(target, ctx.user); + const result = await UserHelpers.unblockUser(target, ctx); ctx.body = convertRelationshipId(result) }, ); @@ -151,7 +149,7 @@ export function setupEndpointsAccount(router: Router): void { //FIXME: parse form data const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, ['duration']), ['notifications'])); const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.muteUser(target, ctx.user, args.notifications, args.duration); + const result = await UserHelpers.muteUser(target, args.notifications, args.duration, ctx); ctx.body = convertRelationshipId(result) }, ); @@ -160,7 +158,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:mutes"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.unmuteUser(target, ctx.user); + const result = await UserHelpers.unmuteUser(target, ctx); ctx.body = convertRelationshipId(result) }, ); @@ -178,8 +176,8 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:bookmarks"]), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserBookmarks(ctx.user, args.max_id, args.since_id, args.min_id, args.limit); - const bookmarks = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await UserHelpers.getUserBookmarks(args.max_id, args.since_id, args.min_id, args.limit, ctx); + const bookmarks = await NoteConverter.encodeMany(res.data, ctx); ctx.body = bookmarks.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; } @@ -188,8 +186,8 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:favourites"]), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserFavorites(ctx.user, args.max_id, args.since_id, args.min_id, args.limit); - const favorites = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await UserHelpers.getUserFavorites(args.max_id, args.since_id, args.min_id, args.limit, ctx); + const favorites = await NoteConverter.encodeMany(res.data, ctx); ctx.body = favorites.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; } @@ -198,7 +196,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:mutes"]), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserMutes(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, ctx); + const res = await UserHelpers.getUserMutes(args.max_id, args.since_id, args.min_id, args.limit, ctx); ctx.body = res.data.map(m => convertAccountId(m)); ctx.pagination = res.pagination; } @@ -207,7 +205,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:blocks"]), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserBlocks(ctx.user, args.max_id, args.since_id, args.min_id, args.limit); + const res = await UserHelpers.getUserBlocks(args.max_id, args.since_id, args.min_id, args.limit, ctx); const blocks = await UserConverter.encodeMany(res.data, ctx); ctx.body = blocks.map(b => convertAccountId(b)); ctx.pagination = res.pagination; @@ -217,7 +215,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["read:follows"]), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await UserHelpers.getUserFollowRequests(ctx.user, args.max_id, args.since_id, args.min_id, args.limit); + const res = await UserHelpers.getUserFollowRequests(args.max_id, args.since_id, args.min_id, args.limit, ctx); const requests = await UserConverter.encodeMany(res.data, ctx); ctx.body = requests.map(b => convertAccountId(b)); ctx.pagination = res.pagination; @@ -228,7 +226,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:follows"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.acceptFollowRequest(target, ctx.user); + const result = await UserHelpers.acceptFollowRequest(target, ctx); ctx.body = convertRelationshipId(result); }, ); @@ -237,7 +235,7 @@ export function setupEndpointsAccount(router: Router): void { auth(true, ["write:follows"]), async (ctx) => { const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); - const result = await UserHelpers.rejectFollowRequest(target, ctx.user); + const result = await UserHelpers.rejectFollowRequest(target, ctx); ctx.body = convertRelationshipId(result); }, ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/list.ts b/packages/backend/src/server/api/mastodon/endpoints/list.ts index fcc42e38a..ae29370e3 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/list.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/list.ts @@ -14,7 +14,7 @@ export function setupEndpointsList(router: Router): void { router.get("/v1/lists", auth(true, ['read:lists']), async (ctx, reply) => { - ctx.body = await ListHelpers.getLists(ctx.user) + ctx.body = await ListHelpers.getLists(ctx) .then(p => p.map(list => convertListId(list))); } ); @@ -24,7 +24,7 @@ export function setupEndpointsList(router: Router): void { async (ctx, reply) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - ctx.body = await ListHelpers.getListOr404(ctx.user, id) + ctx.body = await ListHelpers.getListOr404(id, ctx) .then(p => convertListId(p)); }, ); @@ -34,7 +34,7 @@ export function setupEndpointsList(router: Router): void { const body = ctx.request.body as any; const title = (body.title ?? '').trim(); - ctx.body = await ListHelpers.createList(ctx.user, title) + ctx.body = await ListHelpers.createList(title, ctx) .then(p => convertListId(p)); } ); @@ -48,7 +48,7 @@ export function setupEndpointsList(router: Router): void { const body = ctx.request.body as any; const title = (body.title ?? '').trim(); - ctx.body = await ListHelpers.updateList(ctx.user, list, title) + ctx.body = await ListHelpers.updateList(list, title, ctx) .then(p => convertListId(p)); }, ); @@ -60,7 +60,7 @@ export function setupEndpointsList(router: Router): void { const list = await UserLists.findOneBy({ userId: ctx.user.id, id: id }); if (!list) throw new MastoApiError(404); - await ListHelpers.deleteList(ctx.user, list); + await ListHelpers.deleteList(list, ctx); ctx.body = {}; }, ); @@ -70,7 +70,7 @@ export function setupEndpointsList(router: Router): void { async (ctx, reply) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); - const res = await ListHelpers.getListUsers(ctx.user, id, args.max_id, args.since_id, args.min_id, args.limit); + const res = await ListHelpers.getListUsers(id, args.max_id, args.since_id, args.min_id, args.limit, ctx); const accounts = await UserConverter.encodeMany(res.data, ctx); ctx.body = accounts.map(account => convertAccountId(account)); @@ -90,7 +90,7 @@ export function setupEndpointsList(router: Router): void { const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId)); const targets = await Promise.all(ids.map(p => getUser(p))); - await ListHelpers.addToList(ctx.user, list, targets); + await ListHelpers.addToList(list, targets, ctx); ctx.body = {} }, ); @@ -107,7 +107,7 @@ export function setupEndpointsList(router: Router): void { const ids = toArray(body['account_ids']).map(p => convertId(p, IdType.IceshrimpId)); const targets = await Promise.all(ids.map(p => getUser(p))); - await ListHelpers.removeFromList(ctx.user, list, targets); + await ListHelpers.removeFromList(list, targets, ctx); ctx.body = {} }, ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/media.ts b/packages/backend/src/server/api/mastodon/endpoints/media.ts index c336d8de2..a37869788 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/media.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/media.ts @@ -3,8 +3,6 @@ import { convertId, IdType } from "@/misc/convert-id.js"; import { convertAttachmentId } from "@/server/api/mastodon/converters.js"; import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js"; import { FileConverter } from "@/server/api/mastodon/converters/file.js"; -import { Files } from "formidable"; -import { toSingleLast } from "@/prelude/array.js"; import { auth } from "@/server/api/mastodon/middleware/auth.js"; export function setupEndpointsMedia(router: Router): void { @@ -12,7 +10,7 @@ export function setupEndpointsMedia(router: Router): void { auth(true, ['write:media']), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const file = await MediaHelpers.getMediaPackedOr404(ctx.user, id); + const file = await MediaHelpers.getMediaPackedOr404(id, ctx); const attachment = FileConverter.encode(file); ctx.body = convertAttachmentId(attachment); } @@ -21,8 +19,8 @@ export function setupEndpointsMedia(router: Router): void { auth(true, ['write:media']), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const file = await MediaHelpers.getMediaOr404(ctx.user, id); - const result = await MediaHelpers.updateMedia(ctx.user, file, ctx.request.body) + const file = await MediaHelpers.getMediaOr404(id, ctx); + const result = await MediaHelpers.updateMedia(file, ctx) .then(p => FileConverter.encode(p)); ctx.body = convertAttachmentId(result); } @@ -30,10 +28,7 @@ export function setupEndpointsMedia(router: Router): void { router.post(["/v2/media", "/v1/media"], auth(true, ['write:media']), async (ctx) => { - //FIXME: why do we have to cast this to any first? - const files = (ctx.request as any).files as Files | undefined; - const file = toSingleLast(files?.file); - const result = await MediaHelpers.uploadMedia(ctx.user, file, ctx.request.body) + const result = await MediaHelpers.uploadMedia(ctx) .then(p => FileConverter.encode(p)); ctx.body = convertAttachmentId(result); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/misc.ts b/packages/backend/src/server/api/mastodon/endpoints/misc.ts index 0b3808865..0077ab2fa 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/misc.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/misc.ts @@ -24,7 +24,7 @@ export function setupEndpointsMisc(router: Router): void { auth(true), async (ctx) => { const args = argsToBools(ctx.query, ['with_dismissed']); - ctx.body = await MiscHelpers.getAnnouncements(ctx.user, args['with_dismissed']) + ctx.body = await MiscHelpers.getAnnouncements(args['with_dismissed'], ctx) .then(p => p.map(x => convertAnnouncementId(x))); } ); @@ -37,7 +37,7 @@ export function setupEndpointsMisc(router: Router): void { const announcement = await Announcements.findOneBy({ id: id }); if (!announcement) throw new MastoApiError(404); - await MiscHelpers.dismissAnnouncement(announcement, ctx.user); + await MiscHelpers.dismissAnnouncement(announcement, ctx); ctx.body = {}; }, ); @@ -68,7 +68,7 @@ export function setupEndpointsMisc(router: Router): void { router.get("/v1/preferences", auth(true, ['read:accounts']), async (ctx) => { - ctx.body = await MiscHelpers.getPreferences(ctx.user); + ctx.body = await MiscHelpers.getPreferences(ctx); } ); @@ -76,7 +76,7 @@ export function setupEndpointsMisc(router: Router): void { auth(true, ['read']), async (ctx) => { const args = limitToInt(ctx.query); - ctx.body = await MiscHelpers.getFollowSuggestions(ctx.user, args.limit, ctx) + ctx.body = await MiscHelpers.getFollowSuggestions(args.limit, ctx) .then(p => p.map(x => convertSuggestionIds(x))); } ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 209c02eef..b713e9804 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -11,8 +11,8 @@ export function setupEndpointsNotifications(router: Router): void { auth(true, ['read:notifications']), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']); - const res = await NotificationHelpers.getNotifications(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id); - const data = await NotificationConverter.encodeMany(res.data, ctx.user, ctx); + const res = await NotificationHelpers.getNotifications(args.max_id, args.since_id, args.min_id, args.limit, args['types[]'], args['exclude_types[]'], args.account_id, ctx); + const data = await NotificationConverter.encodeMany(res.data, ctx); ctx.body = data.map(n => convertNotificationIds(n)); ctx.pagination = res.pagination; @@ -22,15 +22,15 @@ export function setupEndpointsNotifications(router: Router): void { router.get("/v1/notifications/:id", auth(true, ['read:notifications']), async (ctx) => { - const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx.user); - ctx.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx.user, ctx)); + const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); + ctx.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx)); } ); router.post("/v1/notifications/clear", auth(true, ['write:notifications']), async (ctx) => { - await NotificationHelpers.clearAllNotifications(ctx.user); + await NotificationHelpers.clearAllNotifications(ctx); ctx.body = {}; } ); @@ -38,8 +38,8 @@ export function setupEndpointsNotifications(router: Router): void { router.post("/v1/notifications/:id/dismiss", auth(true, ['write:notifications']), async (ctx) => { - const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx.user); - await NotificationHelpers.dismissNotification(notification.id, ctx.user); + const notification = await NotificationHelpers.getNotificationOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); + await NotificationHelpers.dismissNotification(notification.id, ctx); ctx.body = {}; } ); @@ -48,7 +48,7 @@ export function setupEndpointsNotifications(router: Router): void { auth(true, ['write:conversations']), async (ctx, reply) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - await NotificationHelpers.markConversationAsRead(id, ctx.user); + await NotificationHelpers.markConversationAsRead(id, ctx); ctx.body = {}; } ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 8e4cece15..c6384dcaa 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -9,7 +9,7 @@ export function setupEndpointsSearch(router: Router): void { auth(true, ['read:search']), async (ctx) => { const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query), ['resolve', 'following', 'exclude_unreviewed']))); - const result = await SearchHelpers.search(ctx.user, args.q, args.type, args.resolve, args.following, args.account_id, args['exclude_unreviewed'], args.max_id, args.min_id, args.limit, args.offset, ctx); + const result = await SearchHelpers.search(args.q, args.type, args.resolve, args.following, args.account_id, args['exclude_unreviewed'], args.max_id, args.min_id, args.limit, args.offset, ctx); ctx.body = convertSearchIds(result); diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index cf456d37d..66e458210 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -20,7 +20,7 @@ export function setupEndpointsStatus(router: Router): void { router.post("/v1/statuses", auth(true, ['write:statuses']), async (ctx) => { - const key = NoteHelpers.getIdempotencyKey(ctx.headers, ctx.user); + const key = NoteHelpers.getIdempotencyKey(ctx); if (key !== null) { const result = await NoteHelpers.getFromIdempotencyCache(key); @@ -31,8 +31,8 @@ export function setupEndpointsStatus(router: Router): void { } let request = NoteHelpers.normalizeComposeOptions(ctx.request.body); - ctx.body = await NoteHelpers.createNote(request, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.createNote(request, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body }); @@ -42,10 +42,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ['write:statuses']), async (ctx) => { const noteId = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(noteId, ctx.user); + const note = await NoteHelpers.getNoteOr404(noteId, ctx); let request = NoteHelpers.normalizeEditOptions(ctx.request.body); - ctx.body = await NoteHelpers.editNote(request, note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.editNote(request, note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); } ); @@ -53,9 +53,9 @@ export function setupEndpointsStatus(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const noteId = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(noteId, ctx.user); + const note = await NoteHelpers.getNoteOr404(noteId, ctx); - const status = await NoteConverter.encode(note, ctx.user, ctx); + const status = await NoteConverter.encode(note, ctx); ctx.body = convertStatusIds(status); } ); @@ -63,8 +63,8 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ['write:statuses']), async (ctx) => { const noteId = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(noteId, ctx.user); - ctx.body = await NoteHelpers.deleteNote(note, ctx.user, ctx) + const note = await NoteHelpers.getNoteOr404(noteId, ctx); + ctx.body = await NoteHelpers.deleteNote(note, ctx) .then(p => convertStatusIds(p)); } ); @@ -73,13 +73,14 @@ export function setupEndpointsStatus(router: Router): void { "/v1/statuses/:id/context", auth(false, ["read:statuses"]), async (ctx) => { + //FIXME: determine final limits within helper functions instead of here const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); - const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user, ctx.user ? 4096 : 60) - .then(n => NoteConverter.encodeMany(n, ctx.user, ctx)) + const note = await NoteHelpers.getNoteOr404(id, ctx); + const ancestors = await NoteHelpers.getNoteAncestors(note, ctx.user ? 4096 : 60, ctx) + .then(n => NoteConverter.encodeMany(n, ctx)) .then(n => n.map(s => convertStatusIds(s))); - const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20) - .then(n => NoteConverter.encodeMany(n, ctx.user, ctx)) + const descendants = await NoteHelpers.getNoteDescendants(note, ctx.user ? 4096 : 40, ctx.user ? 4096 : 20, ctx) + .then(n => NoteConverter.encodeMany(n, ctx)) .then(n => n.map(s => convertStatusIds(s))); ctx.body = { @@ -93,7 +94,7 @@ export function setupEndpointsStatus(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const res = await NoteHelpers.getNoteEditHistory(note, ctx); ctx.body = res.map(p => convertStatusEditIds(p)); } @@ -103,7 +104,7 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["read:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const src = NoteHelpers.getNoteSource(note); ctx.body = convertStatusSourceId(src); } @@ -113,9 +114,9 @@ export function setupEndpointsStatus(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); - const res = await NoteHelpers.getNoteRebloggedBy(note, ctx.user, args.max_id, args.since_id, args.min_id, args.limit); + const res = await NoteHelpers.getNoteRebloggedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx); const users = await UserConverter.encodeMany(res.data, ctx); ctx.body = users.map(m => convertAccountId(m)); ctx.pagination = res.pagination; @@ -126,7 +127,7 @@ export function setupEndpointsStatus(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit); const users = await UserConverter.encodeMany(res.data, ctx); @@ -139,11 +140,11 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:favourites"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const reaction = await NoteHelpers.getDefaultReaction(); - ctx.body = await NoteHelpers.reactToNote(note, ctx.user, reaction) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.reactToNote(note, reaction, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); } ); @@ -152,10 +153,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:favourites"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.removeReactFromNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -165,10 +166,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.reblogNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.reblogNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -178,10 +179,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.unreblogNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.unreblogNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -191,10 +192,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:bookmarks"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.bookmarkNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.bookmarkNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -204,10 +205,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:bookmarks"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.unbookmarkNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.unbookmarkNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -217,10 +218,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:accounts"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.pinNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.pinNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -230,10 +231,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:accounts"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.unpinNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.unpinNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -243,10 +244,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:favourites"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.reactToNote(note, ctx.user, ctx.params.name) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.reactToNote(note, ctx.params.name, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -256,10 +257,10 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:favourites"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); - ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user, ctx)) + ctx.body = await NoteHelpers.removeReactFromNote(note, ctx) + .then(p => NoteConverter.encode(p, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -267,8 +268,8 @@ export function setupEndpointsStatus(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); - const data = await PollHelpers.getPoll(note, ctx.user, ctx); + const note = await NoteHelpers.getNoteOr404(id, ctx); + const data = await PollHelpers.getPoll(note, ctx); ctx.body = convertPollId(data); }); router.post<{ Params: { id: string } }>( @@ -276,13 +277,13 @@ export function setupEndpointsStatus(router: Router): void { auth(true, ["write:statuses"]), async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); - const note = await NoteHelpers.getNoteOr404(id, ctx.user); + const note = await NoteHelpers.getNoteOr404(id, ctx); const body: any = ctx.request.body; const choices = toArray(body.choices ?? []).map(p => parseInt(p)); - if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option"); + if (choices.length < 1) throw new MastoApiError(400, "Must vote for at least one option"); - const data = await PollHelpers.voteInPoll(choices, note, ctx.user, ctx); + const data = await PollHelpers.voteInPoll(choices, note, ctx); ctx.body = convertPollId(data); }, ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index db15ed6ca..897f6707d 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -67,8 +67,8 @@ export function setupEndpointsTimeline(router: Router): void { auth(true, ['read:statuses']), async (ctx, reply) => { const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); - const res = await TimelineHelpers.getPublicTimeline(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote); - const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await TimelineHelpers.getPublicTimeline(args.max_id, args.since_id, args.min_id, args.limit, args.only_media, args.local, args.remote, ctx); + const tl = await NoteConverter.encodeMany(res.data, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -80,7 +80,7 @@ export function setupEndpointsTimeline(router: Router): void { const tag = (ctx.params.hashtag ?? '').trim(); const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ['any[]', 'all[]', 'none[]']); const res = await TimelineHelpers.getTagTimeline(ctx.user, tag, args.max_id, args.since_id, args.min_id, args.limit, args['any[]'] ?? [], args['all[]'] ?? [], args['none[]'] ?? [], args.only_media, args.local, args.remote); - const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const tl = await NoteConverter.encodeMany(res.data, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -90,8 +90,8 @@ export function setupEndpointsTimeline(router: Router): void { auth(true, ['read:statuses']), async (ctx, reply) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); - const res = await TimelineHelpers.getHomeTimeline(ctx.user, args.max_id, args.since_id, args.min_id, args.limit); - const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await TimelineHelpers.getHomeTimeline(args.max_id, args.since_id, args.min_id, args.limit, ctx); + const tl = await NoteConverter.encodeMany(res.data, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -105,8 +105,8 @@ export function setupEndpointsTimeline(router: Router): void { if (!list) throw new MastoApiError(404); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); - const res = await TimelineHelpers.getListTimeline(ctx.user, list, args.max_id, args.since_id, args.min_id, args.limit); - const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); + const res = await TimelineHelpers.getListTimeline(list, args.max_id, args.since_id, args.min_id, args.limit, ctx); + const tl = await NoteConverter.encodeMany(res.data, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -116,7 +116,7 @@ export function setupEndpointsTimeline(router: Router): void { auth(true, ['read:statuses']), async (ctx, reply) => { const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); - const res = await TimelineHelpers.getConversations(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, ctx); + const res = await TimelineHelpers.getConversations(args.max_id, args.since_id, args.min_id, args.limit, ctx); ctx.body = res.data.map(c => convertConversationIds(c)); ctx.pagination = res.pagination; diff --git a/packages/backend/src/server/api/mastodon/helpers/list.ts b/packages/backend/src/server/api/mastodon/helpers/list.ts index ba2f64f5c..db7fa6136 100644 --- a/packages/backend/src/server/api/mastodon/helpers/list.ts +++ b/packages/backend/src/server/api/mastodon/helpers/list.ts @@ -7,9 +7,12 @@ import { pushUserToUserList } from "@/services/user-list/push.js"; import { genId } from "@/misc/gen-id.js"; import { publishUserListStream } from "@/services/stream.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; export class ListHelpers { - public static async getLists(user: ILocalUser): Promise { + public static async getLists(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + return UserLists.findBy({ userId: user.id }).then(p => p.map(list => { return { id: list.id, @@ -18,7 +21,9 @@ export class ListHelpers { })); } - public static async getList(user: ILocalUser, id: string): Promise { + public static async getList(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + return UserLists.findOneByOrFail({ userId: user.id, id: id }).then(list => { return { id: list.id, @@ -27,14 +32,15 @@ export class ListHelpers { }); } - public static async getListOr404(user: ILocalUser, id: string): Promise { - return this.getList(user, id).catch(_ => { + public static async getListOr404(id: string, ctx: MastoContext): Promise { + return this.getList(id, ctx).catch(_ => { throw new MastoApiError(404); }) } - public static async getListUsers(user: ILocalUser, id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + public static async getListUsers(id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser; const list = await UserLists.findOneBy({ userId: user.id, id: id }); if (!list) throw new MastoApiError(404); const query = PaginationHelpers.makePaginationQuery( @@ -59,12 +65,14 @@ export class ListHelpers { }); } - public static async deleteList(user: ILocalUser, list: UserList) { + public static async deleteList(list: UserList, ctx: MastoContext) { + const user = ctx.user as ILocalUser; if (user.id != list.userId) throw new Error("List is not owned by user"); await UserLists.delete(list.id); } - public static async addToList(localUser: ILocalUser, list: UserList, usersToAdd: User[]) { + public static async addToList(list: UserList, usersToAdd: User[], ctx: MastoContext) { + const localUser = ctx.user as ILocalUser; if (localUser.id != list.userId) throw new Error("List is not owned by user"); for (const user of usersToAdd) { if (user.id !== localUser.id) { @@ -89,7 +97,8 @@ export class ListHelpers { } } - public static async removeFromList(localUser: ILocalUser, list: UserList, usersToRemove: User[]) { + public static async removeFromList(list: UserList, usersToRemove: User[], ctx: MastoContext) { + const localUser = ctx.user as ILocalUser; if (localUser.id != list.userId) throw new Error("List is not owned by user"); for (const user of usersToRemove) { const exist = await UserListJoinings.exist({ @@ -105,9 +114,10 @@ export class ListHelpers { } } - public static async createList(user: ILocalUser, title: string): Promise { + public static async createList(title: string, ctx: MastoContext): Promise { if (title.length < 1) throw new MastoApiError(400, "Title must not be empty"); + const user = ctx.user as ILocalUser; const list = await UserLists.insert({ id: genId(), createdAt: new Date(), @@ -121,8 +131,10 @@ export class ListHelpers { }; } - public static async updateList(user: ILocalUser, list: UserList, title: string) { + public static async updateList(list: UserList, title: string, ctx: MastoContext) { if (title.length < 1) throw new MastoApiError(400, "Title must not be empty"); + + const user = ctx.user as ILocalUser; if (user.id != list.userId) throw new Error("List is not owned by user"); const partial = { name: title }; @@ -135,7 +147,8 @@ export class ListHelpers { }; } - public static async getListsByMember(user: ILocalUser, member: User): Promise { + public static async getListsByMember(member: User, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const joinQuery = UserListJoinings.createQueryBuilder('member') .select("member.userListId") .where("member.userId = :memberId"); diff --git a/packages/backend/src/server/api/mastodon/helpers/media.ts b/packages/backend/src/server/api/mastodon/helpers/media.ts index b20b4ee38..8f9881ebd 100644 --- a/packages/backend/src/server/api/mastodon/helpers/media.ts +++ b/packages/backend/src/server/api/mastodon/helpers/media.ts @@ -3,11 +3,18 @@ import { ILocalUser } from "@/models/entities/user.js"; import { DriveFiles } from "@/models/index.js"; import { Packed } from "@/misc/schema.js"; import { DriveFile } from "@/models/entities/drive-file.js"; -import { File } from "formidable"; +import { File, Files } from "formidable"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; +import { toSingleLast } from "@/prelude/array.js"; export class MediaHelpers { - public static async uploadMedia(user: ILocalUser, file: File | undefined, body: any): Promise> { + public static async uploadMedia(ctx: MastoContext): Promise> { + const files = ctx.request.files as Files | undefined; + const file = toSingleLast(files?.file); + const user = ctx.user as ILocalUser + const body = ctx.request.body as any; + if (!file) throw new MastoApiError(400, "Validation failed: File content type is invalid, File is invalid"); return addFile({ @@ -20,7 +27,9 @@ export class MediaHelpers { .then(p => DriveFiles.pack(p)); } - public static async uploadMediaBasic(user: ILocalUser, file: File): Promise { + public static async uploadMediaBasic(file: File, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + return addFile({ user: user, path: file.filepath, @@ -29,7 +38,10 @@ export class MediaHelpers { }) } - public static async updateMedia(user: ILocalUser, file: DriveFile, body: any): Promise> { + public static async updateMedia(file: DriveFile, ctx: MastoContext): Promise> { + const user = ctx.user as ILocalUser; + const body = ctx.request.body as any; + await DriveFiles.update(file.id, { comment: body?.description ?? undefined }); @@ -38,24 +50,26 @@ export class MediaHelpers { .then(p => DriveFiles.pack(p)); } - public static async getMediaPacked(user: ILocalUser, id: string): Promise | null> { - return this.getMedia(user, id) + public static async getMediaPacked(id: string, ctx: MastoContext): Promise | null> { + const user = ctx.user as ILocalUser; + return this.getMedia(id, ctx) .then(p => p ? DriveFiles.pack(p) : null); } - public static async getMediaPackedOr404(user: ILocalUser, id: string): Promise> { - return this.getMediaPacked(user, id).then(p => { + public static async getMediaPackedOr404(id: string, ctx: MastoContext): Promise> { + return this.getMediaPacked(id, ctx).then(p => { if (p) return p; throw new MastoApiError(404); }); } - public static async getMedia(user: ILocalUser, id: string): Promise { + public static async getMedia(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return DriveFiles.findOneBy({ id: id, userId: user.id }); } - public static async getMediaOr404(user: ILocalUser, id: string): Promise { - return this.getMedia(user, id).then(p => { + public static async getMediaOr404(id: string, ctx: MastoContext): Promise { + return this.getMedia(id, ctx).then(p => { if (p) return p; throw new MastoApiError(404); }); diff --git a/packages/backend/src/server/api/mastodon/helpers/misc.ts b/packages/backend/src/server/api/mastodon/helpers/misc.ts index 59186c711..39a620ce8 100644 --- a/packages/backend/src/server/api/mastodon/helpers/misc.ts +++ b/packages/backend/src/server/api/mastodon/helpers/misc.ts @@ -96,7 +96,9 @@ export class MiscHelpers { return awaitAll(res); } - public static async getAnnouncements(user: ILocalUser, includeRead: boolean = false): Promise { + public static async getAnnouncements(includeRead: boolean = false, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + if (includeRead) { const [announcements, reads] = await Promise.all([ Announcements.createQueryBuilder("announcement") @@ -122,7 +124,8 @@ export class MiscHelpers { .then(p => p.map(x => AnnouncementConverter.encode(x, false))); } - public static async dismissAnnouncement(announcement: Announcement, user: ILocalUser): Promise { + public static async dismissAnnouncement(announcement: Announcement, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const exists = await AnnouncementReads.exist({ where: { userId: user.id, announcementId: announcement.id } }); if (!exists) { await AnnouncementReads.insert({ @@ -134,7 +137,8 @@ export class MiscHelpers { } } - public static async getFollowSuggestions(user: ILocalUser, limit: number, ctx: MastoContext): Promise { + public static async getFollowSuggestions(limit: number, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const results: Promise[] = []; const pinned = fetchMeta().then(meta => Promise.all( @@ -220,7 +224,7 @@ export class MiscHelpers { .skip(offset) .take(limit) .getMany() - .then(result => NoteConverter.encodeMany(result, null, ctx)); + .then(result => NoteConverter.encodeMany(result, ctx)); } public static async getTrendingHashtags(limit: number = 10, offset: number = 0): Promise { @@ -229,11 +233,12 @@ export class MiscHelpers { //FIXME: This was already implemented in api/endpoints/hashtags/trend.ts, but the implementation is sketchy at best. Rewrite from scratch. } - public static getPreferences(user: ILocalUser): Promise { + public static getPreferences(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const profile = UserProfiles.findOneByOrFail({ userId: user.id }); const sensitive = profile.then(p => p.alwaysMarkNsfw); const language = profile.then(p => p.lang); - const privacy = UserHelpers.getDefaultNoteVisibility(user) + const privacy = UserHelpers.getDefaultNoteVisibility(ctx) .then(p => VisibilityConverter.encode(p)); const res = { @@ -246,4 +251,4 @@ export class MiscHelpers { return awaitAll(res); } -} \ No newline at end of file +} diff --git a/packages/backend/src/server/api/mastodon/helpers/note.ts b/packages/backend/src/server/api/mastodon/helpers/note.ts index e756cad62..5687dde7a 100644 --- a/packages/backend/src/server/api/mastodon/helpers/note.ts +++ b/packages/backend/src/server/api/mastodon/helpers/note.ts @@ -47,7 +47,8 @@ export class NoteHelpers { }); } - public static async reactToNote(note: Note, user: ILocalUser, reaction: string): Promise { + public static async reactToNote(note: Note, reaction: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; await createReaction(user, note, reaction).catch(e => { if (e instanceof IdentifiableError && e.id == '51c42bb4-931a-456b-bff7-e5a8a70dd298') return; throw e; @@ -55,12 +56,14 @@ export class NoteHelpers { return getNote(note.id, user); } - public static async removeReactFromNote(note: Note, user: ILocalUser): Promise { + public static async removeReactFromNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; await deleteReaction(user, note); return getNote(note.id, user); } - public static async reblogNote(note: Note, user: ILocalUser): Promise { + public static async reblogNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const existingRenote = await Notes.findOneBy({ userId: user.id, renoteId: note.id, @@ -75,7 +78,8 @@ export class NoteHelpers { return await createNote(user, data); } - public static async unreblogNote(note: Note, user: ILocalUser): Promise { + public static async unreblogNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return Notes.findBy({ userId: user.id, renoteId: note.id, @@ -85,7 +89,8 @@ export class NoteHelpers { .then(_ => getNote(note.id, user)); } - public static async bookmarkNote(note: Note, user: ILocalUser): Promise { + public static async bookmarkNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const bookmarked = await NoteFavorites.exist({ where: { noteId: note.id, @@ -105,7 +110,8 @@ export class NoteHelpers { return note; } - public static async unbookmarkNote(note: Note, user: ILocalUser): Promise { + public static async unbookmarkNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return NoteFavorites.findOneBy({ noteId: note.id, userId: user.id, @@ -114,7 +120,8 @@ export class NoteHelpers { .then(_ => note); } - public static async pinNote(note: Note, user: ILocalUser): Promise { + public static async pinNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const pinned = await UserNotePinings.exist({ where: { userId: user.id, @@ -129,7 +136,8 @@ export class NoteHelpers { return note; } - public static async unpinNote(note: Note, user: ILocalUser): Promise { + public static async unpinNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const pinned = await UserNotePinings.exist({ where: { userId: user.id, @@ -144,9 +152,10 @@ export class NoteHelpers { return note; } - public static async deleteNote(note: Note, user: ILocalUser, ctx: MastoContext): Promise { + public static async deleteNote(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; if (user.id !== note.userId) throw new MastoApiError(404); - const status = await NoteConverter.encode(note, user, ctx); + const status = await NoteConverter.encode(note, ctx); await deleteNote(user, note); status.content = undefined; return status; @@ -222,8 +231,9 @@ export class NoteHelpers { } } - public static async getNoteRebloggedBy(note: Note, user: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + public static async getNoteRebloggedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser | null; const query = PaginationHelpers.makePaginationQuery( Notes.createQueryBuilder("note"), sinceId, @@ -249,7 +259,8 @@ export class NoteHelpers { }); } - public static async getNoteDescendants(note: Note | string, user: ILocalUser | null, limit: number = 10, depth: number = 2): Promise { + public static async getNoteDescendants(note: Note | string, limit: number = 10, depth: number = 2, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser | null; const noteId = typeof note === "string" ? note : note.id; const query = makePaginationQuery(Notes.createQueryBuilder("note")) .andWhere( @@ -266,7 +277,8 @@ export class NoteHelpers { return query.getMany().then(p => p.reverse()); } - public static async getNoteAncestors(rootNote: Note, user: ILocalUser | null, limit: number = 10): Promise { + public static async getNoteAncestors(rootNote: Note, limit: number = 10, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser | null; const notes = new Array; for (let i = 0; i < limit; i++) { const currentNote = notes.at(-1) ?? rootNote; @@ -282,13 +294,14 @@ export class NoteHelpers { return notes.reverse(); } - public static async createNote(request: MastodonEntity.StatusCreationRequest, user: ILocalUser): Promise { + public static async createNote(request: MastodonEntity.StatusCreationRequest, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const files = request.media_ids && request.media_ids.length > 0 ? DriveFiles.findByIds(request.media_ids) : []; const reply = request.in_reply_to_id ? await getNote(request.in_reply_to_id, user) : undefined; - const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(user); + const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(ctx); const data = { createdAt: new Date(), @@ -304,13 +317,14 @@ export class NoteHelpers { reply: reply, cw: request.spoiler_text, visibility: visibility, - visibleUsers: Promise.resolve(visibility).then(p => p === 'specified' ? this.extractMentions(request.text ?? '', user) : undefined) + visibleUsers: Promise.resolve(visibility).then(p => p === 'specified' ? this.extractMentions(request.text ?? '', ctx) : undefined) } return createNote(user, await awaitAll(data)); } - public static async editNote(request: MastodonEntity.StatusEditRequest, note: Note, user: ILocalUser): Promise { + public static async editNote(request: MastodonEntity.StatusEditRequest, note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const files = request.media_ids && request.media_ids.length > 0 ? DriveFiles.findByIds(request.media_ids) : []; @@ -331,7 +345,8 @@ export class NoteHelpers { return editNote(user, note, await awaitAll(data)); } - public static async extractMentions(text: string, user: ILocalUser): Promise { + public static async extractMentions(text: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return extractMentionedUsers(user, mfm.parse(text)!); } @@ -397,13 +412,16 @@ export class NoteHelpers { return result; } - public static async getNoteOr404(id: string, user: ILocalUser | null): Promise { + public static async getNoteOr404(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser | null; return getNote(id, user).catch(_ => { throw new MastoApiError(404); }); } - public static getIdempotencyKey(headers: any, user: ILocalUser): string | null { + public static getIdempotencyKey(ctx: MastoContext): string | null { + const headers = ctx.headers; + const user = ctx.user as ILocalUser; if (headers["idempotency-key"] === undefined || headers["idempotency-key"] === null) return null; return `${user.id}-${Array.isArray(headers["idempotency-key"]) ? headers["idempotency-key"].at(-1)! : headers["idempotency-key"]}`; } diff --git a/packages/backend/src/server/api/mastodon/helpers/notification.ts b/packages/backend/src/server/api/mastodon/helpers/notification.ts index 8fa64b6a8..6e28f0125 100644 --- a/packages/backend/src/server/api/mastodon/helpers/notification.ts +++ b/packages/backend/src/server/api/mastodon/helpers/notification.ts @@ -4,11 +4,13 @@ import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { Notification } from "@/models/entities/notification.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; export class NotificationHelpers { - public static async getNotifications(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined): Promise> { + public static async getNotifications(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, types: string[] | undefined, excludeTypes: string[] | undefined, accountId: string | undefined, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser; let requestedTypes = types ? this.decodeTypes(types) : ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest']; @@ -35,26 +37,30 @@ export class NotificationHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getNotification(id: string, user: ILocalUser): Promise { + public static async getNotification(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return Notifications.findOneBy({ id: id, notifieeId: user.id }); } - public static async getNotificationOr404(id: string, user: ILocalUser): Promise { - return this.getNotification(id, user).then(p => { + public static async getNotificationOr404(id: string, ctx: MastoContext): Promise { + return this.getNotification(id, ctx).then(p => { if (p) return p; throw new MastoApiError(404); }); } - public static async dismissNotification(id: string, user: ILocalUser): Promise { - const result = await Notifications.update({ id: id, notifieeId: user.id }, { isRead: true }); + public static async dismissNotification(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + await Notifications.update({ id: id, notifieeId: user.id }, { isRead: true }); } - public static async clearAllNotifications(user: ILocalUser): Promise { + public static async clearAllNotifications(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; await Notifications.update({ notifieeId: user.id }, { isRead: true }); } - public static async markConversationAsRead(id: string, user: ILocalUser): Promise { + public static async markConversationAsRead(id: string, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const notesQuery = Notes.createQueryBuilder("note") .select("note.id") .andWhere("COALESCE(note.threadId, note.id) = :conversationId"); diff --git a/packages/backend/src/server/api/mastodon/helpers/poll.ts b/packages/backend/src/server/api/mastodon/helpers/poll.ts index 2b36dd4d3..6eaf859e4 100644 --- a/packages/backend/src/server/api/mastodon/helpers/poll.ts +++ b/packages/backend/src/server/api/mastodon/helpers/poll.ts @@ -17,7 +17,8 @@ import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { MastoContext } from "@/server/api/mastodon/index.js"; export class PollHelpers { - public static async getPoll(note: Note, user: ILocalUser | null, ctx: MastoContext): Promise { + public static async getPoll(note: Note, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser | null; if (!await Notes.isVisibleForMe(note, user?.id ?? null)) throw new Error('Cannot encode poll not visible for user'); @@ -25,15 +26,16 @@ export class PollHelpers { const host = Promise.resolve(noteUser).then(noteUser => noteUser.host ?? null); const noteEmoji = await host .then(async host => populateEmojis(note.emojis, host) - .then(noteEmoji => noteEmoji - .filter((e) => e.name.indexOf("@") === -1) - .map((e) => EmojiConverter.encode(e)))); + .then(noteEmoji => noteEmoji + .filter((e) => e.name.indexOf("@") === -1) + .map((e) => EmojiConverter.encode(e)))); return populatePoll(note, user?.id ?? null).then(p => PollConverter.encode(p, note.id, noteEmoji)); } - public static async voteInPoll(choices: number[], note: Note, user: ILocalUser, ctx: MastoContext): Promise { + public static async voteInPoll(choices: number[], note: Note, ctx: MastoContext): Promise { if (!note.hasPoll) throw new MastoApiError(404); + const user = ctx.user as ILocalUser; for (const choice of choices) { const createdAt = new Date(); @@ -123,6 +125,6 @@ export class PollHelpers { ); } } - return this.getPoll(note, user, ctx); + return this.getPoll(note, ctx); } } diff --git a/packages/backend/src/server/api/mastodon/helpers/search.ts b/packages/backend/src/server/api/mastodon/helpers/search.ts index 2c87161f5..e86f48ffd 100644 --- a/packages/backend/src/server/api/mastodon/helpers/search.ts +++ b/packages/backend/src/server/api/mastodon/helpers/search.ts @@ -24,15 +24,16 @@ import config from "@/config/index.js"; import { MastoContext } from "@/server/api/mastodon/index.js"; export class SearchHelpers { - public static async search(user: ILocalUser, q: string | undefined, type: string | undefined, resolve: boolean = false, following: boolean = false, accountId: string | undefined, excludeUnreviewed: boolean = false, maxId: string | undefined, minId: string | undefined, limit: number = 20, offset: number | undefined, ctx: MastoContext): Promise { + public static async search(q: string | undefined, type: string | undefined, resolve: boolean = false, following: boolean = false, accountId: string | undefined, excludeUnreviewed: boolean = false, maxId: string | undefined, minId: string | undefined, limit: number = 20, offset: number | undefined, ctx: MastoContext): Promise { if (q === undefined || q.trim().length === 0) throw new Error('Search query cannot be empty'); if (limit > 40) limit = 40; - const notes = type === 'statuses' || !type ? this.searchNotes(user, q, resolve, following, accountId, maxId, minId, limit, offset) : []; - const users = type === 'accounts' || !type ? this.searchUsers(user, q, resolve, following, maxId, minId, limit, offset) : []; + const user = ctx.user as ILocalUser; + const notes = type === 'statuses' || !type ? this.searchNotes(q, resolve, following, accountId, maxId, minId, limit, offset, ctx) : []; + const users = type === 'accounts' || !type ? this.searchUsers(q, resolve, following, maxId, minId, limit, offset, ctx) : []; const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : []; const result = { - statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, user, ctx)), + statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, ctx)), accounts: Promise.resolve(users).then(p => UserConverter.encodeMany(p, ctx)), hashtags: Promise.resolve(tags) }; @@ -40,7 +41,8 @@ export class SearchHelpers { return awaitAll(result); } - private static async searchUsers(user: ILocalUser, q: string, resolve: boolean, following: boolean, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined): Promise { + private static async searchUsers(q: string, resolve: boolean, following: boolean, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined, ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; if (resolve) { try { if (q.startsWith('https://') || q.startsWith('http://')) { @@ -113,8 +115,9 @@ export class SearchHelpers { return query.skip(offset ?? 0).take(limit).getMany().then(p => minId ? p.reverse() : p); } - private static async searchNotes(user: ILocalUser, q: string, resolve: boolean, following: boolean, accountId: string | undefined, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined): Promise { + private static async searchNotes(q: string, resolve: boolean, following: boolean, accountId: string | undefined, maxId: string | undefined, minId: string | undefined, limit: number, offset: number | undefined, ctx: MastoContext): Promise { if (accountId && following) throw new Error("The 'following' and 'accountId' parameters cannot be used simultaneously"); + const user = ctx.user as ILocalUser; if (resolve) { try { diff --git a/packages/backend/src/server/api/mastodon/helpers/timeline.ts b/packages/backend/src/server/api/mastodon/helpers/timeline.ts index 0eb842424..952fd9c40 100644 --- a/packages/backend/src/server/api/mastodon/helpers/timeline.ts +++ b/packages/backend/src/server/api/mastodon/helpers/timeline.ts @@ -22,8 +22,9 @@ import { generatePaginationData, LinkPaginationObject } from "@/server/api/masto import { MastoContext } from "@/server/api/mastodon/index.js"; export class TimelineHelpers { - public static async getHomeTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { + public static async getHomeTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const user = ctx.user as ILocalUser; const followingQuery = Followings.createQueryBuilder("following") .select("following.followeeId") @@ -56,8 +57,9 @@ export class TimelineHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getPublicTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise> { + public static async getPublicTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const user = ctx.user as ILocalUser; if (local && remote) { throw new Error("local and remote are mutually exclusive options"); @@ -99,8 +101,9 @@ export class TimelineHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getListTimeline(user: ILocalUser, list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { + public static async getListTimeline(list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const user = ctx.user as ILocalUser; if (user.id != list.userId) throw new Error("List is not owned by user"); const listQuery = UserListJoinings.createQueryBuilder("member") @@ -123,8 +126,9 @@ export class TimelineHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getTagTimeline(user: ILocalUser, tag: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, any: string[], all: string[], none: string[], onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise> { + public static async getTagTimeline(tag: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, any: string[], all: string[], none: string[], onlyMedia: boolean = false, local: boolean = false, remote: boolean = false, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const user = ctx.user as ILocalUser | null; if (tag.length < 1) throw new MastoApiError(400, "Tag cannot be empty"); @@ -164,8 +168,9 @@ export class TimelineHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getConversations(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { + public static async getConversations(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const user = ctx.user as ILocalUser; const sq = Notes.createQueryBuilder("note") .select("COALESCE(note.threadId, note.id)", "conversationId") .addSelect("note.id", "latest") @@ -207,7 +212,7 @@ export class TimelineHelpers { return { id: c.threadId ?? c.id, accounts: accounts.then(u => u.length > 0 ? u : UserConverter.encodeMany([user], ctx)), // failsafe to prevent apps from crashing case when all participant users have been deleted - last_status: NoteConverter.encode(c, user, ctx), + last_status: NoteConverter.encode(c, ctx), unread: unread } }); diff --git a/packages/backend/src/server/api/mastodon/helpers/user.ts b/packages/backend/src/server/api/mastodon/helpers/user.ts index dc60fae75..bee08bb6b 100644 --- a/packages/backend/src/server/api/mastodon/helpers/user.ts +++ b/packages/backend/src/server/api/mastodon/helpers/user.ts @@ -61,8 +61,9 @@ export type updateCredsData = { type RelationshipType = 'followers' | 'following'; export class UserHelpers { - public static async followUser(target: User, localUser: ILocalUser, reblogs: boolean, notify: boolean): Promise { + public static async followUser(target: User, reblogs: boolean, notify: boolean, ctx: MastoContext): Promise { //FIXME: implement reblogs & notify params + const localUser = ctx.user as ILocalUser; const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } }); const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } }); if (!following && !requested) @@ -71,7 +72,8 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async unfollowUser(target: User, localUser: ILocalUser): Promise { + public static async unfollowUser(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const following = await Followings.exist({ where: { followerId: localUser.id, followeeId: target.id } }); const requested = await FollowRequests.exist({ where: { followerId: localUser.id, followeeId: target.id } }); if (following) @@ -82,7 +84,8 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async blockUser(target: User, localUser: ILocalUser): Promise { + public static async blockUser(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } }); if (!blocked) await createBlocking(localUser, target); @@ -90,7 +93,8 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async unblockUser(target: User, localUser: ILocalUser): Promise { + public static async unblockUser(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const blocked = await Blockings.exist({ where: { blockerId: localUser.id, blockeeId: target.id } }); if (blocked) await deleteBlocking(localUser, target); @@ -98,8 +102,9 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async muteUser(target: User, localUser: ILocalUser, notifications: boolean = true, duration: number = 0): Promise { + public static async muteUser(target: User, notifications: boolean = true, duration: number = 0, ctx: MastoContext): Promise { //FIXME: respect notifications parameter + const localUser = ctx.user as ILocalUser; const muted = await Mutings.exist({ where: { muterId: localUser.id, muteeId: target.id } }); if (!muted) { await Mutings.insert({ @@ -121,7 +126,8 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async unmuteUser(target: User, localUser: ILocalUser): Promise { + public static async unmuteUser(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const muting = await Mutings.findOneBy({ muterId: localUser.id, muteeId: target.id }); if (muting) { await Mutings.delete({ @@ -134,21 +140,27 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async acceptFollowRequest(target: User, localUser: ILocalUser): Promise { + public static async acceptFollowRequest(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } }); if (pending) await acceptFollowRequest(localUser, target); return this.getUserRelationshipTo(target.id, localUser.id); } - public static async rejectFollowRequest(target: User, localUser: ILocalUser): Promise { + public static async rejectFollowRequest(target: User, ctx: MastoContext): Promise { + const localUser = ctx.user as ILocalUser; const pending = await FollowRequests.exist({ where: { followerId: target.id, followeeId: localUser.id } }); if (pending) await rejectFollowRequest(localUser, target); return this.getUserRelationshipTo(target.id, localUser.id); } - public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined, ctx: MastoContext): Promise { + public static async updateCredentials(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; + const files = (ctx.request as any).files as Files | undefined; + const formData = (ctx.request as any).body as updateCredsData; + const updates: Partial = {}; const profileUpdates: Partial = {}; @@ -156,12 +168,12 @@ export class UserHelpers { const header = toSingleLast(files?.header); if (avatar) { - const file = await MediaHelpers.uploadMediaBasic(user, avatar); + const file = await MediaHelpers.uploadMediaBasic(avatar, ctx); updates.avatarId = file.id; } if (header) { - const file = await MediaHelpers.uploadMediaBasic(user, header); + const file = await MediaHelpers.uploadMediaBasic(header, ctx); updates.bannerId = file.id; } @@ -184,13 +196,14 @@ export class UserHelpers { if (Object.keys(updates).length > 0) await Users.update(user.id, updates); if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates); - return this.verifyCredentials(user, ctx); + return this.verifyCredentials(ctx); } - public static async verifyCredentials(user: ILocalUser, ctx: MastoContext): Promise { + public static async verifyCredentials(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; const acct = UserConverter.encode(user, ctx); const profile = UserProfiles.findOneByOrFail({ userId: user.id }); - const privacy = this.getDefaultNoteVisibility(user); + const privacy = this.getDefaultNoteVisibility(ctx); const fields = profile.then(profile => profile.fields.map(field => { return { name: field.name, @@ -225,9 +238,10 @@ export class UserHelpers { }); } - public static async getUserMutes(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { + public static async getUserMutes(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser; const query = PaginationHelpers.makePaginationQuery( Mutings.createQueryBuilder("muting"), sinceId, @@ -260,9 +274,10 @@ export class UserHelpers { }); } - public static async getUserBlocks(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + public static async getUserBlocks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser; const query = PaginationHelpers.makePaginationQuery( Blockings.createQueryBuilder("blocking"), sinceId, @@ -286,9 +301,10 @@ export class UserHelpers { }); } - public static async getUserFollowRequests(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + public static async getUserFollowRequests(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const user = ctx.user as ILocalUser; const query = PaginationHelpers.makePaginationQuery( FollowRequests.createQueryBuilder("request"), sinceId, @@ -312,12 +328,13 @@ export class UserHelpers { }); } - public static async getUserStatuses(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, excludeReplies: boolean = false, excludeReblogs: boolean = false, pinned: boolean = false, tagged: string | undefined): Promise> { + public static async getUserStatuses(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, excludeReplies: boolean = false, excludeReblogs: boolean = false, pinned: boolean = false, tagged: string | undefined, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const localUser = ctx.user as ILocalUser | null; if (tagged !== undefined) { //FIXME respect tagged - return {data: []}; + return { data: [] }; } const query = PaginationHelpers.makePaginationQuery( @@ -373,9 +390,10 @@ export class UserHelpers { return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); } - public static async getUserBookmarks(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { + public static async getUserBookmarks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const localUser = ctx.user as ILocalUser; const query = PaginationHelpers.makePaginationQuery( NoteFavorites.createQueryBuilder("favorite"), sinceId, @@ -396,9 +414,10 @@ export class UserHelpers { }); } - public static async getUserFavorites(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { + public static async getUserFavorites(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; + const localUser = ctx.user as ILocalUser; const query = PaginationHelpers.makePaginationQuery( NoteReactions.createQueryBuilder("reaction"), sinceId, @@ -419,9 +438,10 @@ export class UserHelpers { }); } - private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { + private static async getUserRelationships(type: RelationshipType, user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; + const localUser = ctx.user as ILocalUser | null; const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); if (profile.ffVisibility === "private") { if (!localUser || user.id !== localUser.id) return { data: [] }; @@ -463,12 +483,12 @@ export class UserHelpers { }); } - public static async getUserFollowers(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { - return this.getUserRelationships('followers', user, localUser, maxId, sinceId, minId, limit); + public static async getUserFollowers(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { + return this.getUserRelationships('followers', user, maxId, sinceId, minId, limit, ctx); } - public static async getUserFollowing(user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> { - return this.getUserRelationships('following', user, localUser, maxId, sinceId, minId, limit); + public static async getUserFollowing(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { + return this.getUserRelationships('following', user, maxId, sinceId, minId, limit, ctx); } public static async getUserRelationhipToMany(targetIds: string[], localUserId: string): Promise { @@ -528,7 +548,8 @@ export class UserHelpers { }; } - public static async getDefaultNoteVisibility(user: ILocalUser): Promise { + public static async getDefaultNoteVisibility(ctx: MastoContext): Promise { + const user = ctx.user as ILocalUser; return RegistryItems.findOneBy({ domain: IsNull(), userId: user.id,