summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/CustomEmojiService.ts
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-04-06 11:14:43 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-04-06 11:14:43 +0900
commit73203a3d72b355e3c230c46771292ff9520675c0 (patch)
tree5c6a2861d3c1fa65392e583229985d7cdc1b03ef /packages/backend/src/core/CustomEmojiService.ts
parentUpdate CHANGELOG.md (diff)
downloadsharkey-73203a3d72b355e3c230c46771292ff9520675c0.tar.gz
sharkey-73203a3d72b355e3c230c46771292ff9520675c0.tar.bz2
sharkey-73203a3d72b355e3c230c46771292ff9520675c0.zip
perf(backend): cache local custom emojis
Diffstat (limited to 'packages/backend/src/core/CustomEmojiService.ts')
-rw-r--r--packages/backend/src/core/CustomEmojiService.ts187
1 files changed, 157 insertions, 30 deletions
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 1c3b60e5d7..604a94707f 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -1,24 +1,28 @@
import { Inject, Injectable } from '@nestjs/common';
import { DataSource, In, IsNull } from 'typeorm';
+import Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import { IdService } from '@/core/IdService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Emoji } from '@/models/entities/Emoji.js';
-import type { EmojisRepository, Note } from '@/models/index.js';
+import type { EmojisRepository } from '@/models/index.js';
import { bindThis } from '@/decorators.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { Config } from '@/config.js';
-import { ReactionService } from '@/core/ReactionService.js';
import { query } from '@/misc/prelude/url.js';
@Injectable()
export class CustomEmojiService {
private cache: MemoryKVCache<Emoji | null>;
+ public localEmojisCache: RedisSingleCache<Map<string, Emoji>>;
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.config)
private config: Config,
@@ -32,9 +36,16 @@ export class CustomEmojiService {
private idService: IdService,
private emojiEntityService: EmojiEntityService,
private globalEventService: GlobalEventService,
- private reactionService: ReactionService,
) {
this.cache = new MemoryKVCache<Emoji | null>(1000 * 60 * 60 * 12);
+
+ this.localEmojisCache = new RedisSingleCache<Map<string, Emoji>>(this.redisClient, 'localEmojis', {
+ lifetime: 1000 * 60 * 30, // 30m
+ memoryCacheLifetime: 1000 * 60 * 3, // 3m
+ fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
+ toRedisConverter: (value) => JSON.stringify(value.values()),
+ fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換
+ });
}
@bindThis
@@ -60,7 +71,7 @@ export class CustomEmojiService {
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
if (data.host == null) {
- await this.db.queryResultCache?.remove(['meta_emojis']);
+ this.localEmojisCache.refresh();
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(emoji.id),
@@ -71,6 +82,146 @@ export class CustomEmojiService {
}
@bindThis
+ public async update(id: Emoji['id'], data: {
+ name?: string;
+ category?: string | null;
+ aliases?: string[];
+ license?: string | null;
+ }): Promise<void> {
+ const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
+ const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
+ if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
+
+ await this.emojisRepository.update(emoji.id, {
+ updatedAt: new Date(),
+ name: data.name,
+ category: data.category,
+ aliases: data.aliases,
+ license: data.license,
+ });
+
+ this.localEmojisCache.refresh();
+
+ const updated = await this.emojiEntityService.packDetailed(emoji.id);
+
+ if (emoji.name === data.name) {
+ this.globalEventService.publishBroadcastStream('emojiUpdated', {
+ emojis: [updated],
+ });
+ } else {
+ this.globalEventService.publishBroadcastStream('emojiDeleted', {
+ emojis: [await this.emojiEntityService.packDetailed(emoji)],
+ });
+
+ this.globalEventService.publishBroadcastStream('emojiAdded', {
+ emoji: updated,
+ });
+ }
+ }
+
+ @bindThis
+ public async addAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
+ const emojis = await this.emojisRepository.findBy({
+ id: In(ids),
+ });
+
+ for (const emoji of emojis) {
+ await this.emojisRepository.update(emoji.id, {
+ updatedAt: new Date(),
+ aliases: [...new Set(emoji.aliases.concat(aliases))],
+ });
+ }
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiUpdated', {
+ emojis: await this.emojiEntityService.packDetailedMany(ids),
+ });
+ }
+
+ @bindThis
+ public async setAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
+ await this.emojisRepository.update({
+ id: In(ids),
+ }, {
+ updatedAt: new Date(),
+ aliases: aliases,
+ });
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiUpdated', {
+ emojis: await this.emojiEntityService.packDetailedMany(ids),
+ });
+ }
+
+ @bindThis
+ public async removeAliasesBulk(ids: Emoji['id'][], aliases: string[]) {
+ const emojis = await this.emojisRepository.findBy({
+ id: In(ids),
+ });
+
+ for (const emoji of emojis) {
+ await this.emojisRepository.update(emoji.id, {
+ updatedAt: new Date(),
+ aliases: emoji.aliases.filter(x => !aliases.includes(x)),
+ });
+ }
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiUpdated', {
+ emojis: await this.emojiEntityService.packDetailedMany(ids),
+ });
+ }
+
+ @bindThis
+ public async setCategoryBulk(ids: Emoji['id'][], category: string | null) {
+ await this.emojisRepository.update({
+ id: In(ids),
+ }, {
+ updatedAt: new Date(),
+ category: category,
+ });
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiUpdated', {
+ emojis: await this.emojiEntityService.packDetailedMany(ids),
+ });
+ }
+
+ @bindThis
+ public async delete(id: Emoji['id']) {
+ const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
+
+ await this.emojisRepository.delete(emoji.id);
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiDeleted', {
+ emojis: [await this.emojiEntityService.packDetailed(emoji)],
+ });
+ }
+
+ @bindThis
+ public async deleteBulk(ids: Emoji['id'][]) {
+ const emojis = await this.emojisRepository.findBy({
+ id: In(ids),
+ });
+
+ for (const emoji of emojis) {
+ await this.emojisRepository.delete(emoji.id);
+ }
+
+ this.localEmojisCache.refresh();
+
+ this.globalEventService.publishBroadcastStream('emojiDeleted', {
+ emojis: await this.emojiEntityService.packDetailedMany(emojis),
+ });
+ }
+
+ @bindThis
private normalizeHost(src: string | undefined, noteUserHost: string | null): string | null {
// クエリに使うホスト
let host = src === '.' ? null // .はローカルホスト (ここがマッチするのはリアクションのみ)
@@ -84,7 +235,7 @@ export class CustomEmojiService {
}
@bindThis
- private parseEmojiStr(emojiName: string, noteUserHost: string | null) {
+ public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
if (!match) return { name: null, host: null };
@@ -143,30 +294,6 @@ export class CustomEmojiService {
return res;
}
- @bindThis
- public aggregateNoteEmojis(notes: Note[]) {
- let emojis: { name: string | null; host: string | null; }[] = [];
- for (const note of notes) {
- emojis = emojis.concat(note.emojis
- .map(e => this.parseEmojiStr(e, note.userHost)));
- if (note.renote) {
- emojis = emojis.concat(note.renote.emojis
- .map(e => this.parseEmojiStr(e, note.renote!.userHost)));
- if (note.renote.user) {
- emojis = emojis.concat(note.renote.user.emojis
- .map(e => this.parseEmojiStr(e, note.renote!.userHost)));
- }
- }
- const customReactions = Object.keys(note.reactions).map(x => this.reactionService.decodeReaction(x)).filter(x => x.name != null) as typeof emojis;
- emojis = emojis.concat(customReactions);
- if (note.user) {
- emojis = emojis.concat(note.user.emojis
- .map(e => this.parseEmojiStr(e, note.userHost)));
- }
- }
- return emojis.filter(x => x.name != null && x.host != null) as { name: string; host: string; }[];
- }
-
/**
* 与えられた絵文字のリストをデータベースから取得し、キャッシュに追加します
*/