enhance(backend): 凍結の後処理をQueueで処理するように (MisskeyIO#733)
This commit is contained in:
parent
454a3f91fa
commit
968901e73d
@ -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);
|
||||
|
@ -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<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(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配信
|
||||
|
@ -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,
|
||||
|
@ -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`);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
@ -83,6 +83,10 @@ export type DbUserDeleteJobData = {
|
||||
onlyFiles?: boolean;
|
||||
};
|
||||
|
||||
export type DbUserSuspendJobData = {
|
||||
user: ThinUser
|
||||
};
|
||||
|
||||
export type DbUserImportJobData = {
|
||||
user: ThinUser;
|
||||
fileId: MiDriveFile['id'];
|
||||
|
Loading…
Reference in New Issue
Block a user