[mastodon-client] Set ctx.pagination in helper funcs

This commit is contained in:
Laura Hausmann 2023-10-07 21:53:38 +02:00
parent cc96b0ba72
commit 011d7f36c3
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
20 changed files with 88 additions and 134 deletions

View file

@ -21,4 +21,4 @@ export class AnnouncementConverter {
reactions: [], reactions: [],
}; };
} }
} }

View file

@ -146,4 +146,4 @@ export class AuthConverter {
return unique(res); return unique(res);
} }
} }

View file

@ -56,10 +56,9 @@ export function setupEndpointsAccount(router: Router): void {
const query = await UserHelpers.getUserCachedOr404(userId, ctx); const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
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 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); const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s)); ctx.body = tl.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
}, },
); );
router.get<{ Params: { id: string } }>( router.get<{ Params: { id: string } }>(
@ -76,10 +75,9 @@ export function setupEndpointsAccount(router: Router): void {
const query = await UserHelpers.getUserCachedOr404(userId, ctx); const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserFollowers(query, args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const followers = await UserConverter.encodeMany(res, ctx);
ctx.body = followers.map((account) => convertAccountId(account)); ctx.body = followers.map((account) => convertAccountId(account));
ctx.pagination = res.pagination;
}, },
); );
router.get<{ Params: { id: string } }>( router.get<{ Params: { id: string } }>(
@ -90,10 +88,9 @@ export function setupEndpointsAccount(router: Router): void {
const query = await UserHelpers.getUserCachedOr404(userId, ctx); const query = await UserHelpers.getUserCachedOr404(userId, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserFollowing(query, args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const following = await UserConverter.encodeMany(res, ctx);
ctx.body = following.map((account) => convertAccountId(account)); ctx.body = following.map((account) => convertAccountId(account));
ctx.pagination = res.pagination;
}, },
); );
router.get<{ Params: { id: string } }>( router.get<{ Params: { id: string } }>(
@ -177,9 +174,8 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserBookmarks(args.max_id, args.since_id, args.min_id, args.limit, 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); const bookmarks = await NoteConverter.encodeMany(res, ctx);
ctx.body = bookmarks.map(s => convertStatusIds(s)); ctx.body = bookmarks.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
} }
); );
router.get("/v1/favourites", router.get("/v1/favourites",
@ -187,9 +183,8 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserFavorites(args.max_id, args.since_id, args.min_id, args.limit, 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); const favorites = await NoteConverter.encodeMany(res, ctx);
ctx.body = favorites.map(s => convertStatusIds(s)); ctx.body = favorites.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
} }
); );
router.get("/v1/mutes", router.get("/v1/mutes",
@ -197,8 +192,7 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserMutes(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.body = res.map(m => convertAccountId(m));
ctx.pagination = res.pagination;
} }
); );
router.get("/v1/blocks", router.get("/v1/blocks",
@ -206,9 +200,8 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserBlocks(args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const blocks = await UserConverter.encodeMany(res, ctx);
ctx.body = blocks.map(b => convertAccountId(b)); ctx.body = blocks.map(b => convertAccountId(b));
ctx.pagination = res.pagination;
} }
); );
router.get("/v1/follow_requests", router.get("/v1/follow_requests",
@ -216,9 +209,8 @@ export function setupEndpointsAccount(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await UserHelpers.getUserFollowRequests(args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const requests = await UserConverter.encodeMany(res, ctx);
ctx.body = requests.map(b => convertAccountId(b)); ctx.body = requests.map(b => convertAccountId(b));
ctx.pagination = res.pagination;
} }
); );
router.post<{ Params: { id: string } }>( router.post<{ Params: { id: string } }>(

View file

@ -71,10 +71,9 @@ export function setupEndpointsList(router: Router): void {
const id = convertId(ctx.params.id, IdType.IceshrimpId); const id = convertId(ctx.params.id, IdType.IceshrimpId);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await ListHelpers.getListUsers(id, args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const accounts = await UserConverter.encodeMany(res, ctx);
ctx.body = accounts.map(account => convertAccountId(account)); ctx.body = accounts.map(account => convertAccountId(account));
ctx.pagination = res.pagination;
}, },
); );
router.post<{ Params: { id: string } }>( router.post<{ Params: { id: string } }>(

View file

@ -12,10 +12,9 @@ export function setupEndpointsNotifications(router: Router): void {
async (ctx) => { async (ctx) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)), ['types[]', 'exclude_types[]']);
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 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); const data = await NotificationConverter.encodeMany(res, ctx);
ctx.body = data.map(n => convertNotificationIds(n)); ctx.body = data.map(n => convertNotificationIds(n));
ctx.pagination = res.pagination;
} }
); );

