From 79c3e5698909fbe081adfe8969ccb1cd81286b53 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sat, 7 Oct 2023 20:29:58 +0200 Subject: [PATCH] [mastodon-client] Fully move cache into ctx --- .../server/api/mastodon/converters/note.ts | 19 +++---- .../api/mastodon/converters/notification.ts | 17 ++++--- .../server/api/mastodon/converters/user.ts | 10 ++-- .../server/api/mastodon/endpoints/account.ts | 49 +++++++++---------- .../src/server/api/mastodon/endpoints/list.ts | 3 +- .../src/server/api/mastodon/endpoints/misc.ts | 6 +-- .../api/mastodon/endpoints/notifications.ts | 5 +- .../server/api/mastodon/endpoints/search.ts | 3 +- .../server/api/mastodon/endpoints/status.ts | 43 ++++++++-------- .../server/api/mastodon/endpoints/timeline.ts | 10 ++-- .../src/server/api/mastodon/helpers/misc.ts | 16 +++--- .../src/server/api/mastodon/helpers/note.ts | 12 ++--- .../src/server/api/mastodon/helpers/poll.ts | 11 +++-- .../src/server/api/mastodon/helpers/search.ts | 8 +-- .../server/api/mastodon/helpers/timeline.ts | 12 ++--- .../src/server/api/mastodon/helpers/user.ts | 20 ++++---- .../api/mastodon/middleware/pagination.ts | 1 - 17 files changed, 123 insertions(+), 122 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters/note.ts b/packages/backend/src/server/api/mastodon/converters/note.ts index b2703cf1d..0868234e2 100644 --- a/packages/backend/src/server/api/mastodon/converters/note.ts +++ b/packages/backend/src/server/api/mastodon/converters/note.ts @@ -15,13 +15,14 @@ import { PollConverter } from "@/server/api/mastodon/converters/poll.js"; import { populatePoll } from "@/models/repositories/note.js"; import { FileConverter } from "@/server/api/mastodon/converters/file.js"; import { awaitAll } from "@/prelude/await-all.js"; -import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js"; +import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { IsNull } from "typeorm"; 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, cache: AccountCache = UserHelpers.getFreshAccountCache(), recurse: boolean = true): Promise { - const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, cache); + public static async encode(note: Note, user: ILocalUser | null, ctx: MastoContext, recurse: boolean = true): Promise { + const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx); if (!await Notes.isVisibleForMe(note, user?.id ?? null)) throw new Error('Cannot encode note not visible for user'); @@ -78,7 +79,7 @@ export class NoteConverter { const files = DriveFiles.packMany(note.fileIds); const mentions = Promise.all(note.mentions.map(p => - UserHelpers.getUserCached(p, cache) + UserHelpers.getUserCached(p, ctx) .then(u => MentionConverter.encode(u, JSON.parse(note.mentionedRemoteUsers))) .catch(() => null))) .then(p => p.filter(m => m)) as Promise; @@ -105,10 +106,10 @@ export class NoteConverter { id: note.id, uri: note.uri ? note.uri : `https://${config.host}/notes/${note.id}`, url: note.uri ? note.uri : `https://${config.host}/notes/${note.id}`, - account: Promise.resolve(noteUser).then(p => UserConverter.encode(p, cache)), + 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, cache, false) : null), + reblog: Promise.resolve(renote).then(renote => recurse && renote && note.text === null ? this.encode(renote, user, 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(), @@ -134,13 +135,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, cache, false) : null), + quote: Promise.resolve(renote).then(renote => recurse && renote && note.text !== null ? this.encode(renote, user, ctx, false) : null), edited_at: note.updatedAt?.toISOString() }); } - public static async encodeMany(notes: Note[], user: ILocalUser | null, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { - const encoded = notes.map(n => this.encode(n, user, cache)); + public static async encodeMany(notes: Note[], user: ILocalUser | null, ctx: MastoContext): Promise { + const encoded = notes.map(n => this.encode(n, user, 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 0dc417581..26fc3ed92 100644 --- a/packages/backend/src/server/api/mastodon/converters/notification.ts +++ b/packages/backend/src/server/api/mastodon/converters/notification.ts @@ -2,20 +2,21 @@ import { ILocalUser } from "@/models/entities/user.js"; import { Notification } from "@/models/entities/notification.js"; import { notificationTypes } from "@/types.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js"; -import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js"; +import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { awaitAll } from "@/prelude/await-all.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { getNote } from "@/server/api/common/getters.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; type NotificationType = typeof notificationTypes[number]; export class NotificationConverter { - public static async encode(notification: Notification, localUser: ILocalUser, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + public static async encode(notification: Notification, localUser: ILocalUser, ctx: MastoContext): Promise { if (notification.notifieeId !== localUser.id) throw new Error('User is not recipient of notification'); const account = notification.notifierId - ? UserHelpers.getUserCached(notification.notifierId, cache).then(p => UserConverter.encode(p)) - : UserConverter.encode(localUser); + ? UserHelpers.getUserCached(notification.notifierId, ctx).then(p => UserConverter.encode(p, ctx)) + : UserConverter.encode(localUser, ctx); let result = { id: notification.id, @@ -27,8 +28,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, cache)) - : NoteConverter.encode(notification.note, localUser, cache); + ? getNote(notification.note.renoteId!, localUser).then(note => NoteConverter.encode(note, localUser, ctx)) + : NoteConverter.encode(notification.note, localUser, ctx); result = Object.assign(result, { status: encodedNote, }); @@ -44,8 +45,8 @@ export class NotificationConverter { return awaitAll(result); } - public static async encodeMany(notifications: Notification[], localUser: ILocalUser, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { - const encoded = notifications.map(u => this.encode(u, localUser, cache)); + public static async encodeMany(notifications: Notification[], localUser: ILocalUser, ctx: MastoContext): Promise { + const encoded = notifications.map(u => this.encode(u, localUser, ctx)); return Promise.all(encoded) .then(p => p.filter(n => n !== null) as MastodonEntity.Notification[]); } diff --git a/packages/backend/src/server/api/mastodon/converters/user.ts b/packages/backend/src/server/api/mastodon/converters/user.ts index 4e951f6cd..fb06ec327 100644 --- a/packages/backend/src/server/api/mastodon/converters/user.ts +++ b/packages/backend/src/server/api/mastodon/converters/user.ts @@ -6,8 +6,9 @@ import { populateEmojis } from "@/misc/populate-emojis.js"; import { escapeMFM } from "@/server/api/mastodon/converters/mfm.js"; import mfm from "mfm-js"; import { awaitAll } from "@/prelude/await-all.js"; -import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js"; +import { AccountCache } from "@/server/api/mastodon/helpers/user.js"; import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; type Field = { name: string; @@ -16,7 +17,8 @@ type Field = { }; export class UserConverter { - public static async encode(u: User, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + public static async encode(u: User, ctx: MastoContext): Promise { + const cache = ctx.cache as AccountCache; return cache.locks.acquire(u.id, async () => { const cacheHit = cache.accounts.find(p => p.id == u.id); if (cacheHit) return cacheHit; @@ -90,8 +92,8 @@ export class UserConverter { }); } - public static async encodeMany(users: User[], cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { - const encoded = users.map(u => this.encode(u, cache)); + public static async encodeMany(users: User[], ctx: MastoContext): Promise { + const encoded = users.map(u => this.encode(u, ctx)); return Promise.all(encoded); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index f312ba371..59f6f01e5 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -5,7 +5,6 @@ import { convertAccountId, convertListId, convertRelationshipId, convertStatusId 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 { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { ListHelpers } from "@/server/api/mastodon/helpers/list.js"; import { Files } from "formidable"; import { auth } from "@/server/api/mastodon/middleware/auth.js"; @@ -14,7 +13,7 @@ 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); + const acct = await UserHelpers.verifyCredentials(ctx.user, ctx); ctx.body = convertAccountId(acct); } ); @@ -22,7 +21,7 @@ export function setupEndpointsAccount(router: Router): void { 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); + const acct = await UserHelpers.updateCredentials(ctx.user, (ctx.request as any).body as any, files, ctx); ctx.body = convertAccountId(acct) } ); @@ -30,7 +29,7 @@ export function setupEndpointsAccount(router: Router): void { async (ctx) => { const args = normalizeUrlQuery(ctx.query); const user = await UserHelpers.getUserFromAcct(args.acct); - const account = await UserConverter.encode(user); + const account = await UserConverter.encode(user, ctx); ctx.body = convertAccountId(account); } ); @@ -47,7 +46,7 @@ export function setupEndpointsAccount(router: Router): void { auth(false), async (ctx) => { const userId = convertId(ctx.params.id, IdType.IceshrimpId); - const account = await UserConverter.encode(await UserHelpers.getUserOr404(userId)); + const account = await UserConverter.encode(await UserHelpers.getUserOr404(userId), ctx); ctx.body = convertAccountId(account); } ); @@ -56,10 +55,10 @@ export function setupEndpointsAccount(router: Router): void { auth(false, ["read:statuses"]), async (ctx) => { const userId = convertId(ctx.params.id, IdType.IceshrimpId); - const query = await UserHelpers.getUserCachedOr404(userId, ctx.cache); + 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.cache); + const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -76,10 +75,10 @@ export function setupEndpointsAccount(router: Router): void { auth(false), async (ctx) => { const userId = convertId(ctx.params.id, IdType.IceshrimpId); - const query = await UserHelpers.getUserCachedOr404(userId, ctx.cache); + 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 followers = await UserConverter.encodeMany(res.data, ctx.cache); + const followers = await UserConverter.encodeMany(res.data, ctx); ctx.body = followers.map((account) => convertAccountId(account)); ctx.pagination = res.pagination; @@ -90,10 +89,10 @@ export function setupEndpointsAccount(router: Router): void { auth(false), async (ctx) => { const userId = convertId(ctx.params.id, IdType.IceshrimpId); - const query = await UserHelpers.getUserCachedOr404(userId, ctx.cache); + 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 following = await UserConverter.encodeMany(res.data, ctx.cache); + const following = await UserConverter.encodeMany(res.data, ctx); ctx.body = following.map((account) => convertAccountId(account)); ctx.pagination = res.pagination; @@ -103,7 +102,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/lists", auth(true, ["read:lists"]), async (ctx) => { - const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const member = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const results = await ListHelpers.getListsByMember(ctx.user, member); ctx.body = results.map(p => convertListId(p)); }, @@ -112,7 +111,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/follow", auth(true, ["write:follows"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + 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); ctx.body = convertRelationshipId(result); @@ -122,7 +121,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/unfollow", auth(true, ["write:follows"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.unfollowUser(target, ctx.user); ctx.body = convertRelationshipId(result); }, @@ -131,7 +130,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/block", auth(true, ["write:blocks"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.blockUser(target, ctx.user); ctx.body = convertRelationshipId(result); }, @@ -140,7 +139,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/unblock", auth(true, ["write:blocks"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.unblockUser(target, ctx.user); ctx.body = convertRelationshipId(result) }, @@ -151,7 +150,7 @@ export function setupEndpointsAccount(router: Router): void { async (ctx) => { //FIXME: parse form data const args = normalizeUrlQuery(argsToBools(limitToInt(ctx.query, ['duration']), ['notifications'])); - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.muteUser(target, ctx.user, args.notifications, args.duration); ctx.body = convertRelationshipId(result) }, @@ -160,7 +159,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/accounts/:id/unmute", auth(true, ["write:mutes"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.unmuteUser(target, ctx.user); ctx.body = convertRelationshipId(result) }, @@ -180,7 +179,7 @@ export function setupEndpointsAccount(router: Router): void { 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.cache); + const bookmarks = await NoteConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = bookmarks.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; } @@ -190,7 +189,7 @@ export function setupEndpointsAccount(router: Router): void { 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.cache); + const favorites = await NoteConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = favorites.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; } @@ -199,7 +198,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.cache); + const res = await UserHelpers.getUserMutes(ctx.user, args.max_id, args.since_id, args.min_id, args.limit, ctx); ctx.body = res.data.map(m => convertAccountId(m)); ctx.pagination = res.pagination; } @@ -209,7 +208,7 @@ export function setupEndpointsAccount(router: Router): void { 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 blocks = await UserConverter.encodeMany(res.data, ctx.cache); + const blocks = await UserConverter.encodeMany(res.data, ctx); ctx.body = blocks.map(b => convertAccountId(b)); ctx.pagination = res.pagination; } @@ -219,7 +218,7 @@ export function setupEndpointsAccount(router: Router): void { 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 requests = await UserConverter.encodeMany(res.data, ctx.cache); + const requests = await UserConverter.encodeMany(res.data, ctx); ctx.body = requests.map(b => convertAccountId(b)); ctx.pagination = res.pagination; } @@ -228,7 +227,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/follow_requests/:id/authorize", auth(true, ["write:follows"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.acceptFollowRequest(target, ctx.user); ctx.body = convertRelationshipId(result); }, @@ -237,7 +236,7 @@ export function setupEndpointsAccount(router: Router): void { "/v1/follow_requests/:id/reject", auth(true, ["write:follows"]), async (ctx) => { - const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId)); + const target = await UserHelpers.getUserCachedOr404(convertId(ctx.params.id, IdType.IceshrimpId), ctx); const result = await UserHelpers.rejectFollowRequest(target, ctx.user); 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 f3b40562d..fcc42e38a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/list.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/list.ts @@ -4,7 +4,6 @@ import { convertId, IdType } from "../../index.js"; import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js"; import { ListHelpers } from "@/server/api/mastodon/helpers/list.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js"; -import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { UserLists } from "@/models/index.js"; import { getUser } from "@/server/api/common/getters.js"; import { toArray } from "@/prelude/array.js"; @@ -72,7 +71,7 @@ export function setupEndpointsList(router: Router): void { 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 accounts = await UserConverter.encodeMany(res.data); + const accounts = await UserConverter.encodeMany(res.data, ctx); ctx.body = accounts.map(account => convertAccountId(account)); ctx.pagination = res.pagination; diff --git a/packages/backend/src/server/api/mastodon/endpoints/misc.ts b/packages/backend/src/server/api/mastodon/endpoints/misc.ts index aa6b2b0fa..0b3808865 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/misc.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/misc.ts @@ -16,7 +16,7 @@ export function setupEndpointsMisc(router: Router): void { router.get("/v1/instance", async (ctx) => { - ctx.body = await MiscHelpers.getInstance(); + ctx.body = await MiscHelpers.getInstance(ctx); } ); @@ -54,7 +54,7 @@ export function setupEndpointsMisc(router: Router): void { router.get("/v1/trends/statuses", async (ctx) => { const args = limitToInt(ctx.query); - ctx.body = await MiscHelpers.getTrendingStatuses(args.limit, args.offset) + ctx.body = await MiscHelpers.getTrendingStatuses(args.limit, args.offset, ctx) .then(p => p.map(x => convertStatusIds(x))); } ); @@ -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.body = await MiscHelpers.getFollowSuggestions(ctx.user, 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 3ff5ca04a..209c02eef 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -2,7 +2,6 @@ import Router from "@koa/router"; import { convertId, IdType } from "../../index.js"; import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js"; import { convertNotificationIds } from "../converters.js"; -import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { NotificationHelpers } from "@/server/api/mastodon/helpers/notification.js"; import { NotificationConverter } from "@/server/api/mastodon/converters/notification.js"; import { auth } from "@/server/api/mastodon/middleware/auth.js"; @@ -13,7 +12,7 @@ export function setupEndpointsNotifications(router: Router): void { 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.cache); + const data = await NotificationConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = data.map(n => convertNotificationIds(n)); ctx.pagination = res.pagination; @@ -24,7 +23,7 @@ export function setupEndpointsNotifications(router: Router): void { 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.body = convertNotificationIds(await NotificationConverter.encode(notification, ctx.user, ctx)); } ); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index a9df663eb..8e4cece15 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -1,7 +1,6 @@ import Router from "@koa/router"; import { argsToBools, convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "./timeline.js"; import { convertSearchIds } from "../converters.js"; -import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { SearchHelpers } from "@/server/api/mastodon/helpers/search.js"; import { auth } from "@/server/api/mastodon/middleware/auth.js"; @@ -10,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.cache); + 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); 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 8cc8927e4..cf456d37d 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -10,7 +10,6 @@ import { import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { NoteHelpers } from "@/server/api/mastodon/helpers/note.js"; import { convertPaginationArgsIds, limitToInt, normalizeUrlQuery } from "@/server/api/mastodon/endpoints/timeline.js"; -import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js"; import { PollHelpers } from "@/server/api/mastodon/helpers/poll.js"; import { toArray } from "@/prelude/array.js"; @@ -33,7 +32,7 @@ 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)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); if (key !== null) NoteHelpers.postIdempotencyCache.set(key, { status: ctx.body }); @@ -46,7 +45,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(noteId, ctx.user); let request = NoteHelpers.normalizeEditOptions(ctx.request.body); ctx.body = await NoteHelpers.editNote(request, note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); } ); @@ -56,7 +55,7 @@ export function setupEndpointsStatus(router: Router): void { const noteId = convertId(ctx.params.id, IdType.IceshrimpId); const note = await NoteHelpers.getNoteOr404(noteId, ctx.user); - const status = await NoteConverter.encode(note, ctx.user); + const status = await NoteConverter.encode(note, ctx.user, ctx); ctx.body = convertStatusIds(status); } ); @@ -65,7 +64,7 @@ export function setupEndpointsStatus(router: Router): void { 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.body = await NoteHelpers.deleteNote(note, ctx.user, ctx) .then(p => convertStatusIds(p)); } ); @@ -77,10 +76,10 @@ export function setupEndpointsStatus(router: Router): void { 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.cache)) + .then(n => NoteConverter.encodeMany(n, ctx.user, 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.cache)) + .then(n => NoteConverter.encodeMany(n, ctx.user, ctx)) .then(n => n.map(s => convertStatusIds(s))); ctx.body = { @@ -95,7 +94,7 @@ export function setupEndpointsStatus(router: Router): void { async (ctx) => { const id = convertId(ctx.params.id, IdType.IceshrimpId); const note = await NoteHelpers.getNoteOr404(id, ctx.user); - const res = await NoteHelpers.getNoteEditHistory(note); + const res = await NoteHelpers.getNoteEditHistory(note, ctx); ctx.body = res.map(p => convertStatusEditIds(p)); } ); @@ -117,7 +116,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); 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 users = await UserConverter.encodeMany(res.data, ctx.cache); + const users = await UserConverter.encodeMany(res.data, ctx); ctx.body = users.map(m => convertAccountId(m)); ctx.pagination = res.pagination; } @@ -130,7 +129,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); 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.cache); + const users = await UserConverter.encodeMany(res.data, ctx); ctx.body = users.map(m => convertAccountId(m)); ctx.pagination = res.pagination; } @@ -144,7 +143,7 @@ export function setupEndpointsStatus(router: Router): void { const reaction = await NoteHelpers.getDefaultReaction(); ctx.body = await NoteHelpers.reactToNote(note, ctx.user, reaction) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); } ); @@ -156,7 +155,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -169,7 +168,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.reblogNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -182,7 +181,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.unreblogNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -195,7 +194,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.bookmarkNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -208,7 +207,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.unbookmarkNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -221,7 +220,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.pinNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -234,7 +233,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.unpinNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -247,7 +246,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.reactToNote(note, ctx.user, ctx.params.name) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -260,7 +259,7 @@ export function setupEndpointsStatus(router: Router): void { const note = await NoteHelpers.getNoteOr404(id, ctx.user); ctx.body = await NoteHelpers.removeReactFromNote(note, ctx.user) - .then(p => NoteConverter.encode(p, ctx.user)) + .then(p => NoteConverter.encode(p, ctx.user, ctx)) .then(p => convertStatusIds(p)); }, ); @@ -269,7 +268,7 @@ export function setupEndpointsStatus(router: Router): void { 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.cache); + const data = await PollHelpers.getPoll(note, ctx.user, ctx); ctx.body = convertPollId(data); }); router.post<{ Params: { id: string } }>( @@ -283,7 +282,7 @@ export function setupEndpointsStatus(router: Router): void { const choices = toArray(body.choices ?? []).map(p => parseInt(p)); 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.cache); + const data = await PollHelpers.voteInPoll(choices, note, ctx.user, 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 d4f72c66d..db15ed6ca 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -68,7 +68,7 @@ export function setupEndpointsTimeline(router: Router): void { 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.cache); + const tl = await NoteConverter.encodeMany(res.data, ctx.user, 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.cache); + const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -91,7 +91,7 @@ export function setupEndpointsTimeline(router: Router): void { 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.cache); + const tl = await NoteConverter.encodeMany(res.data, ctx.user, ctx); ctx.body = tl.map(s => convertStatusIds(s)); ctx.pagination = res.pagination; @@ -106,7 +106,7 @@ export function setupEndpointsTimeline(router: Router): void { 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.cache); + const tl = await NoteConverter.encodeMany(res.data, ctx.user, 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); + const res = await TimelineHelpers.getConversations(ctx.user, 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/misc.ts b/packages/backend/src/server/api/mastodon/helpers/misc.ts index ca5910c35..59186c711 100644 --- a/packages/backend/src/server/api/mastodon/helpers/misc.ts +++ b/packages/backend/src/server/api/mastodon/helpers/misc.ts @@ -19,9 +19,10 @@ import { EmojiConverter } from "@/server/api/mastodon/converters/emoji.js"; import { populateEmojis } from "@/misc/populate-emojis.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { VisibilityConverter } from "@/server/api/mastodon/converters/visibility.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; export class MiscHelpers { - public static async getInstance(): Promise { + public static async getInstance(ctx: MastoContext): Promise { const userCount = Users.count({ where: { host: IsNull() } }); const noteCount = Notes.count({ where: { userHost: IsNull() } }); const instanceCount = Instances.count({ cache: 3600000 }); @@ -34,7 +35,7 @@ export class MiscHelpers { }, order: { id: "ASC" }, }) - .then(p => p ? UserConverter.encode(p) : null) + .then(p => p ? UserConverter.encode(p, ctx) : null) .then(p => p ? convertAccountId(p) : null); const meta = await fetchMeta(true); @@ -133,8 +134,7 @@ export class MiscHelpers { } } - public static async getFollowSuggestions(user: ILocalUser, limit: number): Promise { - const cache = UserHelpers.getFreshAccountCache(); + public static async getFollowSuggestions(user: ILocalUser, limit: number, ctx: MastoContext): Promise { const results: Promise[] = []; const pinned = fetchMeta().then(meta => Promise.all( @@ -147,7 +147,7 @@ export class MiscHelpers { })) ) .then(p => p.filter(x => !!x) as User[]) - .then(p => UserConverter.encodeMany(p, cache)) + .then(p => UserConverter.encodeMany(p, ctx)) .then(p => p.map(x => { return { source: "staff", account: x } as MastodonEntity.SuggestedAccount })) @@ -167,7 +167,7 @@ export class MiscHelpers { const global = query .take(limit) .getMany() - .then(p => UserConverter.encodeMany(p, cache)) + .then(p => UserConverter.encodeMany(p, ctx)) .then(p => p.map(x => { return { source: "global", account: x } as MastodonEntity.SuggestedAccount })); @@ -206,7 +206,7 @@ export class MiscHelpers { ); } - public static async getTrendingStatuses(limit: number = 20, offset: number = 0): Promise { + public static async getTrendingStatuses(limit: number = 20, offset: number = 0, ctx: MastoContext): Promise { if (limit > 40) limit = 40; const query = Notes.createQueryBuilder("note") .addSelect("note.score") @@ -220,7 +220,7 @@ export class MiscHelpers { .skip(offset) .take(limit) .getMany() - .then(result => NoteConverter.encodeMany(result, null)); + .then(result => NoteConverter.encodeMany(result, null, ctx)); } public static async getTrendingHashtags(limit: number = 10, offset: number = 0): Promise { diff --git a/packages/backend/src/server/api/mastodon/helpers/note.ts b/packages/backend/src/server/api/mastodon/helpers/note.ts index 640978f25..e756cad62 100644 --- a/packages/backend/src/server/api/mastodon/helpers/note.ts +++ b/packages/backend/src/server/api/mastodon/helpers/note.ts @@ -30,6 +30,7 @@ import { Cache } from "@/misc/cache.js"; import AsyncLock from "async-lock"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IsNull } from "typeorm"; +import { MastoContext } from "@/server/api/mastodon/index.js"; export class NoteHelpers { public static postIdempotencyCache = new Cache<{ status?: MastodonEntity.Status }>('postIdempotencyCache', 60 * 60); @@ -143,9 +144,9 @@ export class NoteHelpers { return note; } - public static async deleteNote(note: Note, user: ILocalUser): Promise { + public static async deleteNote(note: Note, user: ILocalUser, ctx: MastoContext): Promise { if (user.id !== note.userId) throw new MastoApiError(404); - const status = await NoteConverter.encode(note, user); + const status = await NoteConverter.encode(note, user, ctx); await deleteNote(user, note); status.content = undefined; return status; @@ -175,10 +176,9 @@ export class NoteHelpers { }); } - public static async getNoteEditHistory(note: Note): Promise { - const cache = UserHelpers.getFreshAccountCache(); - const account = Promise.resolve(note.user ?? await UserHelpers.getUserCached(note.userId, cache)) - .then(p => UserConverter.encode(p, cache)); + public static async getNoteEditHistory(note: Note, ctx: MastoContext): Promise { + const account = Promise.resolve(note.user ?? await UserHelpers.getUserCached(note.userId, ctx)) + .then(p => UserConverter.encode(p, ctx)); const edits = await NoteEdits.find({ where: { noteId: note.id }, order: { id: "ASC" } }); const history: Promise[] = []; diff --git a/packages/backend/src/server/api/mastodon/helpers/poll.ts b/packages/backend/src/server/api/mastodon/helpers/poll.ts index 23616146f..2b36dd4d3 100644 --- a/packages/backend/src/server/api/mastodon/helpers/poll.ts +++ b/packages/backend/src/server/api/mastodon/helpers/poll.ts @@ -13,14 +13,15 @@ import { Not } from "typeorm"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { populateEmojis } from "@/misc/populate-emojis.js"; import { EmojiConverter } from "@/server/api/mastodon/converters/emoji.js"; -import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js"; +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, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + public static async getPoll(note: Note, user: ILocalUser | null, ctx: MastoContext): Promise { if (!await Notes.isVisibleForMe(note, user?.id ?? null)) throw new Error('Cannot encode poll not visible for user'); - const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, cache); + const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, ctx); const host = Promise.resolve(noteUser).then(noteUser => noteUser.host ?? null); const noteEmoji = await host .then(async host => populateEmojis(note.emojis, host) @@ -31,7 +32,7 @@ export class PollHelpers { return populatePoll(note, user?.id ?? null).then(p => PollConverter.encode(p, note.id, noteEmoji)); } - public static async voteInPoll(choices: number[], note: Note, user: ILocalUser, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + public static async voteInPoll(choices: number[], note: Note, user: ILocalUser, ctx: MastoContext): Promise { if (!note.hasPoll) throw new MastoApiError(404); for (const choice of choices) { @@ -122,6 +123,6 @@ export class PollHelpers { ); } } - return this.getPoll(note, user, cache); + return this.getPoll(note, user, ctx); } } diff --git a/packages/backend/src/server/api/mastodon/helpers/search.ts b/packages/backend/src/server/api/mastodon/helpers/search.ts index f8136fb35..2c87161f5 100644 --- a/packages/backend/src/server/api/mastodon/helpers/search.ts +++ b/packages/backend/src/server/api/mastodon/helpers/search.ts @@ -11,7 +11,6 @@ import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { ILocalUser, User } from "@/models/entities/user.js"; import { Brackets, In, IsNull } from "typeorm"; import { awaitAll } from "@/prelude/await-all.js"; -import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import Resolver from "@/remote/activitypub/resolver.js"; import { getApId, isActor, isPost } from "@/remote/activitypub/type.js"; @@ -22,9 +21,10 @@ import { resolveUser } from "@/remote/resolve-user.js"; import { createNote } from "@/remote/activitypub/models/note.js"; import { getUser } from "@/server/api/common/getters.js"; 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, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + 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 { 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) : []; @@ -32,8 +32,8 @@ export class SearchHelpers { const tags = type === 'hashtags' || !type ? this.searchTags(q, excludeUnreviewed, limit, offset) : []; const result = { - statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, user, cache)), - accounts: Promise.resolve(users).then(p => UserConverter.encodeMany(p, cache)), + statuses: Promise.resolve(notes).then(p => NoteConverter.encodeMany(p, user, ctx)), + accounts: Promise.resolve(users).then(p => UserConverter.encodeMany(p, ctx)), hashtags: Promise.resolve(tags) }; diff --git a/packages/backend/src/server/api/mastodon/helpers/timeline.ts b/packages/backend/src/server/api/mastodon/helpers/timeline.ts index 69aa55c4a..0eb842424 100644 --- a/packages/backend/src/server/api/mastodon/helpers/timeline.ts +++ b/packages/backend/src/server/api/mastodon/helpers/timeline.ts @@ -19,6 +19,7 @@ import { awaitAll } from "@/prelude/await-all.js"; import { unique } from "@/prelude/array.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; +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> { @@ -163,7 +164,7 @@ 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): Promise> { + public static async getConversations(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise> { if (limit > 40) limit = 40; const sq = Notes.createQueryBuilder("note") .select("COALESCE(note.threadId, note.id)", "conversationId") @@ -188,12 +189,11 @@ export class TimelineHelpers { return query.take(limit).getMany().then(p => { if (minId !== undefined) p = p.reverse(); - const cache = UserHelpers.getFreshAccountCache(); const conversations = p.map(c => { // Gather all unique IDs except for the local user const userIds = unique([c.userId].concat(c.visibleUserIds).filter(p => p != user.id)); - const users = userIds.map(id => UserHelpers.getUserCached(id, cache).catch(_ => null)); - const accounts = Promise.all(users).then(u => UserConverter.encodeMany(u.filter(u => u) as User[], cache)); + const users = userIds.map(id => UserHelpers.getUserCached(id, ctx).catch(_ => null)); + const accounts = Promise.all(users).then(u => UserConverter.encodeMany(u.filter(u => u) as User[], ctx)); const unread = Notifications.createQueryBuilder('notification') .where("notification.noteId = :noteId") .andWhere("notification.notifieeId = :userId") @@ -206,8 +206,8 @@ export class TimelineHelpers { return { id: c.threadId ?? c.id, - accounts: accounts.then(u => u.length > 0 ? u : UserConverter.encodeMany([user], cache)), // failsafe to prevent apps from crashing case when all participant users have been deleted - last_status: NoteConverter.encode(c, user, cache), + 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), 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 2e56dd8dd..dc60fae75 100644 --- a/packages/backend/src/server/api/mastodon/helpers/user.ts +++ b/packages/backend/src/server/api/mastodon/helpers/user.ts @@ -41,6 +41,7 @@ import { UserProfile } from "@/models/entities/user-profile.js"; import { verifyLink } from "@/services/fetch-rel-me.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; +import { MastoContext } from "@/server/api/mastodon/index.js"; export type AccountCache = { locks: AsyncLock; @@ -147,7 +148,7 @@ export class UserHelpers { return this.getUserRelationshipTo(target.id, localUser.id); } - public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined): Promise { + public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined, ctx: MastoContext): Promise { const updates: Partial = {}; const profileUpdates: Partial = {}; @@ -183,11 +184,11 @@ 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); + return this.verifyCredentials(user, ctx); } - public static async verifyCredentials(user: ILocalUser): Promise { - const acct = UserConverter.encode(user); + public static async verifyCredentials(user: ILocalUser, ctx: MastoContext): Promise { + const acct = UserConverter.encode(user, ctx); const profile = UserProfiles.findOneByOrFail({ userId: user.id }); const privacy = this.getDefaultNoteVisibility(user); const fields = profile.then(profile => profile.fields.map(field => { @@ -224,7 +225,7 @@ export class UserHelpers { }); } - public static async getUserMutes(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise> { + public static async getUserMutes(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise> { if (limit > 80) limit = 80; const query = PaginationHelpers.makePaginationQuery( @@ -243,7 +244,7 @@ export class UserHelpers { .map(p => p.mutee) .filter(p => p) as User[]; - const result = await UserConverter.encodeMany(users, cache) + const result = await UserConverter.encodeMany(users, ctx) .then(res => res.map(m => { const muting = p.find(acc => acc.muteeId === m.id); return { @@ -495,7 +496,8 @@ export class UserHelpers { return awaitAll(response); } - public static async getUserCached(id: string, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { + public static async getUserCached(id: string, ctx: MastoContext): Promise { + const cache = ctx.cache as AccountCache; return cache.locks.acquire(id, async () => { const cacheHit = cache.users.find(p => p.id == id); if (cacheHit) return cacheHit; @@ -506,8 +508,8 @@ export class UserHelpers { }); } - public static async getUserCachedOr404(id: string, cache: AccountCache = UserHelpers.getFreshAccountCache()): Promise { - return this.getUserCached(id, cache).catch(_ => { + public static async getUserCachedOr404(id: string, ctx: MastoContext): Promise { + return this.getUserCached(id, ctx).catch(_ => { throw new MastoApiError(404); }); } diff --git a/packages/backend/src/server/api/mastodon/middleware/pagination.ts b/packages/backend/src/server/api/mastodon/middleware/pagination.ts index e5140baa5..b9ecb06f8 100644 --- a/packages/backend/src/server/api/mastodon/middleware/pagination.ts +++ b/packages/backend/src/server/api/mastodon/middleware/pagination.ts @@ -1,7 +1,6 @@ import { MastoContext } from "@/server/api/mastodon/index.js"; import config from "@/config/index.js"; import { convertId, IdType } from "@/misc/convert-id.js"; -import { ObjectLiteral } from "typeorm"; type PaginationData = { limit: number;