From 7da7b6e09bbc17c8a353dde0fcc96c5a2e15ca1d Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sun, 24 Sep 2023 23:39:31 +0200 Subject: [PATCH] [mastodon-client] Proper pagination for /bookmarks & /favorites --- .../server/api/mastodon/endpoints/account.ts | 14 ++-- .../server/api/mastodon/helpers/pagination.ts | 4 +- .../src/server/api/mastodon/helpers/user.ts | 64 +++++++------------ 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 07226a8f0..92a8cfd77 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -198,7 +198,7 @@ export function apiAccountMastodon(router: Router): void { const followers = await UserConverter.encodeMany(res.data, cache); ctx.body = followers.map((account) => convertAccount(account)); - PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `accounts/${ctx.params.id}/followers`); + PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `v1/accounts/${ctx.params.id}/followers`); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -223,7 +223,7 @@ export function apiAccountMastodon(router: Router): void { const following = await UserConverter.encodeMany(res.data, cache); ctx.body = following.map((account) => convertAccount(account)); - PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `accounts/${ctx.params.id}/following`); + PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `v1/accounts/${ctx.params.id}/following`); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -410,10 +410,11 @@ export function apiAccountMastodon(router: Router): void { const cache = UserHelpers.getFreshAccountCache(); const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); - const bookmarks = await UserHelpers.getUserBookmarks(user, args.max_id, args.since_id, args.min_id, args.limit) - .then(n => NoteConverter.encodeMany(n, user, cache)); + const res = await UserHelpers.getUserBookmarks(user, args.max_id, args.since_id, args.min_id, args.limit); + const bookmarks = await NoteConverter.encodeMany(res.data, user, cache); ctx.body = bookmarks.map(s => convertStatus(s)); + PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `v1/bookmarks`); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -433,10 +434,11 @@ export function apiAccountMastodon(router: Router): void { const cache = UserHelpers.getFreshAccountCache(); const args = normalizeUrlQuery(convertTimelinesArgsId(limitToInt(ctx.query as any))); - const favorites = await UserHelpers.getUserFavorites(user, args.max_id, args.since_id, args.min_id, args.limit) - .then(n => NoteConverter.encodeMany(n, user, cache)); + const res = await UserHelpers.getUserFavorites(user, args.max_id, args.since_id, args.min_id, args.limit); + const favorites = await NoteConverter.encodeMany(res.data, user, cache); ctx.body = favorites.map(s => convertStatus(s)); + PaginationHelpers.appendLinkPaginationHeader(args, ctx, res, `v1/favourites`); } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/helpers/pagination.ts b/packages/backend/src/server/api/mastodon/helpers/pagination.ts index 16f932d88..b99562997 100644 --- a/packages/backend/src/server/api/mastodon/helpers/pagination.ts +++ b/packages/backend/src/server/api/mastodon/helpers/pagination.ts @@ -69,11 +69,11 @@ export class PaginationHelpers { const link: string[] = []; const limit = args.limit ?? 40; if (res.maxId) { - const l = `<${config.url}/api/v1/${route}?limit=${limit}&max_id=${convertId(res.maxId, IdType.MastodonId)}>; rel="next"`; + const l = `<${config.url}/api/${route}?limit=${limit}&max_id=${convertId(res.maxId, IdType.MastodonId)}>; rel="next"`; link.push(l); } if (res.minId) { - const l = `<${config.url}/api/v1/${route}?limit=${limit}&min_id=${convertId(res.minId, IdType.MastodonId)}>; rel="prev"`; + const l = `<${config.url}/api/${route}?limit=${limit}&min_id=${convertId(res.minId, IdType.MastodonId)}>; rel="prev"`; link.push(l); } if (link.length > 0){ diff --git a/packages/backend/src/server/api/mastodon/helpers/user.ts b/packages/backend/src/server/api/mastodon/helpers/user.ts index 06b529c95..7da05130d 100644 --- a/packages/backend/src/server/api/mastodon/helpers/user.ts +++ b/packages/backend/src/server/api/mastodon/helpers/user.ts @@ -78,68 +78,52 @@ export class UserHelpers { return PaginationHelpers.execQuery(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(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { if (limit > 40) limit = 40; - const bookmarkQuery = NoteFavorites.createQueryBuilder("favorite") - .select("favorite.noteId") - .where("favorite.userId = :meId"); - const query = PaginationHelpers.makePaginationQuery( - Notes.createQueryBuilder("note"), + NoteFavorites.createQueryBuilder("favorite"), sinceId, maxId, minId ) - .andWhere(`note.id IN (${bookmarkQuery.getQuery()})`) - .innerJoinAndSelect("note.user", "user") - .leftJoinAndSelect("user.avatar", "avatar") - .leftJoinAndSelect("user.banner", "banner") - .leftJoinAndSelect("note.reply", "reply") - .leftJoinAndSelect("note.renote", "renote") - .leftJoinAndSelect("reply.user", "replyUser") - .leftJoinAndSelect("replyUser.avatar", "replyUserAvatar") - .leftJoinAndSelect("replyUser.banner", "replyUserBanner") - .leftJoinAndSelect("renote.user", "renoteUser") - .leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar") - .leftJoinAndSelect("renoteUser.banner", "renoteUserBanner"); + .andWhere("favorite.userId = :meId", { meId: localUser.id }) + .leftJoinAndSelect("favorite.note", "note"); generateVisibilityQuery(query, localUser); - query.setParameters({ meId: localUser.id }); - return PaginationHelpers.execQuery(query, limit, minId !== undefined); + return PaginationHelpers.execQuery(query, limit, minId !== undefined) + .then(res => { + return { + data: res.map(p => p.note as Note), + maxId: res.map(p => p.id).at(-1), + minId: res.map(p => p.id)[0], + }; + }); } - public static async getUserFavorites(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise { + public static async getUserFavorites(localUser: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20): Promise> { if (limit > 40) limit = 40; - const favoriteQuery = NoteReactions.createQueryBuilder("reaction") - .select("reaction.noteId") - .where("reaction.userId = :meId"); - const query = PaginationHelpers.makePaginationQuery( - Notes.createQueryBuilder("note"), + NoteReactions.createQueryBuilder("reaction"), sinceId, maxId, minId ) - .andWhere(`note.id IN (${favoriteQuery.getQuery()})`) - .innerJoinAndSelect("note.user", "user") - .leftJoinAndSelect("user.avatar", "avatar") - .leftJoinAndSelect("user.banner", "banner") - .leftJoinAndSelect("note.reply", "reply") - .leftJoinAndSelect("note.renote", "renote") - .leftJoinAndSelect("reply.user", "replyUser") - .leftJoinAndSelect("replyUser.avatar", "replyUserAvatar") - .leftJoinAndSelect("replyUser.banner", "replyUserBanner") - .leftJoinAndSelect("renote.user", "renoteUser") - .leftJoinAndSelect("renoteUser.avatar", "renoteUserAvatar") - .leftJoinAndSelect("renoteUser.banner", "renoteUserBanner"); + .andWhere("reaction.userId = :meId", { meId: localUser.id }) + .leftJoinAndSelect("reaction.note", "note"); generateVisibilityQuery(query, localUser); - query.setParameters({ meId: localUser.id }); - return PaginationHelpers.execQuery(query, limit, minId !== undefined); + return PaginationHelpers.execQuery(query, limit, minId !== undefined) + .then(res => { + return { + data: res.map(p => p.note as Note), + maxId: res.map(p => p.id).at(-1), + minId: res.map(p => p.id)[0], + }; + }); } private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise> {