View file

@ -21,4 +21,4 @@ export function setupEndpointsSearch(router: Router): void {
} }
} }
); );
} }

View file

@ -117,9 +117,8 @@ export function setupEndpointsStatus(router: Router): void {
const note = await NoteHelpers.getNoteOr404(id, ctx); const note = await NoteHelpers.getNoteOr404(id, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any)));
const res = await NoteHelpers.getNoteRebloggedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx); 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); const users = await UserConverter.encodeMany(res, ctx);
ctx.body = users.map(m => convertAccountId(m)); ctx.body = users.map(m => convertAccountId(m));
ctx.pagination = res.pagination;
} }
); );
router.get<{ Params: { id: string } }>( router.get<{ Params: { id: string } }>(
@ -129,10 +128,9 @@ export function setupEndpointsStatus(router: Router): void {
const id = convertId(ctx.params.id, IdType.IceshrimpId); const id = convertId(ctx.params.id, IdType.IceshrimpId);
const note = await NoteHelpers.getNoteOr404(id, ctx); const note = await NoteHelpers.getNoteOr404(id, ctx);
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query as any))); 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 res = await NoteHelpers.getNoteFavoritedBy(note, args.max_id, args.since_id, args.min_id, args.limit, ctx);
const users = await UserConverter.encodeMany(res.data, ctx); const users = await UserConverter.encodeMany(res, ctx);
ctx.body = users.map(m => convertAccountId(m)); ctx.body = users.map(m => convertAccountId(m));
ctx.pagination = res.pagination;
} }
); );
router.post<{ Params: { id: string } }>( router.post<{ Params: { id: string } }>(

View file

@ -68,10 +68,9 @@ export function setupEndpointsTimeline(router: Router): void {
async (ctx, reply) => { async (ctx, reply) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query)))); const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))));
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 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); const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s)); ctx.body = tl.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
}); });
router.get<{ Params: { hashtag: string } }>( router.get<{ Params: { hashtag: string } }>(
"/v1/timelines/tag/:hashtag", "/v1/timelines/tag/:hashtag",
@ -79,11 +78,10 @@ export function setupEndpointsTimeline(router: Router): void {
async (ctx, reply) => { async (ctx, reply) => {
const tag = (ctx.params.hashtag ?? '').trim(); const tag = (ctx.params.hashtag ?? '').trim();
const args = normalizeUrlQuery(convertPaginationArgsIds(argsToBools(limitToInt(ctx.query))), ['any[]', 'all[]', 'none[]']); 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 res = await TimelineHelpers.getTagTimeline(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, ctx);
const tl = await NoteConverter.encodeMany(res.data, ctx); const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s)); ctx.body = tl.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
}, },
); );
router.get("/v1/timelines/home", router.get("/v1/timelines/home",
@ -91,10 +89,9 @@ export function setupEndpointsTimeline(router: Router): void {
async (ctx, reply) => { async (ctx, reply) => {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await TimelineHelpers.getHomeTimeline(args.max_id, args.since_id, args.min_id, args.limit, 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); const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s)); ctx.body = tl.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
}); });
router.get<{ Params: { listId: string } }>( router.get<{ Params: { listId: string } }>(
"/v1/timelines/list/:listId", "/v1/timelines/list/:listId",
@ -106,10 +103,9 @@ export function setupEndpointsTimeline(router: Router): void {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await TimelineHelpers.getListTimeline(list, args.max_id, args.since_id, args.min_id, args.limit, 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); const tl = await NoteConverter.encodeMany(res, ctx);
ctx.body = tl.map(s => convertStatusIds(s)); ctx.body = tl.map(s => convertStatusIds(s));
ctx.pagination = res.pagination;
}, },
); );
router.get("/v1/conversations", router.get("/v1/conversations",
@ -118,8 +114,7 @@ export function setupEndpointsTimeline(router: Router): void {
const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query))); const args = normalizeUrlQuery(convertPaginationArgsIds(limitToInt(ctx.query)));
const res = await TimelineHelpers.getConversations(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.body = res.map(c => convertConversationIds(c));
ctx.pagination = res.pagination;
} }
); );
} }

