fix emoji caching
This commit is contained in:
parent
df5aff9d91
commit
bb0925224d
14 changed files with 552 additions and 288 deletions
|
|
@ -36,6 +36,7 @@ import { IActivity, IAnnounce, ICreate } from '@/core/activitypub/type.js';
|
|||
import { isPureRenote, isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { CustomEmojiService, encodeEmojiKey } from '@/core/CustomEmojiService.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
|
|
@ -89,6 +90,7 @@ export class ActivityPubServerService {
|
|||
private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
|
||||
private loggerService: LoggerService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
this.logger = this.loggerService.getLogger('apserv', 'pink');
|
||||
|
|
@ -1037,10 +1039,8 @@ export class ActivityPubServerService {
|
|||
const { reject } = await this.checkAuthorizedFetch(request, reply);
|
||||
if (reject) return;
|
||||
|
||||
const emoji = await this.emojisRepository.findOneBy({
|
||||
host: IsNull(),
|
||||
name: request.params.emoji,
|
||||
});
|
||||
const emojiKey = encodeEmojiKey({ name: request.params.emoji, host: null });
|
||||
const emoji = await this.customEmojiService.emojisByKeyCache.fetchMaybe(emojiKey);
|
||||
|
||||
if (emoji == null || emoji.localOnly) {
|
||||
reply.code(404);
|
||||
|
|
@ -1048,7 +1048,7 @@ export class ActivityPubServerService {
|
|||
}
|
||||
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.addContext(await this.apRendererService.renderEmoji(emoji)));
|
||||
return (this.apRendererService.addContext(this.apRendererService.renderEmoji(emoji)));
|
||||
});
|
||||
|
||||
// like
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import type Logger from '@/logger.js';
|
|||
import * as Acct from '@/misc/acct.js';
|
||||
import { genIdenticon } from '@/misc/gen-identicon.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { CustomEmojiService, encodeEmojiKey } from '@/core/CustomEmojiService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { renderInlineError } from '@/misc/render-inline-error.js';
|
||||
|
|
@ -71,6 +72,7 @@ export class ServerService implements OnApplicationShutdown {
|
|||
private globalEventService: GlobalEventService,
|
||||
private loggerService: LoggerService,
|
||||
private oauth2ProviderService: OAuth2ProviderService,
|
||||
private readonly customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('server', 'gray');
|
||||
}
|
||||
|
|
@ -171,14 +173,15 @@ export class ServerService implements OnApplicationShutdown {
|
|||
return;
|
||||
}
|
||||
|
||||
const name = pathChunks.shift();
|
||||
const name = pathChunks.shift() as string;
|
||||
const host = pathChunks.pop();
|
||||
|
||||
const emoji = await this.emojisRepository.findOneBy({
|
||||
const emojiKey = encodeEmojiKey({
|
||||
// `@.` is the spec of ReactionService.decodeReaction
|
||||
host: (host === undefined || host === '.') ? IsNull() : host,
|
||||
host: (host === undefined || host === '.') ? null : host,
|
||||
name: name,
|
||||
});
|
||||
const emoji = await this.customEmojiService.emojisByKeyCache.fetchMaybe(emojiKey);
|
||||
|
||||
reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private driveService: DriveService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const emoji = await this.emojisRepository.findOneBy({ id: ps.emojiId });
|
||||
const emoji = await this.customEmojiService.emojisByIdCache.fetchMaybe(ps.emojiId);
|
||||
if (emoji == null) {
|
||||
throw new ApiError(meta.errors.noSuchEmoji);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
if (ps.query) {
|
||||
// TODO use string inclusion func instead of dynamic query building
|
||||
q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query.normalize('NFC')) + '%' })
|
||||
.orderBy('length(emoji.name)', 'ASC');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { EmojisRepository } from '@/models/_.js';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
|
@ -47,14 +48,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private readonly customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const emoji = await this.emojisRepository.findOneOrFail({
|
||||
where: {
|
||||
name: ps.name,
|
||||
host: IsNull(),
|
||||
},
|
||||
});
|
||||
const emoji = await this.customEmojiService.emojisByKeyCache.fetch(ps.name);
|
||||
|
||||
return this.emojiEntityService.packDetailed(emoji);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { EmojisRepository } from '@/models/_.js';
|
||||
import type { EmojisRepository, MiEmoji } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { CacheManagementService, type ManagedMemorySingleCache } from '@/core/CacheManagementService.js';
|
||||
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
|
@ -32,8 +34,11 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
|
||||
// 2 calls per second
|
||||
// Up to 20 calls, then 5 / second
|
||||
limit: {
|
||||
type: 'bucket',
|
||||
size: 20,
|
||||
|
||||
duration: 1000,
|
||||
max: 2,
|
||||
},
|
||||
|
|
@ -48,21 +53,41 @@ export const paramDef = {
|
|||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
// Short (2 second) cache to handle rapid bursts of fetching the emoji list.
|
||||
// This just stores the IDs - the actual emojis are cached by CustomEmojiService
|
||||
private readonly localEmojiIdsCache: ManagedMemorySingleCache<MiEmoji['id'][]>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private emojiEntityService: EmojiEntityService,
|
||||
private readonly customEmojiService: CustomEmojiService,
|
||||
|
||||
cacheManagementService: CacheManagementService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const emojis = await this.emojisRepository.createQueryBuilder()
|
||||
.where('host IS NULL')
|
||||
.orderBy('LOWER(category)', 'ASC')
|
||||
.addOrderBy('LOWER(name)', 'ASC')
|
||||
.getMany();
|
||||
// Fetch the latest emoji list
|
||||
const emojiIds = await this.localEmojiIdsCache.fetch(async () => {
|
||||
const emojis = await this.emojisRepository.createQueryBuilder()
|
||||
.select('id')
|
||||
.where('host IS NULL')
|
||||
.orderBy('LOWER(category)', 'ASC')
|
||||
.addOrderBy('LOWER(name)', 'ASC')
|
||||
.getMany() as { id: MiEmoji['id'] }[];
|
||||
|
||||
return emojis.map(e => e.id);
|
||||
});
|
||||
|
||||
// Fetch the latest version of each emoji
|
||||
const emojis = await this.customEmojiService.emojisByIdCache.fetchMany(emojiIds);
|
||||
|
||||
// Pack and return everything
|
||||
return {
|
||||
emojis: await this.emojiEntityService.packSimpleMany(emojis),
|
||||
emojis: await this.emojiEntityService.packSimpleMany(emojis.values),
|
||||
};
|
||||
});
|
||||
|
||||
this.localEmojiIdsCache = cacheManagementService.createMemorySingleCache<MiEmoji['id'][]>(1000 * 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue