feat: purge notes without drive files

This commit is contained in:
オスカー、 2024-02-15 22:18:22 +09:00 committed by 무라쿠모
parent 13e4702c07
commit 0793a9ba84
Signed by: SWREI
GPG Key ID: 139D6573F92DA9F7
15 changed files with 43 additions and 20 deletions

View File

@ -1826,6 +1826,13 @@ _accountDelete:
started: "Deletion has been started." started: "Deletion has been started."
inProgress: "Deletion is currently in progress" inProgress: "Deletion is currently in progress"
youCantUseThisTime: "You can't request account deletion for now." youCantUseThisTime: "You can't request account deletion for now."
_accountTruncate:
accountTruncate: "Truncate account"
purgeDriveFiles: "Also purge drive's files"
mayTakeTime: "As account deletion is a resource-heavy process, it may take some time to complete depending on how much content you have created and how many files you have uploaded."
requestAccountTruncate: "Request to truncate my account"
started: "Truncate task has been started."
inProgress: "Your account is currently being truncated"
_ad: _ad:
back: "Back" back: "Back"
reduceFrequencyOfThisAd: "Show this ad less" reduceFrequencyOfThisAd: "Show this ad less"

4
locales/index.d.ts vendored
View File

@ -7305,6 +7305,10 @@ export interface Locale extends ILocale {
* *
*/ */
"accountDelete": string; "accountDelete": string;
/**
*
*/
"purgeDriveFiles": string;
/** /**
* *
*/ */

View File

@ -1893,6 +1893,7 @@ _accountDelete:
_accountTruncate: _accountTruncate:
accountDelete: "アカウントの整理" accountDelete: "アカウントの整理"
purgeDriveFiles: "ドライブのファイルもすべて消去"
mayTakeTime: "アカウントの整理は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。" mayTakeTime: "アカウントの整理は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。"
requestAccountTruncate: "アカウント整理をリクエスト" requestAccountTruncate: "アカウント整理をリクエスト"
started: "整理処理が開始されました。" started: "整理処理が開始されました。"

View File

@ -909,7 +909,7 @@ followingVisibility: "팔로우 중인 유저를 볼 수 있는 사람"
followersVisibility: "내 팔로워를 볼 수 있는 사람" followersVisibility: "내 팔로워를 볼 수 있는 사람"
continueThread: "글타래 더 보기" continueThread: "글타래 더 보기"
deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? " deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? "
truncateAccountConfirm: "다이렉트 및 프로필 상단에 고정된 노트를 제외한 모든 노트와 파일을 제거하고 복구할 수 없습니다. 그래도 계속하시겠습니까?" truncateAccountConfirm: "다이렉트 및 프로필 상단에 고정된 노트를 제외한 모든 노트가 (드라이브 정리 옵션을 켠 경우 모든 파일도) 삭제되고 이는 복구할 수 없습니다. 그래도 계속하시겠습니까?"
incorrectPassword: "비밀번호가 올바르지 않습니다." incorrectPassword: "비밀번호가 올바르지 않습니다."
voteConfirm: "\"{choice}\"에 투표하시겠습니까?" voteConfirm: "\"{choice}\"에 투표하시겠습니까?"
hide: "숨기기" hide: "숨기기"
@ -1853,16 +1853,17 @@ _signup:
emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요." emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
_accountDelete: _accountDelete:
accountDelete: "계정 삭제" accountDelete: "계정 삭제"
mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다." mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 서버에서 시간을 들여 느리게 처리합니다. 만약 업로드한 컨텐츠가 많으면 시간이 오래 걸릴 수 있습니다."
sendEmail: "계정 삭제가 완료되면 등록된 이메일 주소로 알림을 보냅니다." sendEmail: "계정 삭제가 완료되면 등록했던 이메일 주소로 알림을 보내드립니다."
requestAccountDelete: "계정 삭제 요청" requestAccountDelete: "계정 삭제 요청하기"
started: "삭제 작업이 시작되었습니다." started: "삭제 작업이 시작되었습니다."
inProgress: "삭제 진행 중" inProgress: "삭제 진행 중"
youCantUseThisTime: "지금은 계정 삭제를 진행할 수 없습니다." youCantUseThisTime: "지금은 계정 삭제를 진행할 수 없습니다."
_accountTruncate: _accountTruncate:
accountTruncate: "계정 청소" accountTruncate: "계정 청소"
mayTakeTime: "계정 청소는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다." purgeDriveFiles: "드라이브의 파일도 정리하기"
requestAccountTruncate: "계정 청소 요청" mayTakeTime: "계정 청소는 서버에 부하를 가하기 때문에, 서버에서 시간을 들여 느리게 처리합니다. 만약 업로드한 컨텐츠가 많으면 시간이 오래 걸릴 수 있습니다."
requestAccountTruncate: "계정 청소를 요청하기"
started: "청소 작업이 시작되었습니다." started: "청소 작업이 시작되었습니다."
inProgress: "청소 진행 중" inProgress: "청소 진행 중"
_ad: _ad:

View File

@ -390,9 +390,10 @@ export class QueueService {
} }
@bindThis @bindThis
public createTruncateAccountJob(user: ThinUser, opts = {}) { public createTruncateAccountJob(user: ThinUser, purgeDrive: boolean, opts = {}) {
return this.dbQueue.add('truncateAccount', { return this.dbQueue.add('truncateAccount', {
user: { id: user.id }, user: { id: user.id },
purgeDrive: purgeDrive,
}, { }, {
removeOnComplete: true, removeOnComplete: true,
removeOnFail: true, removeOnFail: true,

View File

@ -23,10 +23,10 @@ export class TruncateAccountService {
public async truncateAccount(user: { public async truncateAccount(user: {
id: string; id: string;
host: string | null; host: string | null;
}): Promise<void> { }, purgeDrive: boolean | false): Promise<void> {
const _user = await this.usersRepository.findOneByOrFail({ id: user.id }); const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
this.queueService.createTruncateAccountJob(user, { this.queueService.createTruncateAccountJob(user, purgeDrive, {
soft: false, soft: false,
}); });
} }

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: syuilo and noridev and other misskey, cherrypick contributors * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */

View File

@ -43,7 +43,7 @@ export class TruncateAccountProcessorService {
@bindThis @bindThis
public async process(job: Bull.Job<DbUserTruncateJobData>): Promise<string | void> { public async process(job: Bull.Job<DbUserTruncateJobData>): Promise<string | void> {
this.logger.info(`Truncate notes and drives account of ${job.data.user.id} ...`); this.logger.info(`Truncate account of ${job.data.user.id} ...`);
const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
if (user == null) { if (user == null) {
@ -98,7 +98,7 @@ export class TruncateAccountProcessorService {
this.logger.succ('All of notes deleted'); this.logger.succ('All of notes deleted');
} }
{ // Delete files if (job.data.purgeDrive) { // Delete files
let cursor: MiDriveFile['id'] | null = null; let cursor: MiDriveFile['id'] | null = null;
while (true) { while (true) {
@ -124,13 +124,13 @@ export class TruncateAccountProcessorService {
cursor = files.at(-1)?.id ?? null; cursor = files.at(-1)?.id ?? null;
for (const file of files) { for (const file of files) {
await this.driveService.deleteFileSync(file); await this.driveService.deleteFileSync(file, false, user);
} }
} }
this.logger.succ('All of files deleted'); this.logger.succ('All of files deleted');
} }
return 'Account notes and drives are truncated'; return 'Account truncate job completed';
} }
} }

View File

@ -86,6 +86,7 @@ export type DbUserDeleteJobData = {
export type DbUserTruncateJobData = { export type DbUserTruncateJobData = {
user: ThinUser; user: ThinUser;
purgeDrive: boolean;
}; };
export type DbUserImportJobData = { export type DbUserImportJobData = {

View File

@ -22,6 +22,7 @@ export const paramDef = {
properties: { properties: {
password: { type: 'string' }, password: { type: 'string' },
token: { type: 'string', nullable: true }, token: { type: 'string', nullable: true },
purgeDrive: { type: 'boolean', nullable: true },
}, },
required: ['password'], required: ['password'],
} as const; } as const;
@ -40,6 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const token = ps.token; const token = ps.token;
const purgeDrive = ps.purgeDrive ? true : false;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
if (profile.twoFactorEnabled) { if (profile.twoFactorEnabled) {
@ -64,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new Error('incorrect password'); throw new Error('incorrect password');
} }
await this.truncateAccountService.truncateAccount(me); await this.truncateAccountService.truncateAccount(me, purgeDrive);
}); });
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors * SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */

View File

@ -82,7 +82,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.truncateAccount }}</template> <template #label>{{ i18n.ts.truncateAccount }}</template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkInfo warn>{{ i18n.ts._accountTruncate.mayTakeTime }}</MkInfo> <FormInfo warn>{{ i18n.ts._accountTruncate.mayTakeTime }}</FormInfo>
<MkSwitch v-model="purgeDrive">
<template #label>{{ i18n.ts._accountTruncate.purgeDriveFiles }}</template>
</MkSwitch>
<MkButton v-if="!$i.isDeleted" danger @click="truncateAccount">{{ i18n.ts._accountTruncate.requestAccountTruncate }}</MkButton> <MkButton v-if="!$i.isDeleted" danger @click="truncateAccount">{{ i18n.ts._accountTruncate.requestAccountTruncate }}</MkButton>
<MkButton v-else disabled>{{ i18n.ts._accountTruncate.inProgress }}</MkButton> <MkButton v-else disabled>{{ i18n.ts._accountTruncate.inProgress }}</MkButton>
</div> </div>
@ -132,8 +135,9 @@ import { signout, signinRequired } from '@/account.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
const $i = signinRequired();
// const reportError = computed(defaultStore.makeGetterSetter('reportError')); // const reportError = computed(defaultStore.makeGetterSetter('reportError'));
const $i = signinRequired();
const purgeDrive = ref<boolean>(false);
const devMode = computed(defaultStore.makeGetterSetter('devMode')); const devMode = computed(defaultStore.makeGetterSetter('devMode'));
const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies')); const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
const autoRemoval = ref<boolean>($i.autoRemovalCondition.active); const autoRemoval = ref<boolean>($i.autoRemovalCondition.active);
@ -189,6 +193,7 @@ async function truncateAccount() {
await os.apiWithDialog('i/truncate-account', { await os.apiWithDialog('i/truncate-account', {
password: auth.result.password, password: auth.result.password,
token: auth.result.token, token: auth.result.token,
purgeDrive: purgeDrive.value,
}); });
await os.alert({ await os.alert({

View File

@ -18247,6 +18247,7 @@ export type operations = {
'application/json': { 'application/json': {
password: string; password: string;
token?: string | null; token?: string | null;
purgeDrive?: boolean | null;
}; };
}; };
}; };