[mastodon-client] Fully move cache into ctx

This commit is contained in:
Laura Hausmann 2023-10-07 20:29:58 +02:00
parent 8428f2efc4
commit 79c3e56989
Signed by: zotan
GPG key ID: D044E84C5BE01605
17 changed files with 123 additions and 122 deletions

View file

@ -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<MastodonEntity.Status> {
const noteUser = note.user ?? UserHelpers.getUserCached(note.userId, cache);
public static async encode(note: Note, user: ILocalUser | null, ctx: MastoContext, recurse: boolean = true): Promise<MastodonEntity.Status> {
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<MastodonEntity.Mention[]>;
@ -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<MastodonEntity.Status[]> {
const encoded = notes.map(n => this.encode(n, user, cache));
public static async encodeMany(notes: Note[], user: ILocalUser | null, ctx: MastoContext): Promise<MastodonEntity.Status[]> {
const encoded = notes.map(n => this.encode(n, user, ctx));
return Promise.all(encoded);
}
}

View file

@ -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<MastodonEntity.Notification> {
public static async encode(notification: Notification, localUser: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Notification> {
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<MastodonEntity.Notification[]> {
const encoded = notifications.map(u => this.encode(u, localUser, cache));
public static async encodeMany(notifications: Notification[], localUser: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Notification[]> {
const encoded = notifications.map(u => this.encode(u, localUser, ctx));
return Promise.all(encoded)
.then(p => p.filter(n => n !== null) as MastodonEntity.Notification[]);
}

View file

@ -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<MastodonEntity.Account> {
public static async encode(u: User, ctx: MastoContext): Promise<MastodonEntity.Account> {
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<MastodonEntity.Account[]> {
const encoded = users.map(u => this.encode(u, cache));
public static async encodeMany(users: User[], ctx: MastoContext): Promise<MastodonEntity.Account[]> {
const encoded = users.map(u => this.encode(u, ctx));
return Promise.all(encoded);
}

View file

@ -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);
},

View file

@ -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;

View file

@ -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)));
}
);

View file

@ -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));
}
);

View file

@ -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);

View file

@ -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);
},
);

View file

@ -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;

View file

@ -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<MastodonEntity.Instance> {
public static async getInstance(ctx: MastoContext): Promise<MastodonEntity.Instance> {
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<MastodonEntity.SuggestedAccount[]> {
const cache = UserHelpers.getFreshAccountCache();
public static async getFollowSuggestions(user: ILocalUser, limit: number, ctx: MastoContext): Promise<MastodonEntity.SuggestedAccount[]> {
const results: Promise<MastodonEntity.SuggestedAccount[]>[] = [];
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<MastodonEntity.Status[]> {
public static async getTrendingStatuses(limit: number = 20, offset: number = 0, ctx: MastoContext): Promise<MastodonEntity.Status[]> {
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<MastodonEntity.Tag[]> {

View file

@ -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<MastodonEntity.Status> {
public static async deleteNote(note: Note, user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Status> {
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<MastodonEntity.StatusEdit[]> {
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<MastodonEntity.StatusEdit[]> {
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<MastodonEntity.StatusEdit>[] = [];

View file

@ -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<MastodonEntity.Poll> {
public static async getPoll(note: Note, user: ILocalUser | null, ctx: MastoContext): Promise<MastodonEntity.Poll> {
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<MastodonEntity.Poll> {
public static async voteInPoll(choices: number[], note: Note, user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Poll> {
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);
}
}

View file

@ -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<MastodonEntity.Search> {
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<MastodonEntity.Search> {
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)
};

View file

@ -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<LinkPaginationObject<Note[]>> {
@ -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<LinkPaginationObject<MastodonEntity.Conversation[]>> {
public static async getConversations(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 20, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.Conversation[]>> {
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
}
});

View file

@ -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<MastodonEntity.Account> {
public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined, ctx: MastoContext): Promise<MastodonEntity.Account> {
const updates: Partial<User> = {};
const profileUpdates: Partial<UserProfile> = {};
@ -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<MastodonEntity.Account> {
const acct = UserConverter.encode(user);
public static async verifyCredentials(user: ILocalUser, ctx: MastoContext): Promise<MastodonEntity.Account> {
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<LinkPaginationObject<MastodonEntity.MutedAccount[]>> {
public static async getUserMutes(user: ILocalUser, maxId: string | undefined, sinceId: string | undefined, minId: string | undefined, limit: number = 40, ctx: MastoContext): Promise<LinkPaginationObject<MastodonEntity.MutedAccount[]>> {
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<User> {
public static async getUserCached(id: string, ctx: MastoContext): Promise<User> {
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<User> {
return this.getUserCached(id, cache).catch(_ => {
public static async getUserCachedOr404(id: string, ctx: MastoContext): Promise<User> {
return this.getUserCached(id, ctx).catch(_ => {
throw new MastoApiError(404);
});
}

View file

@ -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;