From c34d3234d567b79b50ea245a465e393ad3caefeb Mon Sep 17 00:00:00 2001 From: samunohito <46447427+samunohito@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:21:47 +0900 Subject: [PATCH] fix api call --- .../backend/src/core/CustomEmojiService.ts | 170 +----------------- .../admin/custom-emojis-grid.local.list.vue | 18 +- .../custom-emojis-grid.local.register.vue | 39 ++-- .../pages/admin/custom-emojis-grid.remote.vue | 46 ++--- 4 files changed, 45 insertions(+), 228 deletions(-) diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 34206cb189..ec09e972e8 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { setImmediate } from 'node:timers/promises'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { In, IsNull } from 'typeorm'; import * as Redis from 'ioredis'; @@ -77,8 +76,10 @@ export class CustomEmojiService implements OnApplicationShutdown { constructor( @Inject(DI.redis) private redisClient: Redis.Redis, + @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + private utilityService: UtilityService, private idService: IdService, private emojiEntityService: EmojiEntityService, @@ -530,173 +531,6 @@ export class CustomEmojiService implements OnApplicationShutdown { }; } - @bindThis - public async addBulk( - params: { - driveFile: MiDriveFile; - name: string; - category: string | null; - aliases: string[]; - host: string | null; - license: string | null; - isSensitive: boolean; - localOnly: boolean; - roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][]; - }[], - moderator?: MiUser, - ): Promise { - const emojis = await this.emojisRepository - .insert( - params.map(it => ({ - id: this.idService.gen(), - updatedAt: new Date(), - name: it.name, - category: it.category, - host: it.host, - aliases: it.aliases, - originalUrl: it.driveFile.url, - publicUrl: it.driveFile.webpublicUrl ?? it.driveFile.url, - type: it.driveFile.webpublicType ?? it.driveFile.type, - license: it.license, - isSensitive: it.isSensitive, - localOnly: it.localOnly, - roleIdsThatCanBeUsedThisEmojiAsReaction: it.roleIdsThatCanBeUsedThisEmojiAsReaction, - })), - ) - .then(x => this.emojisRepository.createQueryBuilder('emoji') - .where({ id: In(x.identifiers) }) - .getMany(), - ); - - // 以降は絵文字登録による副作用なのでリクエストから切り離して実行 - - // noinspection ES6MissingAwait - setImmediate(async () => { - const localEmojis = emojis.filter(it => it.host == null); - if (localEmojis.length > 0) { - await this.localEmojisCache.refresh(); - - const packedEmojis = await this.emojiEntityService.packDetailedMany(localEmojis); - for (const emoji of packedEmojis) { - this.globalEventService.publishBroadcastStream('emojiAdded', { emoji }); - } - - if (moderator) { - for (const emoji of localEmojis) { - await this.moderationLogService.log(moderator, 'addCustomEmoji', { - emojiId: emoji.id, - emoji: emoji, - }); - } - } - } - }); - - return emojis; - } - - @bindThis - public async updateBulk( - params: { - id: MiEmoji['id']; - driveFile?: MiDriveFile; - name?: string; - category?: string | null; - aliases?: string[]; - license?: string | null; - isSensitive?: boolean; - localOnly?: boolean; - roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; - }[], - moderator?: MiUser, - ): Promise { - const ids = params.map(it => it.id); - - // IDに対応するものと、新しく設定しようとしている名前と同じ名前を持つレコードをそれぞれ取得する - const [storedEmojis, sameNameEmojis] = await Promise.all([ - this.emojisRepository.createQueryBuilder('emoji') - .whereInIds(ids) - .getMany() - .then(emojis => new Map(emojis.map(it => [it.id, it]))), - this.emojisRepository.createQueryBuilder('emoji') - .where('emoji.name IN (:...names) AND emoji.host IS NULL', { names: params.map(it => it.name) }) - .getMany(), - ]); - - // 新しく設定しようとしている名前と同じ名前を持つ別レコードがある場合、重複とみなしてエラーとする - const alreadyExists = Array.of(); - for (const sameNameEmoji of sameNameEmojis) { - const emoji = storedEmojis.get(sameNameEmoji.id); - if (emoji != null && emoji.id !== sameNameEmoji.id) { - alreadyExists.push(sameNameEmoji.name); - } - } - if (alreadyExists.length > 0) { - throw new Error(`name already exists: ${alreadyExists.join(', ')}`); - } - - for (const emoji of params) { - await this.emojisRepository.update(emoji.id, { - updatedAt: new Date(), - name: emoji.name, - category: emoji.category, - aliases: emoji.aliases, - license: emoji.license, - isSensitive: emoji.isSensitive, - localOnly: emoji.localOnly, - originalUrl: emoji.driveFile != null ? emoji.driveFile.url : undefined, - publicUrl: emoji.driveFile != null ? (emoji.driveFile.webpublicUrl ?? emoji.driveFile.url) : undefined, - type: emoji.driveFile != null ? (emoji.driveFile.webpublicType ?? emoji.driveFile.type) : undefined, - roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined, - }); - } - - // 以降は絵文字更新による副作用なのでリクエストから切り離して実行 - - // noinspection ES6MissingAwait - setImmediate(async () => { - await this.localEmojisCache.refresh(); - - // 名前が変わっていないものはそのまま更新としてイベント発信 - const updateEmojis = params.filter(it => storedEmojis.get(it.id)?.name === it.name); - if (updateEmojis.length > 0) { - const packedList = await this.emojiEntityService.packDetailedMany(updateEmojis); - this.globalEventService.publishBroadcastStream('emojiUpdated', { - emojis: packedList, - }); - } - - // 名前が変わったものは削除・追加としてイベント発信 - const nameChangeEmojis = params.filter(it => storedEmojis.get(it.id)?.name !== it.name); - if (nameChangeEmojis.length > 0) { - const packedList = await this.emojiEntityService.packDetailedMany(nameChangeEmojis); - this.globalEventService.publishBroadcastStream('emojiDeleted', { - emojis: packedList, - }); - - for (const packed of packedList) { - this.globalEventService.publishBroadcastStream('emojiAdded', { - emoji: packed, - }); - } - } - - if (moderator) { - const updatedEmojis = await this.emojisRepository.createQueryBuilder('emoji') - .whereInIds(storedEmojis.keys()) - .getMany() - .then(it => new Map(it.map(it => [it.id, it]))); - for (const emoji of storedEmojis.values()) { - await this.moderationLogService.log(moderator, 'updateCustomEmoji', { - emojiId: emoji.id, - before: emoji, - after: updatedEmojis.get(emoji.id), - }); - } - } - }); - } - @bindThis public dispose(): void { this.cache.dispose(); diff --git a/packages/frontend/src/pages/admin/custom-emojis-grid.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-grid.local.list.vue index 1b4daf8e4a..52b57fee53 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-grid.local.list.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-grid.local.list.vue @@ -109,12 +109,12 @@ async function onUpdateClicked() { return; } - async function action() { + const action = () => { const emptyStrToNull = (value: string) => value === '' ? null : value; const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); - for (const item of updatedItems) { - await misskeyApi('admin/emoji/update', { + return updatedItems.map(item => + misskeyApi('admin/emoji/update', { id: item.id!, name: item.name, category: emptyStrToNull(item.category), @@ -123,15 +123,11 @@ async function onUpdateClicked() { isSensitive: item.isSensitive, localOnly: item.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), - }); - } - } + }), + ); + }; - await os.promiseDialog( - action(), - () => {}, - () => {}, - ); + await os.promiseDialog(Promise.all(action())); } async function onDeleteClicked() { diff --git a/packages/frontend/src/pages/admin/custom-emojis-grid.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-grid.local.register.vue index 4305dc84fd..7b069ac027 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-grid.local.register.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-grid.local.register.vue @@ -153,32 +153,29 @@ async function onRegistryClicked() { } const items = new Map(gridItems.value.map(it => [`${it.fileId}|${it.name}`, it])); - const upload = async (): Promise => { + const upload = (): Promise[] => { const emptyStrToNull = (value: string) => value === '' ? null : value; const emptyStrToEmptyArray = (value: string) => value === '' ? [] : value.split(',').map(it => it.trim()); - const result = Array.of(); - for (const [key, item] of [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT)) { - try { - await misskeyApi('admin/emoji/add', { - name: item.name, - category: emptyStrToNull(item.category), - aliases: emptyStrToEmptyArray(item.aliases), - license: emptyStrToNull(item.license), - isSensitive: item.isSensitive, - localOnly: item.localOnly, - roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), - fileId: item.fileId!, - }); - result.push({ key, item, success: true, err: undefined }); - } catch (err: any) { - result.push({ key, item, success: false, err }); - } - } - return result; + return [...items.entries()].slice(0, MAXIMUM_EMOJI_COUNT) + .map(([key, item]) => + misskeyApi( + 'admin/emoji/add', { + name: item.name, + category: emptyStrToNull(item.category), + aliases: emptyStrToEmptyArray(item.aliases), + license: emptyStrToNull(item.license), + isSensitive: item.isSensitive, + localOnly: item.localOnly, + roleIdsThatCanBeUsedThisEmojiAsReaction: emptyStrToEmptyArray(item.roleIdsThatCanBeUsedThisEmojiAsReaction), + fileId: item.fileId!, + }) + .then((): UploadResult => ({ key, item, success: true, err: undefined })) + .catch((err: any): UploadResult => ({ key, item, success: false, err })), + ); }; - const result = await os.promiseDialog(upload()); + const result = await os.promiseDialog(Promise.all(upload())); const failedItems = result.filter(it => !it.success); if (failedItems.length > 0) { diff --git a/packages/frontend/src/pages/admin/custom-emojis-grid.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-grid.remote.vue index ed6a499779..8ddd9cd42c 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-grid.remote.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-grid.remote.vue @@ -61,7 +61,7 @@ const columnSettings: ColumnSetting[] = [ { bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' }, ]; -const customEmojis = ref([]); +const customEmojis = ref([]); const gridItems = ref([]); const query = ref(''); const host = ref(''); @@ -144,15 +144,13 @@ function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) } async function importEmojis(targets: GridItem[]) { - async function action() { - for (const target of targets) { - await misskeyApi('admin/emoji/copy', { + const action = () => { + return targets.map(target => + misskeyApi('admin/emoji/copy', { emojiId: target.id!, - }); - } - - await refreshCustomEmojis(query.value, host.value); - } + }), + ); + }; const confirm = await os.confirm({ type: 'info', @@ -161,32 +159,24 @@ async function importEmojis(targets: GridItem[]) { }); if (!confirm.canceled) { - await os.promiseDialog( - action(), - () => { - }, - () => { - }, - ); + await os.promiseDialog(Promise.all(action())); + await refreshCustomEmojis(); } } async function refreshCustomEmojis(query?: string, host?: string, sinceId?: string, untilId?: string) { - const emojis = await misskeyApi('admin/emoji/list-remote', { + const emojis = await misskeyApi('admin/emoji/v2/list', { limit: 100, - query: query?.length ? query : undefined, - host: host?.length ? host : undefined, - sinceId, - untilId, + query: { + name: query, + host: host, + sinceId: sinceId, + untilId: untilId, + hostType: 'remote', + }, }); - if (sinceId) { - // 通常はID降順だが、sinceIdを設定すると昇順での並び替えとなるので、逆順にする必要がある - emojis.reverse(); - } - - customEmojis.value = emojis; - console.log(customEmojis.value); + customEmojis.value = emojis.emojis; gridItems.value = customEmojis.value.map(it => fromEmojiDetailedAdmin(it)); }