View file

@ -1,6 +1,6 @@
import { ILocalUser, User } from "@/models/entities/user.js"; import { ILocalUser, User } from "@/models/entities/user.js";
import { Blockings, UserListJoinings, UserLists, Users } from "@/models/index.js"; import { Blockings, UserListJoinings, UserLists, Users } from "@/models/index.js";
import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; import { generatePaginationData } from "@/server/api/mastodon/middleware/pagination.js";
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { UserList } from "@/models/entities/user-list.js"; import { UserList } from "@/models/entities/user-list.js";
import { pushUserToUserList } from "@/services/user-list/push.js"; import { pushUserToUserList } from "@/services/user-list/push.js";
@ -38,7 +38,7 @@ export class ListHelpers {
}) })
} }
public static async getListUsers(id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getListUsers(id: string, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
const list = await UserLists.findOneBy({ userId: user.id, id: id }); const list = await UserLists.findOneBy({ userId: user.id, id: id });
@ -58,10 +58,8 @@ export class ListHelpers {
.map(p => p.user) .map(p => p.user)
.filter(p => p) as User[]; .filter(p => p) as User[];
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: users, return users;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }

View file

@ -15,7 +15,7 @@ import { genId } from "@/misc/gen-id.js";
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js" import { generatePaginationData } from "@/server/api/mastodon/middleware/pagination.js"
import { addPinned, removePinned } from "@/services/i/pin.js"; import { addPinned, removePinned } from "@/services/i/pin.js";
import { NoteConverter } from "@/server/api/mastodon/converters/note.js"; import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { convertId, IdType } from "@/misc/convert-id.js"; import { convertId, IdType } from "@/misc/convert-id.js";
@ -161,7 +161,7 @@ export class NoteHelpers {
return status; return status;
} }
public static async getNoteFavoritedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> { public static async getNoteFavoritedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const query = PaginationHelpers.makePaginationQuery( const query = PaginationHelpers.makePaginationQuery(
NoteReactions.createQueryBuilder("reaction"), NoteReactions.createQueryBuilder("reaction"),
@ -178,10 +178,8 @@ export class NoteHelpers {
.map(p => p.user) .map(p => p.user)
.filter(p => p) as User[]; .filter(p => p) as User[];
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: users, return users;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
@ -231,7 +229,7 @@ export class NoteHelpers {
} }
} }
public static async getNoteRebloggedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getNoteRebloggedBy(note: Note, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser | null; const user = ctx.user as ILocalUser | null;
const query = PaginationHelpers.makePaginationQuery( const query = PaginationHelpers.makePaginationQuery(
@ -252,10 +250,8 @@ export class NoteHelpers {
.map(p => p.user) .map(p => p.user)
.filter(p => p) as User[]; .filter(p => p) as User[];
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: users, return users;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }

View file

@ -3,11 +3,10 @@ import { Notes, Notifications } from "@/models/index.js";
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { Notification } from "@/models/entities/notification.js"; import { Notification } from "@/models/entities/notification.js";
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.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"; import { MastoContext } from "@/server/api/mastodon/index.js";
export class NotificationHelpers { export class NotificationHelpers {
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<LinkPaginationObject<Notification[]>> { 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<Notification[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -34,7 +33,7 @@ export class NotificationHelpers {
query.leftJoinAndSelect("notification.note", "note"); query.leftJoinAndSelect("notification.note", "note");
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
public static async getNotification(id: string, ctx: MastoContext): Promise<Notification | null> { public static async getNotification(id: string, ctx: MastoContext): Promise<Notification | null> {

View file

@ -1,5 +1,5 @@
import { ObjectLiteral, SelectQueryBuilder } from "typeorm"; import { ObjectLiteral, SelectQueryBuilder } from "typeorm";
import { LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; import { MastoContext } from "@/server/api/mastodon/index.js";
export class PaginationHelpers { export class PaginationHelpers {
public static makePaginationQuery<T extends ObjectLiteral>( public static makePaginationQuery<T extends ObjectLiteral>(
@ -45,18 +45,16 @@ export class PaginationHelpers {
return query.take(limit).getMany().then(found => reverse ? found.reverse() : found); return query.take(limit).getMany().then(found => reverse ? found.reverse() : found);
} }
public static async execQueryLinkPagination<T extends ObjectLiteral>(query: SelectQueryBuilder<T>, limit: number, reverse: boolean): Promise<LinkPaginationObject<T[]>> { public static async execQueryLinkPagination<T extends ObjectLiteral>(query: SelectQueryBuilder<T>, limit: number, reverse: boolean, ctx: MastoContext): Promise<T[]> {
return this.execQuery(query, limit, reverse) return this.execQuery(query, limit, reverse)
.then(p => { .then(p => {
const ids = p.map(x => x.id); const ids = p.map(x => x.id);
return { ctx.pagination = p.length > 0 ? {
data: p, limit: limit,
pagination: p.length > 0 ? { maxId: ids.at(reverse ? 0 : -1),
limit: limit, minId: ids.at(reverse ? -1 : 0)
maxId: ids.at(reverse ? 0 : -1), } : undefined;
minId: ids.at(reverse ? -1 : 0) return p;
} : undefined
}
}); });
} }
} }

View file

@ -18,11 +18,11 @@ import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { awaitAll } from "@/prelude/await-all.js"; import { awaitAll } from "@/prelude/await-all.js";
import { unique } from "@/prelude/array.js"; import { unique } from "@/prelude/array.js";
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; import { generatePaginationData } from "@/server/api/mastodon/middleware/pagination.js";
import { MastoContext } from "@/server/api/mastodon/index.js"; import { MastoContext } from "@/server/api/mastodon/index.js";
export class TimelineHelpers { export class TimelineHelpers {
public static async getHomeTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> { public static async getHomeTimeline(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -54,10 +54,10 @@ export class TimelineHelpers {
query.andWhere("note.visibility != 'hidden'"); query.andWhere("note.visibility != 'hidden'");
query.andWhere("note.visibility != 'specified'"); query.andWhere("note.visibility != 'specified'");
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
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<LinkPaginationObject<Note[]>> { 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<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -98,10 +98,10 @@ export class TimelineHelpers {
if (onlyMedia) query.andWhere("note.fileIds != '{}'"); if (onlyMedia) query.andWhere("note.fileIds != '{}'");
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
public static async getListTimeline(list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> { public static async getListTimeline(list: UserList, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
if (user.id != list.userId) throw new Error("List is not owned by user"); if (user.id != list.userId) throw new Error("List is not owned by user");
@ -123,10 +123,10 @@ export class TimelineHelpers {
generateVisibilityQuery(query, user); generateVisibilityQuery(query, user);
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
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<LinkPaginationObject<Note[]>> { 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<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const user = ctx.user as ILocalUser | null; const user = ctx.user as ILocalUser | null;
@ -165,10 +165,10 @@ export class TimelineHelpers {
if (onlyMedia) query.andWhere("note.fileIds != '{}'"); if (onlyMedia) query.andWhere("note.fileIds != '{}'");
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
public static async getConversations(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.Conversation[]>> { public static async getConversations(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<MastodonEntity.Conversation[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
const sq = Notes.createQueryBuilder("note") const sq = Notes.createQueryBuilder("note")
@ -216,12 +216,9 @@ export class TimelineHelpers {
unread: unread unread: unread
} }
}); });
const res = {
data: Promise.all(conversations.map(c => awaitAll(c))),
pagination: generatePaginationData(p.map(p => p.threadId ?? p.id), limit, minId !== undefined)
};
return awaitAll(res); ctx.pagination = generatePaginationData(p.map(p => p.threadId ?? p.id), limit, minId !== undefined);
return Promise.all(conversations.map(c => awaitAll(c)));
}); });
} }
} }

View file

@ -40,7 +40,7 @@ import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js";
import { UserProfile } from "@/models/entities/user-profile.js"; import { UserProfile } from "@/models/entities/user-profile.js";
import { verifyLink } from "@/services/fetch-rel-me.js"; import { verifyLink } from "@/services/fetch-rel-me.js";
import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js"; import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js";
import { generatePaginationData, LinkPaginationObject } from "@/server/api/mastodon/middleware/pagination.js"; import { generatePaginationData } from "@/server/api/mastodon/middleware/pagination.js";
import { MastoContext } from "@/server/api/mastodon/index.js"; import { MastoContext } from "@/server/api/mastodon/index.js";
export type AccountCache = { export type AccountCache = {
@ -238,7 +238,7 @@ export class UserHelpers {
}); });
} }
public static async getUserMutes(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.MutedAccount[]>> { public static async getUserMutes(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<MastodonEntity.MutedAccount[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -267,14 +267,12 @@ export class UserHelpers {
} as MastodonEntity.MutedAccount } as MastodonEntity.MutedAccount
})); }));
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: result, return result;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
public static async getUserBlocks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getUserBlocks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -294,14 +292,12 @@ export class UserHelpers {
.map(p => p.blockee) .map(p => p.blockee)
.filter(p => p) as User[]; .filter(p => p) as User[];
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: users, return users;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
public static async getUserFollowRequests(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getUserFollowRequests(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const user = ctx.user as ILocalUser; const user = ctx.user as ILocalUser;
@ -321,20 +317,18 @@ export class UserHelpers {
.map(p => p.follower) .map(p => p.follower)
.filter(p => p) as User[]; .filter(p => p) as User[];
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: users, return users;
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
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<LinkPaginationObject<Note[]>> { 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<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const localUser = ctx.user as ILocalUser | null; const localUser = ctx.user as ILocalUser | null;
if (tagged !== undefined) { if (tagged !== undefined) {
//FIXME respect tagged //FIXME respect tagged
return { data: [] }; return [];
} }
const query = PaginationHelpers.makePaginationQuery( const query = PaginationHelpers.makePaginationQuery(
@ -387,10 +381,10 @@ export class UserHelpers {
query.setParameters({ userId: user.id }); query.setParameters({ userId: user.id });
return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined); return PaginationHelpers.execQueryLinkPagination(query, limit, minId !== undefined, ctx);
} }
public static async getUserBookmarks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> { public static async getUserBookmarks(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const localUser = ctx.user as ILocalUser; const localUser = ctx.user as ILocalUser;
@ -407,14 +401,12 @@ export class UserHelpers {
return PaginationHelpers.execQuery(query, limit, minId !== undefined) return PaginationHelpers.execQuery(query, limit, minId !== undefined)
.then(res => { .then(res => {
return { ctx.pagination = generatePaginationData(res.map(p => p.id), limit, minId !== undefined);
data: res.map(p => p.note as Note), return res.map(p => p.note as Note);
pagination: generatePaginationData(res.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
public static async getUserFavorites(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<Note[]>> { public static async getUserFavorites(maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<Note[]> {
if (limit > 40) limit = 40; if (limit > 40) limit = 40;
const localUser = ctx.user as ILocalUser; const localUser = ctx.user as ILocalUser;
@ -431,22 +423,20 @@ export class UserHelpers {
return PaginationHelpers.execQuery(query, limit, minId !== undefined) return PaginationHelpers.execQuery(query, limit, minId !== undefined)
.then(res => { .then(res => {
return { ctx.pagination = generatePaginationData(res.map(p => p.id), limit, minId !== undefined);
data: res.map(p => p.note as Note), return res.map(p => p.note as Note);
pagination: generatePaginationData(res.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
private static async getUserRelationships(type: RelationshipType, user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { private static async getUserRelationships(type: RelationshipType, user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;
const localUser = ctx.user as ILocalUser | null; const localUser = ctx.user as ILocalUser | null;
const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.ffVisibility === "private") { if (profile.ffVisibility === "private") {
if (!localUser || user.id !== localUser.id) return { data: [] }; if (!localUser || user.id !== localUser.id) return [];
} else if (profile.ffVisibility === "followers") { } else if (profile.ffVisibility === "followers") {
if (!localUser) return { data: [] }; if (!localUser) return [];
if (user.id !== localUser.id) { if (user.id !== localUser.id) {
const isFollowed = await Followings.exist({ const isFollowed = await Followings.exist({
where: { where: {
@ -454,7 +444,7 @@ export class UserHelpers {
followerId: localUser.id, followerId: localUser.id,
}, },
}); });
if (!isFollowed) return { data: [] }; if (!isFollowed) return [];
} }
} }
@ -476,18 +466,16 @@ export class UserHelpers {
return query.take(limit).getMany().then(p => { return query.take(limit).getMany().then(p => {
if (minId !== undefined) p = p.reverse(); if (minId !== undefined) p = p.reverse();
return { ctx.pagination = generatePaginationData(p.map(p => p.id), limit, minId !== undefined);
data: p.map(p => type === "followers" ? p.follower : p.followee).filter(p => p) as User[], return p.map(p => type === "followers" ? p.follower : p.followee).filter(p => p) as User[];
pagination: generatePaginationData(p.map(p => p.id), limit, minId !== undefined)
};
}); });
} }
public static async getUserFollowers(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getUserFollowers(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
return this.getUserRelationships('followers', user, maxId, sinceId, minId, limit, ctx); return this.getUserRelationships('followers', user, maxId, sinceId, minId, limit, ctx);
} }
public static async getUserFollowing(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<User[]>> { public static async getUserFollowing(user: User, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<User[]> {
return this.getUserRelationships('following', user, maxId, sinceId, minId, limit, ctx); return this.getUserRelationships('following', user, maxId, sinceId, minId, limit, ctx);
} }

View file

@ -27,4 +27,4 @@ export function auth(required: boolean, scopes: string[] = []) {
await next(); await next();
}; };
} }

View file

@ -4,4 +4,4 @@ import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
export async function CacheMiddleware(ctx: MastoContext, next: () => Promise<any>) { export async function CacheMiddleware(ctx: MastoContext, next: () => Promise<any>) {
ctx.cache = UserHelpers.getFreshAccountCache(); ctx.cache = UserHelpers.getFreshAccountCache();
await next(); await next();
} }

View file

@ -45,4 +45,4 @@ export async function CatchErrorsMiddleware(ctx: MastoContext, next: () => Promi
ctx.body = { error: e.message ?? e }; ctx.body = { error: e.message ?? e };
return; return;
} }
} }

View file

@ -9,4 +9,4 @@ export async function NormalizeQueryMiddleware(ctx: MastoContext, next: () => Pr
} }
} }
await next(); await next();
} }

View file

@ -8,11 +8,6 @@ type PaginationData = {
minId?: string | undefined; minId?: string | undefined;
} }
export type LinkPaginationObject<T> = {
data: T;
pagination?: PaginationData;
}
export async function PaginationMiddleware(ctx: MastoContext, next: () => Promise<any>) { export async function PaginationMiddleware(ctx: MastoContext, next: () => Promise<any>) {
await next(); await next();
if (!ctx.pagination) return; if (!ctx.pagination) return;
@ -40,4 +35,4 @@ export function generatePaginationData(ids: string[], limit: number, reverse: bo
maxId: ids.at(reverse ? 0 : -1), maxId: ids.at(reverse ? 0 : -1),
minId: ids.at(reverse ? -1 : 0) minId: ids.at(reverse ? -1 : 0)
} }
} }

View file

@ -7,4 +7,4 @@ const headers = {
export async function SetHeadersMiddleware(ctx: MastoContext, next: () => Promise<any>) { export async function SetHeadersMiddleware(ctx: MastoContext, next: () => Promise<any>) {
ctx.set(headers); ctx.set(headers);
await next(); await next();
} }