[mastodon-client] GET /bookmarks

This commit is contained in:
Laura Hausmann 2023-09-24 22:46:44 +02:00
parent 5ee1a1e604
commit 376bfc6b13
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 96 additions and 55 deletions

View file

@ -399,14 +399,21 @@ export function apiAccountMastodon(router: Router): void {
} }
}); });
router.get("/v1/bookmarks", async (ctx) => { router.get("/v1/bookmarks", async (ctx) => {
const BASE_URL = `${ctx.protocol}://${ctx.hostname}`;
const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getBookmarks( const auth = await authenticate(ctx.headers.authorization, null);
convertTimelinesArgsId(limitToInt(ctx.query as any)), const user = auth[0] ?? null;
);
ctx.body = data.data.map((status) => convertStatus(status)); if (!user) {
ctx.status = 401;
return;
}
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));
ctx.body = bookmarks.map(s => convertStatus(s));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);

View file

@ -45,34 +45,4 @@ export class NoteHelpers {
return notes.reverse(); return notes.reverse();
} }
/**
*
* @param query
* @param limit
* @param reverse whether the result needs to be .reverse()'d. Set this to true when the parameter minId is not undefined in the original request.
*/
public static async execQuery(query: SelectQueryBuilder<Note>, limit: number, reverse: boolean): Promise<Note[]> {
// We fetch more than requested because some may be filtered out, and if there's less than
// requested, the pagination stops.
const found = [];
const take = Math.floor(limit * 1.5);
let skip = 0;
try {
while (found.length < limit) {
const notes = await query.take(take).skip(skip).getMany();
found.push(...notes);
skip += take;
if (notes.length < take) break;
}
} catch (error) {
return [];
}
if (found.length > limit) {
found.length = limit;
}
return reverse ? found.reverse() : found;
}
} }

View file

