From 6696cf9ce567617636ed7c7e8410e7c4fbb1cf4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Tue, 17 Sep 2024 01:34:46 +0900 Subject: [PATCH 1/2] =?UTF-8?q?enhance(backend):=20=E5=87=8D=E7=B5=90?= =?UTF-8?q?=E3=81=AE=E5=BE=8C=E5=87=A6=E7=90=86=E3=82=92Queue=E3=81=A7?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/QueueService.ts | 10 ++ .../backend/src/core/UserSuspendService.ts | 68 +----------- .../src/queue/QueueProcessorService.ts | 3 + .../DeleteAccountProcessorService.ts | 2 +- .../processors/UserSuspendProcessorService.ts | 101 ++++++++++++++++++ packages/backend/src/queue/types.ts | 4 + 6 files changed, 121 insertions(+), 67 deletions(-) create mode 100644 packages/backend/src/queue/processors/UserSuspendProcessorService.ts diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 7c92dd402103..526751044869 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -364,6 +364,16 @@ export class QueueService { }); } + @bindThis + public createUserSuspendJob(user: ThinUser) { + return this.dbQueue.add('userSuspend', { + user: { id: user.id }, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createReportAbuseJob(report: MiAbuseUserReport) { return this.dbQueue.add('reportAbuse', report); diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index 677db309d31c..2cbc35d93397 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -9,16 +9,7 @@ import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import type { MiUser } from '@/models/User.js'; -import type { - AntennasRepository, - ClipNotesRepository, - ClipsRepository, - FollowingsRepository, - FollowRequestsRepository, - UserListMembershipsRepository, - UserListsRepository, - WebhooksRepository, -} from '@/models/_.js'; +import type { FollowingsRepository } from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -36,27 +27,6 @@ export class UserSuspendService { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, - @Inject(DI.followRequestsRepository) - private followRequestsRepository: FollowRequestsRepository, - - @Inject(DI.antennasRepository) - private antennasRepository: AntennasRepository, - - @Inject(DI.webhooksRepository) - private webhooksRepository: WebhooksRepository, - - @Inject(DI.userListsRepository) - private userListsRepository: UserListsRepository, - - @Inject(DI.clipsRepository) - private clipsRepository: ClipsRepository, - - @Inject(DI.clipNotesRepository) - private clipNotesRepository: ClipNotesRepository, - - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, - private queueService: QueueService, private globalEventService: GlobalEventService, private apRendererService: ApRendererService, @@ -72,41 +42,7 @@ export class UserSuspendService { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); - const promises: Promise[] = []; - - let cursor = ''; - while (true) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition, no-constant-condition - const clipNotes = await this.clipNotesRepository.createQueryBuilder('c') - .select('c.id') - .innerJoin('c.note', 'n') - .where('n.userId = :userId', { userId: user.id }) - .andWhere('c.id > :cursor', { cursor }) - .orderBy('c.id', 'ASC') - .limit(500) - .getRawMany<{ id: string }>(); - - if (clipNotes.length === 0) break; - - cursor = clipNotes.at(-1)?.id ?? ''; - - promises.push(this.clipNotesRepository.createQueryBuilder() - .delete() - .where('id IN (:...ids)', { ids: clipNotes.map((clipNote) => clipNote.id) }) - .execute()); - } - - await Promise.allSettled([ - this.followRequestsRepository.delete({ followeeId: user.id }), - this.followRequestsRepository.delete({ followerId: user.id }), - - this.antennasRepository.delete({ userId: user.id }), - this.webhooksRepository.delete({ userId: user.id }), - this.userListsRepository.delete({ userId: user.id }), - this.clipsRepository.delete({ userId: user.id }), - - ...promises, - this.userListMembershipsRepository.delete({ userId: user.id }), - ]); + await this.queueService.createUserSuspendJob(user); if (this.userEntityService.isLocalUser(user)) { // 知り得る全SharedInboxにDelete配信 diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 540a58b8b1a6..738ba9e25464 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -28,6 +28,7 @@ import { ImportBlockingProcessorService } from './processors/ImportBlockingProce import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js'; +import { UserSuspendProcessorService } from './processors/UserSuspendProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; @@ -106,6 +107,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private importUserListsProcessorService: ImportUserListsProcessorService, private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService, private importAntennasProcessorService: ImportAntennasProcessorService, + private userSuspendProcessorService: UserSuspendProcessorService, private deleteAccountProcessorService: DeleteAccountProcessorService, private deleteFileProcessorService: DeleteFileProcessorService, private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService, @@ -184,6 +186,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job); case 'importAntennas': return this.importAntennasProcessorService.process(job); case 'deleteAccount': return this.deleteAccountProcessorService.process(job); + case 'userSuspend': return this.userSuspendProcessorService.process(job); case 'reportAbuse': return this.reportAbuseProcessorService.process(job); default: throw new Error(`unrecognized job type ${job.name} for db`); } diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 08dc31e5462b..84e949d4faa9 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -40,7 +40,7 @@ export class DeleteAccountProcessorService { private roleService: RoleService, private queueLoggerService: QueueLoggerService, ) { - this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); + this.logger = this.queueLoggerService.logger.createSubLogger('account:delete'); } private async deleteNotes(user: MiUser) { diff --git a/packages/backend/src/queue/processors/UserSuspendProcessorService.ts b/packages/backend/src/queue/processors/UserSuspendProcessorService.ts new file mode 100644 index 000000000000..9f4c2576af72 --- /dev/null +++ b/packages/backend/src/queue/processors/UserSuspendProcessorService.ts @@ -0,0 +1,101 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; +import type Logger from '@/logger.js'; +import type { + AntennasRepository, + ClipNotesRepository, + ClipsRepository, + FollowRequestsRepository, + UserListMembershipsRepository, + UserListsRepository, UsersRepository, + WebhooksRepository, +} from '@/models/_.js'; +import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; +import type * as Bull from "bullmq"; +import type { DbUserSuspendJobData } from "@/queue/types.js"; + +@Injectable() +export class UserSuspendProcessorService { + public logger: Logger; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followRequestsRepository) + private followRequestsRepository: FollowRequestsRepository, + + @Inject(DI.antennasRepository) + private antennasRepository: AntennasRepository, + + @Inject(DI.webhooksRepository) + private webhooksRepository: WebhooksRepository, + + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('account:suspend'); + } + + @bindThis + public async process(job: Bull.Job): Promise { + this.logger.warn(`Cleaning up suspended account of ${job.data.user.id} ...`, { userSuspendJobData: job.data }); + + const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); + if (user == null) { + return 'User not found'; + } + + const promises: Promise[] = []; + + let cursor = ''; + while (true) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition, no-constant-condition + const clipNotes = await this.clipNotesRepository.createQueryBuilder('c') + .select('c.id') + .innerJoin('c.note', 'n') + .where('n.userId = :userId', { userId: user.id }) + .andWhere('c.id > :cursor', { cursor }) + .orderBy('c.id', 'ASC') + .limit(100) + .getRawMany<{ id: string }>(); + + if (clipNotes.length === 0) break; + + cursor = clipNotes.at(-1)?.id ?? ''; + + promises.push(this.clipNotesRepository.createQueryBuilder() + .delete() + .where('id IN (:...ids)', { ids: clipNotes.map((clipNote) => clipNote.id) }) + .execute()); + } + + await Promise.allSettled([ + this.followRequestsRepository.delete({ followeeId: user.id }), + this.followRequestsRepository.delete({ followerId: user.id }), + + this.antennasRepository.delete({ userId: user.id }), + this.webhooksRepository.delete({ userId: user.id }), + this.userListsRepository.delete({ userId: user.id }), + this.clipsRepository.delete({ userId: user.id }), + + ...promises, + this.userListMembershipsRepository.delete({ userId: user.id }), + ]); + + this.logger.info(`Completed cleaning up suspended account of ${job.data.user.id}`); + + return 'done'; + } +} diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 954168964007..49aedec58478 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -83,6 +83,10 @@ export type DbUserDeleteJobData = { onlyFiles?: boolean; }; +export type DbUserSuspendJobData = { + user: ThinUser +}; + export type DbUserImportJobData = { user: ThinUser; fileId: MiDriveFile['id']; From 9c8fc7cb3c5b433b64033332f24c218655de51e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Tue, 17 Sep 2024 04:12:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E6=BC=8F=E3=82=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/queue/QueueProcessorModule.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 8b9e5a45b7ea..f3588544fc7d 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -16,6 +16,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; +import { UserSuspendProcessorService } from './processors/UserSuspendProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; @@ -68,6 +69,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor ImportUserListsProcessorService, ImportCustomEmojisProcessorService, ImportAntennasProcessorService, + UserSuspendProcessorService, DeleteAccountProcessorService, DeleteFileProcessorService, CleanRemoteFilesProcessorService,