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
|
@bindThis
|
||||||
public createReportAbuseJob(report: MiAbuseUserReport) {
|
public createReportAbuseJob(report: MiAbuseUserReport) {
|
||||||
return this.dbQueue.add('reportAbuse', report);
|
return this.dbQueue.add('reportAbuse', report);
|
||||||
|
@ -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配信
|
||||||
|
@ -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,
|
||||||
|
@ -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`);
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
onlyFiles?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DbUserSuspendJobData = {
|
||||||
|
user: ThinUser
|
||||||
|
};
|
||||||
|
|
||||||
export type DbUserImportJobData = {
|
export type DbUserImportJobData = {
|
||||||
user: ThinUser;
|
user: ThinUser;
|
||||||
fileId: MiDriveFile['id'];
|
fileId: MiDriveFile['id'];
|
||||||
|
Loading…
Reference in New Issue
Block a user