Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/backend/src/core/QueueService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
68 changes: 2 additions & 66 deletions packages/backend/src/core/UserSuspendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -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配信
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/queue/QueueProcessorModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -68,6 +69,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
ImportUserListsProcessorService,
ImportCustomEmojisProcessorService,
ImportAntennasProcessorService,
UserSuspendProcessorService,
DeleteAccountProcessorService,
DeleteFileProcessorService,
CleanRemoteFilesProcessorService,
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/queue/QueueProcessorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
101 changes: 101 additions & 0 deletions packages/backend/src/queue/processors/UserSuspendProcessorService.ts
Original file line number Diff line number Diff line change
@@ -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';
}
}
4 changes: 4 additions & 0 deletions packages/backend/src/queue/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export type DbUserDeleteJobData = {
onlyFiles?: boolean;
};

export type DbUserSuspendJobData = {
user: ThinUser
};

export type DbUserImportJobData = {
user: ThinUser;
fileId: MiDriveFile['id'];
Expand Down
Loading