mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-22 01:47:39 -07:00
[mastodon-client] Handle html cache misses properly
This commit is contained in:
parent
7ab7edeefd
commit
58d70d005f
2 changed files with 66 additions and 5 deletions
|
@ -23,7 +23,7 @@ import { PollConverter } from "@/server/api/mastodon/converters/poll.js";
|
||||||
import { populatePoll } from "@/models/repositories/note.js";
|
import { populatePoll } from "@/models/repositories/note.js";
|
||||||
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
|
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
|
||||||
import { awaitAll } from "@/prelude/await-all.js";
|
import { awaitAll } from "@/prelude/await-all.js";
|
||||||
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
import { AccountCache, UserHelpers } from "@/server/api/mastodon/helpers/user.js";
|
||||||
import { In, IsNull } from "typeorm";
|
import { In, IsNull } from "typeorm";
|
||||||
import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js";
|
import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js";
|
||||||
import { getStubMastoContext, MastoContext } from "@/server/api/mastodon/index.js";
|
import { getStubMastoContext, MastoContext } from "@/server/api/mastodon/index.js";
|
||||||
|
@ -308,14 +308,40 @@ export class NoteConverter {
|
||||||
return Promise.resolve(dbHit)
|
return Promise.resolve(dbHit)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res === null || (res.updatedAt?.getTime() !== note.updatedAt?.getTime())) {
|
if (res === null || (res.updatedAt?.getTime() !== note.updatedAt?.getTime())) {
|
||||||
this.prewarmCache(note);
|
return this.dbCacheMiss(note, ctx);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.then(hit => hit?.updatedAt === note.updatedAt ? hit?.content ?? null : null);
|
.then(hit => hit?.updatedAt === note.updatedAt ? hit?.content ?? null : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async dbCacheMiss(note: Note, ctx: MastoContext): Promise<HtmlNoteCacheEntry | null> {
|
||||||
|
const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
|
||||||
|
const cache = ctx.cache as AccountCache;
|
||||||
|
return cache.locks.acquire(identifier, async () => {
|
||||||
|
const cachedContent = await this.noteContentHtmlCache.get(identifier);
|
||||||
|
if (cachedContent !== undefined) {
|
||||||
|
return { content: cachedContent } as HtmlNoteCacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quoteUri = note.renote
|
||||||
|
? isQuote(note)
|
||||||
|
? (note.renote.url ?? note.renote.uri ?? `${config.url}/notes/${note.renote.id}`)
|
||||||
|
: null
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const text = note.text !== null ? quoteUri !== null ? note.text.replaceAll(`RE: ${quoteUri}`, '').replaceAll(quoteUri, '').trimEnd() : note.text : null;
|
||||||
|
const content = text !== null
|
||||||
|
? MfmHelpers.toHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), note.userHost, false, quoteUri)
|
||||||
|
.then(p => p ?? escapeMFM(text))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
HtmlNoteCacheEntries.upsert({ noteId: note.id, updatedAt: note.updatedAt, content: await content }, ["noteId"]);
|
||||||
|
await this.noteContentHtmlCache.set(identifier, await content);
|
||||||
|
return { content } as HtmlNoteCacheEntry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static async prewarmCache(note: Note): Promise<void> {
|
public static async prewarmCache(note: Note): Promise<void> {
|
||||||
if (!config.htmlCache?.prewarm) return;
|
if (!config.htmlCache?.prewarm) return;
|
||||||
const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
|
const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`;
|
||||||
|
|
|
@ -244,13 +244,48 @@ export class UserConverter {
|
||||||
return Promise.resolve(dbHit)
|
return Promise.resolve(dbHit)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res === null || (res.updatedAt.getTime() !== (user.lastFetchedAt ?? user.createdAt).getTime())) {
|
if (res === null || (res.updatedAt.getTime() !== (user.lastFetchedAt ?? user.createdAt).getTime())) {
|
||||||
this.prewarmCache(user, profile);
|
return this.dbCacheMiss(user, profile, ctx);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async dbCacheMiss(user: User, profile: UserProfile | null, ctx: MastoContext): Promise<HtmlUserCacheEntry | null> {
|
||||||
|
const identifier = `${user.id}:${(user.lastFetchedAt ?? user.createdAt).getTime()}`;
|
||||||
|
const cache = ctx.cache as AccountCache;
|
||||||
|
return cache.locks.acquire(identifier, async () => {
|
||||||
|
const cachedBio = await this.userBioHtmlCache.get(identifier);
|
||||||
|
const cachedFields = await this.userFieldsHtmlCache.get(identifier);
|
||||||
|
if (cachedBio !== undefined && cachedFields !== undefined) {
|
||||||
|
return { bio: cachedBio, fields: cachedFields } as HtmlUserCacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile === undefined) {
|
||||||
|
profile = await UserProfiles.findOneBy({ userId: user.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
let bio: string | null | Promise<string | null> | undefined = cachedBio;
|
||||||
|
let fields: MastodonEntity.Field[] | Promise<MastodonEntity.Field[]> | undefined = cachedFields;
|
||||||
|
|
||||||
|
if (bio === undefined) {
|
||||||
|
bio = MfmHelpers.toHtml(mfm.parse(profile?.description ?? ""), profile?.mentions, user.host)
|
||||||
|
.then(p => p ?? escapeMFM(profile?.description ?? ""))
|
||||||
|
.then(p => p !== '<p></p>' ? p : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields === undefined) {
|
||||||
|
fields = Promise.all(profile!.fields.map(async p => this.encodeField(p, user.host, profile!.mentions)) ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
HtmlUserCacheEntries.upsert({ userId: user.id, updatedAt: user.lastFetchedAt ?? user.createdAt, bio: await bio, fields: await fields }, ["userId"]);
|
||||||
|
|
||||||
|
await this.userBioHtmlCache.set(identifier, await bio);
|
||||||
|
await this.userFieldsHtmlCache.set(identifier, await fields);
|
||||||
|
|
||||||
|
return { bio, fields } as HtmlUserCacheEntry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static async prewarmCache(user: User, profile?: UserProfile | null, oldProfile?: UserProfile | null): Promise<void> {
|
public static async prewarmCache(user: User, profile?: UserProfile | null, oldProfile?: UserProfile | null): Promise<void> {
|
||||||
const identifier = `${user.id}:${(user.lastFetchedAt ?? user.createdAt).getTime()}`;
|
const identifier = `${user.id}:${(user.lastFetchedAt ?? user.createdAt).getTime()}`;
|
||||||
if (profile !== null) {
|
if (profile !== null) {
|
||||||
|
|
Loading…
Reference in a new issue