From fc56b1269084f4556e2a03f6dd3ffd893a5e0063 Mon Sep 17 00:00:00 2001 From: tamaina Date: Fri, 20 Aug 2021 19:38:16 +0900 Subject: refactor: localStorageのaccountsはindexedDBで保持するように (#7609) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * accountsストアはindexedDBで保持するように * fix lint * fix indexeddb available detection * remove debugging code * fix lint * resolve https://github.com/misskey-dev/misskey/pull/7609/files/ba756204b77ce6e1189b8443e9641f2d02119621#diff-f565878e8202f0037b830c780b7c0932dc1bb5fd3d05ede14d72d10efbc3740c Firefoxでの動作を改善 * fix lint * fix lint * add changelog --- src/client/account.ts | 57 +++++++++++++++++++++++-------- src/client/init.ts | 9 +++++ src/client/pages/settings/accounts.vue | 10 +++--- src/client/scripts/get-account-from-id.ts | 7 ++++ src/client/scripts/idb-proxy.ts | 38 +++++++++++++++++++++ src/client/ui/_common_/sidebar.vue | 6 ++-- src/client/ui/default.header.vue | 6 ++-- src/client/ui/default.sidebar.vue | 6 ++-- 8 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 src/client/scripts/get-account-from-id.ts create mode 100644 src/client/scripts/idb-proxy.ts (limited to 'src/client') diff --git a/src/client/account.ts b/src/client/account.ts index 2b860b3ddf..cf52c4d828 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -1,7 +1,8 @@ +import { get, set } from '@client/scripts/idb-proxy'; import { reactive } from 'vue'; import { apiUrl } from '@client/config'; import { waiting } from '@client/os'; -import { unisonReload } from '@client/scripts/unison-reload'; +import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; // TODO: 他のタブと永続化されたstateを同期 @@ -17,22 +18,43 @@ const data = localStorage.getItem('account'); // TODO: 外部からはreadonlyに export const $i = data ? reactive(JSON.parse(data) as Account) : null; -export function signout() { +export async function signout() { + waiting(); localStorage.removeItem('account'); + + //#region Remove account + const accounts = await getAccounts(); + accounts.splice(accounts.findIndex(x => x.id === $i.id), 1); + set('accounts', accounts); + //#endregion + + //#region Remove push notification registration + const registration = await navigator.serviceWorker.ready; + const push = await registration.pushManager.getSubscription(); + if (!push) return; + await fetch(`${apiUrl}/sw/unregister`, { + method: 'POST', + body: JSON.stringify({ + i: $i.token, + endpoint: push.endpoint, + }), + }); + //#endregion + document.cookie = `igi=; path=/`; - location.href = '/'; + + if (accounts.length > 0) login(accounts[0].token); + else unisonReload(); } -export function getAccounts() { - const accountsData = localStorage.getItem('accounts'); - const accounts: { id: Account['id'], token: Account['token'] }[] = accountsData ? JSON.parse(accountsData) : []; - return accounts; +export async function getAccounts(): Promise<{ id: Account['id'], token: Account['token'] }[]> { + return (await get('accounts')) || []; } -export function addAccount(id: Account['id'], token: Account['token']) { - const accounts = getAccounts(); +export async function addAccount(id: Account['id'], token: Account['token']) { + const accounts = await getAccounts(); if (!accounts.some(x => x.id === id)) { - localStorage.setItem('accounts', JSON.stringify(accounts.concat([{ id, token }]))); + await set('accounts', accounts.concat([{ id, token }])); } } @@ -47,7 +69,7 @@ function fetchAccount(token): Promise { }) .then(res => { // When failed to authenticate user - if (res.status >= 400 && res.status < 500) { + if (res.status !== 200 && res.status < 500) { return signout(); } @@ -69,15 +91,22 @@ export function updateAccount(data) { } export function refreshAccount() { - fetchAccount($i.token).then(updateAccount); + return fetchAccount($i.token).then(updateAccount); } -export async function login(token: Account['token']) { +export async function login(token: Account['token'], redirect?: string) { waiting(); if (_DEV_) console.log('logging as token ', token); const me = await fetchAccount(token); localStorage.setItem('account', JSON.stringify(me)); - addAccount(me.id, token); + await addAccount(me.id, token); + + if (redirect) { + reloadChannel.postMessage('reload'); + location.href = redirect; + return; + } + unisonReload(); } diff --git a/src/client/init.ts b/src/client/init.ts index 1580ef3e08..0313af4374 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -4,6 +4,15 @@ import '@client/style.scss'; +//#region account indexedDB migration +import { set } from '@client/scripts/idb-proxy'; + +if (localStorage.getItem('accounts') != null) { + set('accounts', JSON.parse(localStorage.getItem('accounts'))); + localStorage.removeItem('accounts'); +} +//#endregion + import * as Sentry from '@sentry/browser'; import { Integrations } from '@sentry/tracing'; import { computed, createApp, watch, markRaw } from 'vue'; diff --git a/src/client/pages/settings/accounts.vue b/src/client/pages/settings/accounts.vue index 53e28bdf6f..ca6f53776a 100644 --- a/src/client/pages/settings/accounts.vue +++ b/src/client/pages/settings/accounts.vue @@ -48,10 +48,10 @@ export default defineComponent({ title: this.$ts.accounts, icon: 'fas fa-users', }, - storedAccounts: getAccounts().filter(x => x.id !== this.$i.id), + storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), accounts: null, - init: () => os.api('users/show', { - userIds: this.storedAccounts.map(x => x.id) + init: async () => os.api('users/show', { + userIds: (await this.storedAccounts).map(x => x.id) }).then(accounts => { this.accounts = accounts; }), @@ -104,8 +104,8 @@ export default defineComponent({ }, 'closed'); }, - switchAccount(account: any) { - const storedAccounts = getAccounts(); + async switchAccount(account: any) { + const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; this.switchAccountWithToken(token); }, diff --git a/src/client/scripts/get-account-from-id.ts b/src/client/scripts/get-account-from-id.ts new file mode 100644 index 0000000000..065b41118c --- /dev/null +++ b/src/client/scripts/get-account-from-id.ts @@ -0,0 +1,7 @@ +import { get } from '@client/scripts/idb-proxy'; + +export async function getAccountFromId(id: string) { + const accounts = await get('accounts') as { token: string; id: string; }[]; + if (!accounts) console.log('Accounts are not recorded'); + return accounts.find(e => e.id === id); +} diff --git a/src/client/scripts/idb-proxy.ts b/src/client/scripts/idb-proxy.ts new file mode 100644 index 0000000000..21c4dcff65 --- /dev/null +++ b/src/client/scripts/idb-proxy.ts @@ -0,0 +1,38 @@ +// FirefoxのプライベートモードなどではindexedDBが使用不可能なので、 +// indexedDBが使えない環境ではlocalStorageを使う +import { + get as iget, + set as iset, + del as idel, + createStore, +} from 'idb-keyval'; + +const fallbackName = (key: string) => `idbfallback::${key}`; + +let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; + +if (idbAvailable) { + try { + await createStore('keyval-store', 'keyval'); + } catch (e) { + console.error('idb open error', e); + idbAvailable = false; + } +} + +if (!idbAvailable) console.error('indexedDB is unavailable. It will use localStorage.'); + +export async function get(key: string) { + if (idbAvailable) return iget(key); + return JSON.parse(localStorage.getItem(fallbackName(key))); +} + +export async function set(key: string, val: any) { + if (idbAvailable) return iset(key, val); + return localStorage.setItem(fallbackName(key), JSON.stringify(val)); +} + +export async function del(key: string) { + if (idbAvailable) return idel(key); + return localStorage.removeItem(fallbackName(key)); +} diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index b7b88faeac..333d0ac392 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -135,7 +135,7 @@ export default defineComponent({ }, async openAccountMenu(ev) { - const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); + const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); const accountItemPromises = storedAccounts.map(a => new Promise(res => { @@ -195,8 +195,8 @@ export default defineComponent({ }, 'closed'); }, - switchAccount(account: any) { - const storedAccounts = getAccounts(); + async switchAccount(account: any) { + const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; this.switchAccountWithToken(token); }, diff --git a/src/client/ui/default.header.vue b/src/client/ui/default.header.vue index df2e99f13a..6fbdd625c7 100644 --- a/src/client/ui/default.header.vue +++ b/src/client/ui/default.header.vue @@ -101,7 +101,7 @@ export default defineComponent({ }, async openAccountMenu(ev) { - const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); + const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); const accountItemPromises = storedAccounts.map(a => new Promise(res => { @@ -161,8 +161,8 @@ export default defineComponent({ }, 'closed'); }, - switchAccount(account: any) { - const storedAccounts = getAccounts(); + async switchAccount(account: any) { + const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; this.switchAccountWithToken(token); }, diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index b500ab582c..be907aa2a4 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -121,7 +121,7 @@ export default defineComponent({ }, async openAccountMenu(ev) { - const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); + const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)); const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); const accountItemPromises = storedAccounts.map(a => new Promise(res => { @@ -181,8 +181,8 @@ export default defineComponent({ }, 'closed'); }, - switchAccount(account: any) { - const storedAccounts = getAccounts(); + async switchAccount(account: any) { + const storedAccounts = await getAccounts(); const token = storedAccounts.find(x => x.id === account.id).token; this.switchAccountWithToken(token); }, -- cgit v1.2.3-freya From 04855f920144cd3d040d9896157f186c1ceebc88 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Aug 2021 10:29:26 +0900 Subject: enhance(client): Improve emoji autocomplete behaviour cherry picked from https://github.com/kat-atat/misskey/commit/4b2c215e25a0bae47f4375b296d1f5d07a179f88 --- CHANGELOG.md | 3 ++- src/client/scripts/autocomplete.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client') diff --git a/CHANGELOG.md b/CHANGELOG.md index 47372dda56..e77ee9b247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ ## 12.x.x (unreleased) ### Improvements -- 依存関係の更新 +- 絵文字オートコンプリートの挙動を改修 - localStorageのaccountsはindexedDBで保持するように - ActivityPub: ジョブキューの試行タイミングを調整 (#7635) +- 依存関係の更新 ### Bugfixes - チャンネルを作成しているとアカウントを削除できないのを修正 diff --git a/src/client/scripts/autocomplete.ts b/src/client/scripts/autocomplete.ts index 99c54c69c5..924d6a62ee 100644 --- a/src/client/scripts/autocomplete.ts +++ b/src/client/scripts/autocomplete.ts @@ -65,7 +65,7 @@ export class Autocomplete { */ private onInput() { const caretPos = this.textarea.selectionStart; - const text = this.text.substr(0, caretPos).split('\n').pop(); + const text = this.text.substr(0, caretPos).split('\n').pop()!; const mentionIndex = text.lastIndexOf('@'); const hashtagIndex = text.lastIndexOf('#'); @@ -83,7 +83,7 @@ export class Autocomplete { const isMention = mentionIndex != -1; const isHashtag = hashtagIndex != -1; - const isEmoji = emojiIndex != -1; + const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; -- cgit v1.2.3-freya From 47dd30d3b2eb1ada91b621e5201add5a014b587d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Aug 2021 11:16:56 +0900 Subject: fix(client): ノートの「削除して編集」をするとアンケートの選択肢が[object Object]になる問題を修正 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #7037 --- CHANGELOG.md | 1 + src/client/components/post-form.vue | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src/client') diff --git a/CHANGELOG.md b/CHANGELOG.md index e77ee9b247..21f3add690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Bugfixes - チャンネルを作成しているとアカウントを削除できないのを修正 +- ノートの「削除して編集」をするとアンケートの選択肢が[object Object]になる問題を修正 ## 12.88.0 (2021/08/17) diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue index 221dc74313..657053cc93 100644 --- a/src/client/components/post-form.vue +++ b/src/client/components/post-form.vue @@ -339,7 +339,12 @@ export default defineComponent({ this.cw = init.cw; this.useCw = init.cw != null; if (init.poll) { - this.poll = init.poll; + this.poll = { + choices: init.poll.choices.map(x => x.text), + multiple: init.poll.multiple, + expiresAt: init.poll.expiresAt, + expiredAfter: init.poll.expiredAfter, + }; } this.visibility = init.visibility; this.localOnly = init.localOnly; -- cgit v1.2.3-freya From 8ab9068d8e1a8362cbbb413d7abdc72233e9fa6e Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Aug 2021 11:51:46 +0900 Subject: fix bug --- src/client/account.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'src/client') diff --git a/src/client/account.ts b/src/client/account.ts index cf52c4d828..7cd3d8cb88 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -29,16 +29,18 @@ export async function signout() { //#endregion //#region Remove push notification registration - const registration = await navigator.serviceWorker.ready; - const push = await registration.pushManager.getSubscription(); - if (!push) return; - await fetch(`${apiUrl}/sw/unregister`, { - method: 'POST', - body: JSON.stringify({ - i: $i.token, - endpoint: push.endpoint, - }), - }); + try { + const registration = await navigator.serviceWorker.ready; + const push = await registration.pushManager.getSubscription(); + if (!push) return; + await fetch(`${apiUrl}/sw/unregister`, { + method: 'POST', + body: JSON.stringify({ + i: $i.token, + endpoint: push.endpoint, + }), + }); + } catch (e) {} //#endregion document.cookie = `igi=; path=/`; -- cgit v1.2.3-freya From fd1ef4a62d670aab5f0c0089ab3806639c779813 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Aug 2021 12:41:56 +0900 Subject: enhance(server): Use job queue for account delete (#7668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(server): Use job queue for account delete Fix #5336 * ジョブをひとつに * remove done call * clean up * add User.isDeleted * コミット忘れ * Update 1629512953000-user-is-deleted.ts * show dialog * lint * Update 1629512953000-user-is-deleted.ts --- CHANGELOG.md | 1 + locales/ja-JP.yml | 1 + migration/1629512953000-user-is-deleted.ts | 15 ++++++ src/client/account.ts | 1 + src/client/init.ts | 7 +++ src/models/entities/user.ts | 7 +++ src/models/repositories/user.ts | 1 + src/queue/index.ts | 9 ++++ src/queue/processors/db/delete-account.ts | 79 ++++++++++++++++++++++++++++ src/queue/processors/db/index.ts | 4 +- src/server/api/endpoints/i/delete-account.ts | 13 ++++- 11 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 migration/1629512953000-user-is-deleted.ts create mode 100644 src/queue/processors/db/delete-account.ts (limited to 'src/client') diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f3add690..54c0554e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## 12.x.x (unreleased) ### Improvements +- アカウント削除の安定性を向上 - 絵文字オートコンプリートの挙動を改修 - localStorageのaccountsはindexedDBで保持するように - ActivityPub: ジョブキューの試行タイミングを調整 (#7635) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7499523b08..f27fc0abe0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -777,6 +777,7 @@ misskeyUpdated: "Misskeyが更新されました!" whatIsNew: "更新情報を見る" translate: "翻訳" translatedFrom: "{x}から翻訳" +accountDeletionInProgress: "アカウントの削除が進行中です" _docs: continueReading: "続きを読む" diff --git a/migration/1629512953000-user-is-deleted.ts b/migration/1629512953000-user-is-deleted.ts new file mode 100644 index 0000000000..10b7d1d7b7 --- /dev/null +++ b/migration/1629512953000-user-is-deleted.ts @@ -0,0 +1,15 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class isUserDeleted1629512953000 implements MigrationInterface { + name = 'isUserDeleted1629512953000' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" ADD "isDeleted" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`COMMENT ON COLUMN "user"."isDeleted" IS 'Whether the User is deleted.'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isDeleted"`); + } + +} diff --git a/src/client/account.ts b/src/client/account.ts index 7cd3d8cb88..ee1d845493 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -11,6 +11,7 @@ type Account = { token: string; isModerator: boolean; isAdmin: boolean; + isDeleted: boolean; }; const data = localStorage.getItem('account'); diff --git a/src/client/init.ts b/src/client/init.ts index 0313af4374..194ece886b 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -310,6 +310,13 @@ for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { } if ($i) { + if ($i.isDeleted) { + dialog({ + type: 'warning', + text: i18n.locale.accountDeletionInProgress, + }); + } + if ('Notification' in window) { // 許可を得ていなかったらリクエスト if (Notification.permission === 'default') { diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 060ec06b9a..65aebd2d1a 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -175,6 +175,13 @@ export class User { }) public isExplorable: boolean; + // アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ + @Column('boolean', { + default: false, + comment: 'Whether the User is deleted.' + }) + public isDeleted: boolean; + @Column('varchar', { length: 128, array: true, default: '{}' }) diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index f56090bb82..d4bb995ce2 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -252,6 +252,7 @@ export class UserRepository extends Repository { autoAcceptFollowed: profile!.autoAcceptFollowed, noCrawle: profile!.noCrawle, isExplorable: user.isExplorable, + isDeleted: user.isDeleted, hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: NoteUnreads.count({ where: { userId: user.id, isSpecified: true }, diff --git a/src/queue/index.ts b/src/queue/index.ts index ff96c0fb15..4ca7998e61 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -171,6 +171,15 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id'] }); } +export function createDeleteAccountJob(user: ThinUser) { + return dbQueue.add('deleteAccount', { + user: user + }, { + removeOnComplete: true, + removeOnFail: true + }); +} + export function createDeleteObjectStorageFileJob(key: string) { return objectStorageQueue.add('deleteFile', { key: key diff --git a/src/queue/processors/db/delete-account.ts b/src/queue/processors/db/delete-account.ts new file mode 100644 index 0000000000..95614b61aa --- /dev/null +++ b/src/queue/processors/db/delete-account.ts @@ -0,0 +1,79 @@ +import * as Bull from 'bull'; +import { queueLogger } from '../../logger'; +import { DriveFiles, Notes, Users } from '@/models/index'; +import { DbUserJobData } from '@/queue/types'; +import { Note } from '@/models/entities/note'; +import { DriveFile } from '@/models/entities/drive-file'; +import { MoreThan } from 'typeorm'; +import { deleteFileSync } from '@/services/drive/delete-file'; + +const logger = queueLogger.createSubLogger('delete-account'); + +export async function deleteAccount(job: Bull.Job): Promise { + logger.info(`Deleting account of ${job.data.user.id} ...`); + + const user = await Users.findOne(job.data.user.id); + if (user == null) { + return; + } + + { // Delete notes + let cursor: Note['id'] | null = null; + + while (true) { + const notes = await Notes.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}) + }, + take: 100, + order: { + id: 1 + } + }); + + if (notes.length === 0) { + break; + } + + cursor = notes[notes.length - 1].id; + + await Notes.delete(notes.map(note => note.id)); + } + + logger.succ(`All of notes deleted`); + } + + { // Delete files + let cursor: DriveFile['id'] | null = null; + + while (true) { + const files = await DriveFiles.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}) + }, + take: 10, + order: { + id: 1 + } + }); + + if (files.length === 0) { + break; + } + + cursor = files[files.length - 1].id; + + for (const file of files) { + await deleteFileSync(file); + } + } + + logger.succ(`All of files deleted`); + } + + await Users.delete(job.data.user.id); + + return 'Account deleted'; +} diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts index b56b7bfa2c..b051a28e0b 100644 --- a/src/queue/processors/db/index.ts +++ b/src/queue/processors/db/index.ts @@ -8,6 +8,7 @@ import { exportBlocking } from './export-blocking'; import { exportUserLists } from './export-user-lists'; import { importFollowing } from './import-following'; import { importUserLists } from './import-user-lists'; +import { deleteAccount } from './delete-account'; const jobs = { deleteDriveFiles, @@ -17,7 +18,8 @@ const jobs = { exportBlocking, exportUserLists, importFollowing, - importUserLists + importUserLists, + deleteAccount, } as Record | Bull.ProcessPromiseFunction>; export default function(dbQueue: Bull.Queue) { diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index f761e5cc34..77f11925cd 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; -import { Users, UserProfiles } from '@/models/index'; +import { UserProfiles, Users } from '@/models/index'; import { doPostSuspend } from '@/services/suspend-user'; import { publishUserEvent } from '@/services/stream'; +import { createDeleteAccountJob } from '@/queue'; export const meta = { requireCredential: true as const, @@ -19,6 +20,10 @@ export const meta = { export default define(meta, async (ps, user) => { const profile = await UserProfiles.findOneOrFail(user.id); + const userDetailed = await Users.findOneOrFail(user.id); + if (userDetailed.isDeleted) { + return; + } // Compare password const same = await bcrypt.compare(ps.password, profile.password!); @@ -30,7 +35,11 @@ export default define(meta, async (ps, user) => { // 物理削除する前にDelete activityを送信する await doPostSuspend(user).catch(e => {}); - await Users.delete(user.id); + createDeleteAccountJob(user); + + await Users.update(user.id, { + isDeleted: true, + }); // Terminate streaming publishUserEvent(user.id, 'terminate', {}); -- cgit v1.2.3-freya From a53e1e4ec33575de0ff79fb0ec85833e3d6e22cd Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 21 Aug 2021 12:48:50 +0900 Subject: enhance: Improve account deletion experience --- locales/ja-JP.yml | 8 ++++ src/client/pages/settings/delete-account.vue | 67 ++++++++++++++++++++++++++++ src/client/pages/settings/index.vue | 1 + src/client/pages/settings/other.vue | 19 +------- src/queue/processors/db/delete-account.ts | 12 ++++- 5 files changed, 88 insertions(+), 19 deletions(-) create mode 100644 src/client/pages/settings/delete-account.vue (limited to 'src/client') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f27fc0abe0..35739d2760 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -779,6 +779,14 @@ translate: "翻訳" translatedFrom: "{x}から翻訳" accountDeletionInProgress: "アカウントの削除が進行中です" +_accountDelete: + accountDelete: "アカウントの削除" + mayTakeTime: "アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。" + sendEmail: "アカウントの削除が完了する際は、登録してあったメールアドレス宛に通知を送信します。" + requestAccountDelete: "アカウント削除をリクエスト" + started: "削除処理が開始されました。" + inProgress: "削除が進行中" + _docs: continueReading: "続きを読む" features: "機能" diff --git a/src/client/pages/settings/delete-account.vue b/src/client/pages/settings/delete-account.vue new file mode 100644 index 0000000000..3af1879857 --- /dev/null +++ b/src/client/pages/settings/delete-account.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue index 17b373fcd8..e7e2506020 100644 --- a/src/client/pages/settings/index.vue +++ b/src/client/pages/settings/index.vue @@ -132,6 +132,7 @@ export default defineComponent({ case 'account-info': return defineAsyncComponent(() => import('./account-info.vue')); case 'update': return defineAsyncComponent(() => import('./update.vue')); case 'registry': return defineAsyncComponent(() => import('./registry.vue')); + case 'delete-account': return defineAsyncComponent(() => import('./delete-account.vue')); case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue')); } if (page.value.startsWith('registry/keys/system/')) { diff --git a/src/client/pages/settings/other.vue b/src/client/pages/settings/other.vue index f73ff9cb21..6857950350 100644 --- a/src/client/pages/settings/other.vue +++ b/src/client/pages/settings/other.vue @@ -26,7 +26,7 @@ BIOS CLI - {{ $ts.closeAccount }} + {{ $ts.closeAccount }} @@ -41,7 +41,6 @@ import FormButton from '@client/components/form/button.vue'; import * as os from '@client/os'; import { debug } from '@client/config'; import { defaultStore } from '@client/store'; -import { signout } from '@client/account'; import { unisonReload } from '@client/scripts/unison-reload'; import * as symbols from '@client/symbols'; @@ -92,22 +91,6 @@ export default defineComponent({ os.popup(import('@client/components/taskmanager.vue'), { }, {}, 'closed'); }, - - closeAccount() { - os.dialog({ - title: this.$ts.password, - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/delete-account', { - password: password - }).then(() => { - signout(); - }); - }); - } } }); diff --git a/src/queue/processors/db/delete-account.ts b/src/queue/processors/db/delete-account.ts index 95614b61aa..65327754c2 100644 --- a/src/queue/processors/db/delete-account.ts +++ b/src/queue/processors/db/delete-account.ts @@ -1,11 +1,12 @@ import * as Bull from 'bull'; import { queueLogger } from '../../logger'; -import { DriveFiles, Notes, Users } from '@/models/index'; +import { DriveFiles, Notes, UserProfiles, Users } from '@/models/index'; import { DbUserJobData } from '@/queue/types'; import { Note } from '@/models/entities/note'; import { DriveFile } from '@/models/entities/drive-file'; import { MoreThan } from 'typeorm'; import { deleteFileSync } from '@/services/drive/delete-file'; +import { sendEmail } from '@/services/send-email'; const logger = queueLogger.createSubLogger('delete-account'); @@ -73,6 +74,15 @@ export async function deleteAccount(job: Bull.Job): Promise Date: Sat, 21 Aug 2021 17:40:15 +0900 Subject: :art: --- CHANGELOG.md | 1 + src/client/components/notes.vue | 2 ++ src/client/components/notifications.vue | 6 ++++- src/client/pages/notifications.vue | 18 +++++++++++++-- src/client/pages/timeline.vue | 39 +++++++++++++++++++++------------ 5 files changed, 49 insertions(+), 17 deletions(-) (limited to 'src/client') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a29234110..9ac26311ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - ActivityPub: ジョブキューの試行タイミングを調整 (#7635) - API: sw/unregisterを追加 - ワードミュートのドキュメントを追加 +- クライアントのデザインの調整 - 依存関係の更新 ### Bugfixes diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue index ba3b7d2b39..919cb29952 100644 --- a/src/client/components/notes.vue +++ b/src/client/components/notes.vue @@ -118,6 +118,8 @@ export default defineComponent({ &:not(.noGap) { > .notes { + background: var(--bg); + .qtqtichx { background: var(--panel); border-radius: var(--radius); diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index 9db47e08d6..e91f18a693 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -7,7 +7,7 @@

{{ $ts.noNotifications }}

- + @@ -141,4 +141,8 @@ export default defineComponent({ text-align: center; color: var(--fg); } + +.elsfgstc { + background: var(--panel); +} diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue index 6b16b85b78..633718a90b 100644 --- a/src/client/pages/notifications.vue +++ b/src/client/pages/notifications.vue @@ -1,6 +1,6 @@ @@ -43,3 +43,17 @@ export default defineComponent({ } }); + + diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue index 119815e2ae..f54549b982 100644 --- a/src/client/pages/timeline.vue +++ b/src/client/pages/timeline.vue @@ -1,5 +1,5 @@ @@ -231,6 +233,7 @@ export default defineComponent({ padding: 0 8px; white-space: nowrap; overflow: auto; + border-bottom: solid 0.5px var(--divider); // 影の都合上 position: relative; @@ -287,8 +290,16 @@ export default defineComponent({ } } - > .tl { - border-top: solid 0.5px var(--divider); + &.min-width_800px { + > .tl { + background: var(--bg); + padding: 32px 0; + + > .tl { + max-width: 800px; + margin: 0 auto; + } + } } } -- cgit v1.2.3-freya