From 06855f769f1fe8c84fc3bbef615dac0a9fd2cf7b Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:58:11 +0900 Subject: refactor(sw): use fully typed locales (#14470) * refactor(sw): use fully typed locales * fix(backend): enumerate achievement notification type --- packages/sw/src/scripts/create-notification.ts | 50 +++++++++++++------------- 1 file changed, 24 insertions(+), 26 deletions(-) (limited to 'packages/sw/src/scripts/create-notification.ts') diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 7d28d8a694..02d9b07767 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -41,11 +41,10 @@ export async function createNotification { const i18n = await (swLang.i18n ?? swLang.fetchLocale()); - const { t } = i18n; switch (data.type) { /* case 'driveFileCreated': // TODO (Server Side) - return [t('_notification.fileUploaded'), { + return [i18n.ts._notification.fileUploaded, { body: body.name, icon: body.url, data @@ -58,7 +57,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif const account = await getAccountFromId(data.userId); if (!account) return null; const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token); - return [t('_notification.youWereFollowed'), { + return [i18n.ts._notification.youWereFollowed, { body: getUserName(data.body.user), icon: data.body.user.avatarUrl, badge: iconUrl('user-plus'), @@ -66,14 +65,14 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif actions: userDetail.isFollowing ? [] : [ { action: 'follow', - title: t('_notification._actions.followBack'), + title: i18n.ts._notification._actions.followBack, }, ], }]; } case 'mention': - return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotMention({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, badge: iconUrl('at'), @@ -81,13 +80,13 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ], }]; case 'reply': - return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotReply({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, badge: iconUrl('arrow-back-up'), @@ -95,13 +94,13 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ], }]; case 'renote': - return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youRenoted({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, badge: iconUrl('repeat'), @@ -115,7 +114,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; case 'quote': - return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), { + return [i18n.tsx._notification.youGotQuote({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, badge: iconUrl('quote'), @@ -123,19 +122,19 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif actions: [ { action: 'reply', - title: t('_notification._actions.reply'), + title: i18n.ts._notification._actions.reply, }, ...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [ { action: 'renote', - title: t('_notification._actions.renote'), + title: i18n.ts._notification._actions.renote, }, ] : []), ], }]; case 'note': - return [t('_notification.newNote') + ': ' + getUserName(data.body.user), { + return [i18n.ts._notification.newNote + ': ' + getUserName(data.body.user), { body: data.body.note.text ?? '', icon: data.body.user.avatarUrl, data, @@ -178,7 +177,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif } case 'receiveFollowRequest': - return [t('_notification.youReceivedFollowRequest'), { + return [i18n.ts._notification.youReceivedFollowRequest, { body: getUserName(data.body.user), icon: data.body.user.avatarUrl, badge: iconUrl('user-plus'), @@ -186,17 +185,17 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif actions: [ { action: 'accept', - title: t('accept'), + title: i18n.ts.accept, }, { action: 'reject', - title: t('reject'), + title: i18n.ts.reject, }, ], }]; case 'followRequestAccepted': - return [t('_notification.yourFollowRequestAccepted'), { + return [i18n.ts._notification.yourFollowRequestAccepted, { body: getUserName(data.body.user), icon: data.body.user.avatarUrl, badge: iconUrl('circle-check'), @@ -204,15 +203,15 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; case 'achievementEarned': - return [t('_notification.achievementEarned'), { - body: t(`_achievements._types._${data.body.achievement}.title`), + return [i18n.ts._notification.achievementEarned, { + body: i18n.ts._achievements._types[`_${data.body.achievement}`].title, badge: iconUrl('medal'), data, tag: `achievement:${data.body.achievement}`, }]; case 'pollEnded': - return [t('_notification.pollEnded'), { + return [i18n.ts._notification.pollEnded, { body: data.body.note.text ?? '', badge: iconUrl('chart-arrows'), data, @@ -226,8 +225,8 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif }]; case 'test': - return [t('_notification.testNotification'), { - body: t('_notification.notificationWillBeDisplayedLikeThis'), + return [i18n.ts._notification.testNotification, { + body: i18n.ts._notification.notificationWillBeDisplayedLikeThis, badge: iconUrl('bell'), data, }]; @@ -236,7 +235,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif return null; } case 'unreadAntennaNote': - return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), { + return [i18n.tsx._notification.unreadAntennaNote({ name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, icon: data.body.note.user.avatarUrl, badge: iconUrl('antenna'), @@ -252,7 +251,6 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif export async function createEmptyNotification(): Promise { return new Promise(async res => { const i18n = await (swLang.i18n ?? swLang.fetchLocale()); - const { t } = i18n; await globalThis.registration.showNotification( (new URL(origin)).host, @@ -264,11 +262,11 @@ export async function createEmptyNotification(): Promise { actions: [ { action: 'markAllAsRead', - title: t('markAllAsRead'), + title: i18n.ts.markAllAsRead, }, { action: 'settings', - title: t('notificationSettings'), + title: i18n.ts.notificationSettings, }, ], data: {}, -- cgit v1.2.3-freya From 8be624aa4441dbbc3f911453d22b70dee8a685b5 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:53:04 +0900 Subject: refactor(sw): fix type errors (#14478) * style(sw): lint fixes * refactor(sw): fix type errors * chore(sw): disable `noImplicitAny` * ci(sw): enable typecheck ci * ci(sw): build `misskey-js` before typecheck --- .github/workflows/lint.yml | 3 ++- packages/sw/build.js | 4 ++-- packages/sw/src/scripts/create-notification.ts | 20 ++++++++++---------- packages/sw/src/scripts/get-account-from-id.ts | 5 +++-- packages/sw/src/scripts/operations.ts | 15 +++++++++++---- packages/sw/src/sw.ts | 4 ++-- packages/sw/tsconfig.json | 1 + 7 files changed, 31 insertions(+), 21 deletions(-) (limited to 'packages/sw/src/scripts/create-notification.ts') diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c21fc95123..1f13f4fa2f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -78,6 +78,7 @@ jobs: matrix: workspace: - backend + - sw - misskey-js steps: - uses: actions/checkout@v4.1.1 @@ -92,7 +93,7 @@ jobs: - run: corepack enable - run: pnpm i --frozen-lockfile - run: pnpm --filter misskey-js run build - if: ${{ matrix.workspace == 'backend' }} + if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }} - run: pnpm --filter misskey-reversi run build if: ${{ matrix.workspace == 'backend' }} - run: pnpm --filter ${{ matrix.workspace }} run typecheck diff --git a/packages/sw/build.js b/packages/sw/build.js index 9522d061e0..a9c2e428c0 100644 --- a/packages/sw/build.js +++ b/packages/sw/build.js @@ -8,10 +8,10 @@ import { fileURLToPath } from 'node:url'; import * as esbuild from 'esbuild'; import locales from '../../locales/index.js'; -import meta from '../../package.json' with { type: "json" }; +import meta from '../../package.json' with { type: 'json' }; const watch = process.argv[2]?.includes('watch'); -const __dirname = fileURLToPath(new URL('.', import.meta.url)) +const __dirname = fileURLToPath(new URL('.', import.meta.url)); console.log('Starting SW building...'); diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 02d9b07767..3c37657958 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -59,7 +59,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token); return [i18n.ts._notification.youWereFollowed, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('user-plus'), data, actions: userDetail.isFollowing ? [] : [ @@ -74,7 +74,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'mention': return [i18n.tsx._notification.youGotMention({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('at'), data, actions: [ @@ -88,7 +88,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'reply': return [i18n.tsx._notification.youGotReply({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('arrow-back-up'), data, actions: [ @@ -102,7 +102,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'renote': return [i18n.tsx._notification.youRenoted({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('repeat'), data, actions: [ @@ -116,7 +116,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'quote': return [i18n.tsx._notification.youGotQuote({ name: getUserName(data.body.user) }), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('quote'), data, actions: [ @@ -136,7 +136,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'note': return [i18n.ts._notification.newNote + ': ' + getUserName(data.body.user), { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, data, }]; @@ -163,7 +163,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif const tag = `reaction:${data.body.note.id}`; return [`${reaction} ${getUserName(data.body.user)}`, { body: data.body.note.text ?? '', - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, tag, badge, data, @@ -179,7 +179,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'receiveFollowRequest': return [i18n.ts._notification.youReceivedFollowRequest, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('user-plus'), data, actions: [ @@ -197,7 +197,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'followRequestAccepted': return [i18n.ts._notification.yourFollowRequestAccepted, { body: getUserName(data.body.user), - icon: data.body.user.avatarUrl, + icon: data.body.user.avatarUrl ?? undefined, badge: iconUrl('circle-check'), data, }]; @@ -237,7 +237,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif case 'unreadAntennaNote': return [i18n.tsx._notification.unreadAntennaNote({ name: data.body.antenna.name }), { body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`, - icon: data.body.note.user.avatarUrl, + icon: data.body.note.user.avatarUrl ?? undefined, badge: iconUrl('antenna'), tag: `antenna:${data.body.antenna.id}`, data, diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts index 19bfe052ee..157dbd005e 100644 --- a/packages/sw/src/scripts/get-account-from-id.ts +++ b/packages/sw/src/scripts/get-account-from-id.ts @@ -4,9 +4,10 @@ */ import { get } from 'idb-keyval'; +import * as Misskey from 'misskey-js'; -export async function getAccountFromId(id: string): Promise<{ token: string; id: string } | void> { - const accounts = await get<{ token: string; id: string }[]>('accounts'); +export async function getAccountFromId(id: string): Promise | undefined> { + const accounts = await get[]>('accounts'); if (!accounts) { console.log('Accounts are not recorded'); return; diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts index 24eea06231..8862c6faa5 100644 --- a/packages/sw/src/scripts/operations.ts +++ b/packages/sw/src/scripts/operations.ts @@ -14,15 +14,22 @@ import { getUrlWithLoginId } from '@/scripts/login-id.js'; export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise => fetch(...args) }); -export async function api(endpoint: E, userId?: string, options?: O): Promise>> { - let account: { token: string; id: string } | void = undefined; +export async function api< + E extends keyof Misskey.Endpoints, + P extends Misskey.Endpoints[E]['req'] +>(endpoint: E, userId?: string, params?: P): Promise | undefined> { + let account: Pick | undefined; if (userId) { account = await getAccountFromId(userId); if (!account) return; } - return cli.request(endpoint, options, account?.token); + return (cli.request as ( + endpoint: E, + params: P, + credential?: string | null, + ) => Promise>)(endpoint, params, account?.token); } // mark-all-as-read送出を1秒間隔に制限する @@ -33,7 +40,7 @@ export function sendMarkAllAsRead(userId: string): Promise { setTimeout(() => { readBlockingStatus.set(userId, false); - api('notifications/mark-all-as-read', userId).then(resolve, resolve); + (api('notifications/mark-all-as-read', userId) as Promise).then(resolve, resolve); }, 1000); }); } diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts index 7a0010992e..2d39d23ec7 100644 --- a/packages/sw/src/sw.ts +++ b/packages/sw/src/sw.ts @@ -160,8 +160,8 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv case 'markAllAsRead': await globalThis.registration.getNotifications() .then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close())); - await get('accounts').then(accounts => { - return Promise.all(accounts.map(async account => { + await get[]>('accounts').then(accounts => { + return Promise.all((accounts ?? []).map(async account => { await swos.sendMarkAllAsRead(account.id); })); }); diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json index 50d4aae19d..f3f3543013 100644 --- a/packages/sw/tsconfig.json +++ b/packages/sw/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowJs": true, "noEmitOnError": false, + "noImplicitAny": false, "noImplicitReturns": true, "noUnusedParameters": false, "noUnusedLocals": true, -- cgit v1.2.3-freya From d8a2eeb7ed44a4e25426ca9b7c6c85b8c62d3106 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:15:03 +0900 Subject: feat: エクスポート完了時に通知を発行するように (#14484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: エクスポート完了時に通知を発行するように * Update Changelog * entitity -> entity * fix: ペイロードを含むように * fix icon * exportableEntities -> userExportableEntities --- CHANGELOG.md | 1 + locales/index.d.ts | 16 ++++++++++++ locales/ja-JP.yml | 4 +++ .../src/core/entities/NotificationEntityService.ts | 4 +++ packages/backend/src/models/Notification.ts | 8 ++++++ .../backend/src/models/json-schema/notification.ts | 22 +++++++++++++++- .../processors/ExportAntennasProcessorService.ts | 7 ++++++ .../processors/ExportBlockingProcessorService.ts | 7 ++++++ .../processors/ExportClipsProcessorService.ts | 7 ++++++ .../ExportCustomEmojisProcessorService.ts | 8 ++++++ .../processors/ExportFavoritesProcessorService.ts | 7 ++++++ .../processors/ExportFollowingProcessorService.ts | 7 ++++++ .../processors/ExportMutingProcessorService.ts | 7 ++++++ .../processors/ExportNotesProcessorService.ts | 7 ++++++ .../processors/ExportUserListsProcessorService.ts | 7 ++++++ packages/backend/src/types.ts | 16 ++++++++++++ packages/frontend-shared/js/const.ts | 2 ++ .../frontend/src/components/MkNotification.vue | 29 +++++++++++++++++++++- .../frontend/src/pages/settings/notifications.vue | 2 +- packages/misskey-js/src/autogen/types.ts | 19 +++++++++++--- packages/sw/src/scripts/create-notification.ts | 19 ++++++++++++++ 21 files changed, 199 insertions(+), 7 deletions(-) (limited to 'packages/sw/src/scripts/create-notification.ts') diff --git a/CHANGELOG.md b/CHANGELOG.md index 086ff8dcc8..a1d2e950b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445) - Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680) +- Feat: データエクスポートが完了した際に通知を発行するように - Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように - Enhance: 依存関係の更新 - Enhance: l10nの更新 diff --git a/locales/index.d.ts b/locales/index.d.ts index 1250aa4f4d..a52ee8d808 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1352,6 +1352,10 @@ export interface Locale extends ILocale { * ファイルを追加 */ "addFile": string; + /** + * ファイルを表示 + */ + "showFile": string; /** * ドライブは空です */ @@ -9253,6 +9257,10 @@ export interface Locale extends ILocale { * 通知の履歴をリセットする */ "flushNotification": string; + /** + * {x}のエクスポートが完了しました + */ + "exportOfXCompleted": ParameterizedString<"x">; "_types": { /** * すべて @@ -9306,6 +9314,14 @@ export interface Locale extends ILocale { * 実績の獲得 */ "achievementEarned": string; + /** + * エクスポートが完了した + */ + "exportCompleted": string; + /** + * 通知のテスト + */ + "test": string; /** * 連携アプリからの通知 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6c5df3e658..75c895a230 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -334,6 +334,7 @@ renameFolder: "フォルダー名を変更" deleteFolder: "フォルダーを削除" folder: "フォルダー" addFile: "ファイルを追加" +showFile: "ファイルを表示" emptyDrive: "ドライブは空です" emptyFolder: "フォルダーは空です" unableToDelete: "削除できません" @@ -2443,6 +2444,7 @@ _notification: renotedBySomeUsers: "{n}人がリノートしました" followedBySomeUsers: "{n}人にフォローされました" flushNotification: "通知の履歴をリセットする" + exportOfXCompleted: "{x}のエクスポートが完了しました" _types: all: "すべて" @@ -2458,6 +2460,8 @@ _notification: followRequestAccepted: "フォローが受理された" roleAssigned: "ロールが付与された" achievementEarned: "実績の獲得" + exportCompleted: "エクスポートが完了した" + test: "通知のテスト" app: "連携アプリからの通知" _actions: diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index f393513510..1b61a6195d 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -162,6 +162,10 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'achievementEarned' ? { achievement: notification.achievement, } : {}), + ...(notification.type === 'exportCompleted' ? { + exportedEntity: notification.exportedEntity, + fileId: notification.fileId, + } : {}), ...(notification.type === 'app' ? { body: notification.customBody, header: notification.customHeader, diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 87d8c16cb3..2c5b75f577 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -7,6 +7,8 @@ import { MiUser } from './User.js'; import { MiNote } from './Note.js'; import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; +import { MiDriveFile } from './DriveFile.js'; +import { userExportableEntities } from '@/types.js'; export type MiNotification = { type: 'note'; @@ -77,6 +79,12 @@ export type MiNotification = { id: string; createdAt: string; achievement: string; +} | { + type: 'exportCompleted'; + id: string; + createdAt: string; + exportedEntity: typeof userExportableEntities[number]; + fileId: MiDriveFile['id']; } | { type: 'app'; id: string; diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index b05ec8b762..bbec2e397f 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -4,7 +4,7 @@ */ import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; -import { notificationTypes } from '@/types.js'; +import { notificationTypes, userExportableEntities } from '@/types.js'; const baseSchema = { type: 'object', @@ -298,6 +298,26 @@ export const packedNotificationSchema = { enum: ACHIEVEMENT_TYPES, }, }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, + enum: ['exportCompleted'], + }, + exportedEntity: { + type: 'string', + optional: false, nullable: false, + enum: userExportableEntities, + }, + fileId: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, }, { type: 'object', properties: { diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 88c4ea29c0..b3111865ad 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { DBExportAntennasData } from '../types.js'; import type * as Bull from 'bullmq'; @@ -35,6 +36,7 @@ export class ExportAntennasProcessorService { private driveService: DriveService, private utilityService: UtilityService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas'); } @@ -95,6 +97,11 @@ export class ExportAntennasProcessorService { const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ('Exported to: ' + driveFile.id); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'antenna', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index 6ec3c18786..ecc439db69 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -30,6 +31,7 @@ export class ExportBlockingProcessorService { private blockingsRepository: BlockingsRepository, private utilityService: UtilityService, + private notificationService: NotificationService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, ) { @@ -109,6 +111,11 @@ export class ExportBlockingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'blocking', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts index 01eab26e96..583ddbb745 100644 --- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -43,6 +44,7 @@ export class ExportClipsProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); } @@ -79,6 +81,11 @@ export class ExportClipsProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'clip', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index e4eb4791bd..e237cd4975 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -16,6 +16,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { DownloadService } from '@/core/DownloadService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -37,6 +38,7 @@ export class ExportCustomEmojisProcessorService { private driveService: DriveService, private downloadService: DownloadService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis'); } @@ -134,6 +136,12 @@ export class ExportCustomEmojisProcessorService { const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'customEmoji', + fileId: driveFile.id, + }); + cleanup(); archiveCleanup(); resolve(); diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index 7bb626dd31..b81feece01 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -16,6 +16,7 @@ import type { MiPoll } from '@/models/Poll.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbJobDataWithUser } from '../types.js'; @@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService { private driveService: DriveService, private queueLoggerService: QueueLoggerService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites'); } @@ -123,6 +125,11 @@ export class ExportFavoritesProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'favorite', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 1cc80e66d7..903f962515 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import type { MiFollowing } from '@/models/Following.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -36,6 +37,7 @@ export class ExportFollowingProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-following'); } @@ -113,6 +115,11 @@ export class ExportFollowingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'following', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 243b74f2c2..f9867ade29 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -32,6 +33,7 @@ export class ExportMutingProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-muting'); } @@ -110,6 +112,11 @@ export class ExportMutingProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'muting', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index c7611012d7..9e2b678219 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { Packed } from '@/misc/json-schema.js'; import { IdService } from '@/core/IdService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { JsonArrayStream } from '@/misc/JsonArrayStream.js'; import { FileWriterStream } from '@/misc/FileWriterStream.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; @@ -112,6 +113,7 @@ export class ExportNotesProcessorService { private queueLoggerService: QueueLoggerService, private driveFileEntityService: DriveFileEntityService, private idService: IdService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-notes'); } @@ -150,6 +152,11 @@ export class ExportNotesProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'note', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index ee87cff5d3..c483d79854 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -13,6 +13,7 @@ import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; import { UtilityService } from '@/core/UtilityService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -35,6 +36,7 @@ export class ExportUserListsProcessorService { private utilityService: UtilityService, private driveService: DriveService, private queueLoggerService: QueueLoggerService, + private notificationService: NotificationService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists'); } @@ -89,6 +91,11 @@ export class ExportUserListsProcessorService { const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' }); this.logger.succ(`Exported to: ${driveFile.id}`); + + this.notificationService.createNotification(user.id, 'exportCompleted', { + exportedEntity: 'userList', + fileId: driveFile.id, + }); } finally { cleanup(); } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index e852cf5ae2..5854c6b392 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -16,6 +16,7 @@ * followRequestAccepted - 自分の送ったフォローリクエストが承認された * roleAssigned - ロールが付与された * achievementEarned - 実績を獲得 + * exportCompleted - エクスポートが完了 * app - アプリ通知 * test - テスト通知(サーバー側) */ @@ -32,6 +33,7 @@ export const notificationTypes = [ 'followRequestAccepted', 'roleAssigned', 'achievementEarned', + 'exportCompleted', 'app', 'test', ] as const; @@ -51,6 +53,20 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const followingVisibilities = ['public', 'followers', 'private'] as const; export const followersVisibilities = ['public', 'followers', 'private'] as const; +/** + * ユーザーがエクスポートできるものの種類 + * + * (主にエクスポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない) + */ +export const userExportableEntities = ['antenna', 'blocking', 'clip', 'customEmoji', 'favorite', 'following', 'muting', 'note', 'userList'] as const; + +/** + * ユーザーがインポートできるものの種類 + * + * (主にインポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない) + */ +export const userImportableEntities = ['antenna', 'blocking', 'customEmoji', 'following', 'muting', 'userList'] as const; + export const moderationLogTypes = [ 'updateServerSettings', 'suspend', diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index b62a69ba24..aec4a4a58b 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -67,6 +67,8 @@ export const notificationTypes = [ 'followRequestAccepted', 'roleAssigned', 'achievementEarned', + 'exportCompleted', + 'test', 'app', ] as const; export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 738cba2134..3989c61776 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
@@ -37,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only +