diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts index b69ca8362..df3de63e8 100644 --- a/packages/backend/src/core/ClipService.ts +++ b/packages/backend/src/core/ClipService.ts @@ -134,6 +134,10 @@ export class ClipService { throw new ClipService.NoSuchClipError(); } + if (await this.clipNotesRepository.existsBy({ clipId, noteId })) { + throw new ClipService.AlreadyAddedError(); + } + const policies = await this.roleService.getUserPolicies(me.id); const currentClipCount = await this.clipsRepository.countBy({ @@ -143,6 +147,13 @@ export class ClipService { throw new ClipService.ClipLimitExceededError(); } + const currentNoteCount = await this.clipNotesRepository.countBy({ + clipId: clip.id, + }); + if (currentNoteCount >= policies.noteEachClipsLimit) { + throw new ClipService.TooManyClipNotesError(); + } + const currentNoteCounts = await this.clipNotesRepository .createQueryBuilder('cn') .select('COUNT(*)') @@ -154,13 +165,6 @@ export class ClipService { throw new ClipService.ClipNotesLimitExceededError(); } - const currentNoteCount = await this.clipNotesRepository.countBy({ - clipId: clip.id, - }); - if (currentNoteCount >= policies.noteEachClipsLimit) { - throw new ClipService.TooManyClipNotesError(); - } - try { await this.clipNotesRepository.insert({ id: this.idService.gen(), diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts index f9cf38684..677db309d 100644 --- a/packages/backend/src/core/UserSuspendService.ts +++ b/packages/backend/src/core/UserSuspendService.ts @@ -9,7 +9,16 @@ 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 { FollowingsRepository, FollowRequestsRepository } from '@/models/_.js'; +import type { + AntennasRepository, + ClipNotesRepository, + ClipsRepository, + FollowingsRepository, + FollowRequestsRepository, + UserListMembershipsRepository, + UserListsRepository, + WebhooksRepository, +} from '@/models/_.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -30,6 +39,24 @@ export class UserSuspendService { @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, @@ -45,10 +72,41 @@ export class UserSuspendService { this.globalEventService.publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true }); - await Promise.all([ + 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 }), - ]).catch(() => null); + + 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)) { // 知り得る全SharedInboxにDelete配信