1
0
mirror of https://github.com/MisskeyIO/misskey synced 2024-11-27 06:18:40 +09:00

enhance(backend): 凍結の後処理をQueueで処理するように (MisskeyIO#733)

This commit is contained in:
まっちゃとーにゅ 2024-09-17 08:07:18 +09:00 committed by GitHub
parent 454a3f91fa
commit 968901e73d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 123 additions and 67 deletions

View File

@ -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 @bindThis
public createReportAbuseJob(report: MiAbuseUserReport) { public createReportAbuseJob(report: MiAbuseUserReport) {
return this.dbQueue.add('reportAbuse', report); return this.dbQueue.add('reportAbuse', report);

View File

@ -9,16 +9,7 @@ import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import type { import type { FollowingsRepository } from '@/models/_.js';
AntennasRepository,
ClipNotesRepository,
ClipsRepository,
FollowingsRepository,
FollowRequestsRepository,
UserListMembershipsRepository,
UserListsRepository,
WebhooksRepository,
} from '@/models/_.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@ -36,27 +27,6 @@ export class UserSuspendService {
@Inject(DI.followingsRepository) @Inject(DI.followingsRepository)
private followingsRepository: 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 queueService: QueueService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
@ -72,41 +42,7 @@ export class UserSuspendService {
this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
const promises: Promise<unknown>[] = []; await this.queueService.createUserSuspendJob(user);
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 }),
]);
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信 // 知り得る全SharedInboxにDelete配信

View File

@ -16,6 +16,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
import { UserSuspendProcessorService } from './processors/UserSuspendProcessorService.js';
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js'; import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
@ -68,6 +69,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
ImportUserListsProcessorService, ImportUserListsProcessorService,
ImportCustomEmojisProcessorService, ImportCustomEmojisProcessorService,
ImportAntennasProcessorService, ImportAntennasProcessorService,
UserSuspendProcessorService,
DeleteAccountProcessorService, DeleteAccountProcessorService,
DeleteFileProcessorService, DeleteFileProcessorService,
CleanRemoteFilesProcessorService, CleanRemoteFilesProcessorService,

View File

@ -28,6 +28,7 @@ import { ImportBlockingProcessorService } from './processors/ImportBlockingProce
import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js'; import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js'; import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js'; import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
import { UserSuspendProcessorService } from './processors/UserSuspendProcessorService.js';
import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js'; import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
@ -106,6 +107,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
private importUserListsProcessorService: ImportUserListsProcessorService, private importUserListsProcessorService: ImportUserListsProcessorService,
private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService, private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
private importAntennasProcessorService: ImportAntennasProcessorService, private importAntennasProcessorService: ImportAntennasProcessorService,
private userSuspendProcessorService: UserSuspendProcessorService,
private deleteAccountProcessorService: DeleteAccountProcessorService, private deleteAccountProcessorService: DeleteAccountProcessorService,
private deleteFileProcessorService: DeleteFileProcessorService, private deleteFileProcessorService: DeleteFileProcessorService,
private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService, private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
@ -184,6 +186,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job); case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
case 'importAntennas': return this.importAntennasProcessorService.process(job); case 'importAntennas': return this.importAntennasProcessorService.process(job);
case 'deleteAccount': return this.deleteAccountProcessorService.process(job); case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
case 'userSuspend': return this.userSuspendProcessorService.process(job);
case 'reportAbuse': return this.reportAbuseProcessorService.process(job); case 'reportAbuse': return this.reportAbuseProcessorService.process(job);
default: throw new Error(`unrecognized job type ${job.name} for db`); default: throw new Error(`unrecognized job type ${job.name} for db`);
} }

View File

@ -40,7 +40,7 @@ export class DeleteAccountProcessorService {
private roleService: RoleService, private roleService: RoleService,
private queueLoggerService: QueueLoggerService, private queueLoggerService: QueueLoggerService,
) { ) {
this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); this.logger = this.queueLoggerService.logger.createSubLogger('account:delete');
} }
private async deleteNotes(user: MiUser) { private async deleteNotes(user: MiUser) {

View File

@ -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<DbUserSuspendJobData>): Promise<string | void> {
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<unknown>[] = [];
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';
}
}

View File

@ -83,6 +83,10 @@ export type DbUserDeleteJobData = {
onlyFiles?: boolean; onlyFiles?: boolean;
}; };
export type DbUserSuspendJobData = {
user: ThinUser
};
export type DbUserImportJobData = { export type DbUserImportJobData = {
user: ThinUser; user: ThinUser;
fileId: MiDriveFile['id']; fileId: MiDriveFile['id'];