@ -7,33 +7,64 @@ export class PaginationHelpers {
q: SelectQueryBuilder<T>, q: SelectQueryBuilder<T>,
sinceId?: string, sinceId?: string,
maxId?: string, maxId?: string,
minId?: string minId?: string,
idField: string = "id"
) { ) {
if (sinceId && minId) throw new Error("Can't user both sinceId and minId params"); if (sinceId && minId) throw new Error("Can't user both sinceId and minId params");
if (sinceId && maxId) { if (sinceId && maxId) {
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.${idField} > :sinceId`, { sinceId: sinceId });
q.andWhere(`${q.alias}.id < :maxId`, { maxId: maxId }); q.andWhere(`${q.alias}.${idField} < :maxId`, { maxId: maxId });
q.orderBy(`${q.alias}.id`, "DESC"); q.orderBy(`${q.alias}.${idField}`, "DESC");
} if (minId && maxId) { } if (minId && maxId) {
q.andWhere(`${q.alias}.id > :minId`, { minId: minId }); q.andWhere(`${q.alias}.${idField} > :minId`, { minId: minId });
q.andWhere(`${q.alias}.id < :maxId`, { maxId: maxId }); q.andWhere(`${q.alias}.${idField} < :maxId`, { maxId: maxId });
q.orderBy(`${q.alias}.id`, "ASC"); q.orderBy(`${q.alias}.${idField}`, "ASC");
} else if (sinceId) { } else if (sinceId) {
q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.${idField} > :sinceId`, { sinceId: sinceId });
q.orderBy(`${q.alias}.id`, "DESC"); q.orderBy(`${q.alias}.${idField}`, "DESC");
} else if (minId) { } else if (minId) {
q.andWhere(`${q.alias}.id > :minId`, { minId: minId }); q.andWhere(`${q.alias}.${idField} > :minId`, { minId: minId });
q.orderBy(`${q.alias}.id`, "ASC"); q.orderBy(`${q.alias}.${idField}`, "ASC");
} else if (maxId) { } else if (maxId) {
q.andWhere(`${q.alias}.id < :maxId`, { maxId: maxId }); q.andWhere(`${q.alias}.${idField} < :maxId`, { maxId: maxId });
q.orderBy(`${q.alias}.id`, "DESC"); q.orderBy(`${q.alias}.${idField}`, "DESC");
} else { } else {
q.orderBy(`${q.alias}.id`, "DESC"); q.orderBy(`${q.alias}.${idField}`, "DESC");
} }
return q; return q;
} }
/**
*
* @param query
* @param limit
* @param reverse whether the result needs to be .reverse()'d. Set this to true when the parameter minId is not undefined in the original request.
*/
public static async execQuery<T extends ObjectLiteral>(query: SelectQueryBuilder<T>, limit: number, reverse: boolean): Promise<T[]> {
// We fetch more than requested because some may be filtered out, and if there's less than
// requested, the pagination stops.
const found = [];
const take = Math.floor(limit * 1.5);
let skip = 0;
try {
while (found.length < limit) {
const notes = await query.take(take).skip(skip).getMany();
found.push(...notes);
skip += take;
if (notes.length < take) break;
}
} catch (error) {
return [];
}
if (found.length > limit) {
found.length = limit;
}
return reverse ? found.reverse() : found;
}
public static appendLinkPaginationHeader(args: any, ctx: any, res: any, route: string): void { public static appendLinkPaginationHeader(args: any, ctx: any, res: any, route: string): void {
const link: string[] = []; const link: string[] = [];
const limit = args.limit ?? 40; const limit = args.limit ?? 40;

View file

@ -68,7 +68,7 @@ export class TimelineHelpers {
query.andWhere("note.visibility != 'hidden'"); query.andWhere("note.visibility != 'hidden'");
return NoteHelpers.execQuery(query, limit, minId !== undefined); return PaginationHelpers.execQuery(query, limit, minId !== undefined);
} }
public static async getPublicTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise<Note[]> { public static async getPublicTimeline(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, onlyMedia: boolean = false, local: boolean = false, remote: boolean = false): Promise<Note[]> {
@ -122,6 +122,6 @@ export class TimelineHelpers {
query.andWhere("note.visibility != 'hidden'"); query.andWhere("note.visibility != 'hidden'");
return NoteHelpers.execQuery(query, limit, minId !== undefined); return PaginationHelpers.execQuery(query, limit, minId !== undefined);
} }
} }

View file

@ -1,6 +1,6 @@
import { Note } from "@/models/entities/note.js"; import { Note } from "@/models/entities/note.js";
import { ILocalUser, User } from "@/models/entities/user.js"; import { ILocalUser, User } from "@/models/entities/user.js";
import { Followings, Notes, UserProfiles } from "@/models/index.js"; import { Followings, NoteFavorites, Notes, UserProfiles } from "@/models/index.js";
import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js";
import { generateRepliesQuery } from "@/server/api/common/generate-replies-query.js"; import { generateRepliesQuery } from "@/server/api/common/generate-replies-query.js";
import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js";
@ -75,9 +75,42 @@ export class UserHelpers {
query.andWhere("note.visibility != 'hidden'"); query.andWhere("note.visibility != 'hidden'");
return NoteHelpers.execQuery(query, limit, minId !== undefined); 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<Note[]> {
if (limit > 40) limit = 40;
const bookmarkQuery = NoteFavorites.createQueryBuilder("favorite")
.select("favorite.noteId")
.where("favorite.userId = :meId");
const query = PaginationHelpers.makePaginationQuery(
Notes.createQueryBuilder("note"),
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");
generateVisibilityQuery(query, localUser);
query.setParameters({ meId: localUser.id });
return PaginationHelpers.execQuery(query, limit, minId !== undefined);
}
private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> { private static async getUserRelationships(type: RelationshipType, user: User, localUser: ILocalUser | null, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40): Promise<LinkPaginationObject<User[]>> {
if (limit > 80) limit = 80; if (limit > 80) limit = 80;