// SPDX-License-Identifier: AGPL-3.0-or-later import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes'; import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError'; import type {MessageShredRequest} from '@fluxer/schema/src/domains/admin/AdminMessageSchemas'; import type {WorkerJobPayload} from '@pkgs/worker/src/contracts/WorkerTypes'; import type {ApiContext} from '../../ApiContext'; import type {UserID} from '../../BrandedTypes'; import {Logger} from '../../Logger'; import type {AdminAuditService} from './AdminAuditService'; type MessageShredStatusCacheEntry = { status: 'in_progress' | 'completed' | 'failed'; requested: number; total: number; processed: number; skipped: number; started_at?: string; completed_at?: string; failed_at?: string; error?: string; }; type MessageShredStatusResult = | MessageShredStatusCacheEntry | { status: 'not_found'; }; interface AdminMessageShredServiceDeps { apiContext: ApiContext; auditService: AdminAuditService; } interface QueueMessageShredJobPayload extends WorkerJobPayload { job_id: string; admin_user_id: string; target_user_id: string; entries: Array<{ channel_id: string; message_id: string; }>; } export class AdminMessageShredService { constructor(private readonly deps: AdminMessageShredServiceDeps) {} async queueMessageShred( data: MessageShredRequest, adminUserId: UserID, auditLogReason: string | null, ): Promise<{ success: true; job_id: string; requested: number; }> { if (data.entries.length === 0) { throw InputValidationError.fromCode('entries', ValidationErrorCodes.AT_LEAST_ONE_ENTRY_IS_REQUIRED); } const {snowflake: snowflakeService, worker: workerService} = this.deps.apiContext.services; const jobId = (await snowflakeService.generate()).toString(); const payload: QueueMessageShredJobPayload = { job_id: jobId, admin_user_id: adminUserId.toString(), target_user_id: data.user_id.toString(), entries: data.entries.map((entry) => ({ channel_id: entry.channel_id.toString(), message_id: entry.message_id.toString(), })), }; await workerService.addJob('messageShred', payload, { jobKey: `message_shred_${data.user_id.toString()}_${jobId}`, maxAttempts: 1, }); Logger.debug({target_user_id: data.user_id, job_id: jobId}, 'Queued message shred job'); const metadata = new Map([ ['user_id', data.user_id.toString()], ['job_id', jobId], ['requested_entries', data.entries.length.toString()], ]); await this.deps.auditService.createAuditLog({ adminUserId, targetType: 'message_shred', targetId: data.user_id, action: 'queue_message_shred', auditLogReason, metadata, }); return { success: true, job_id: jobId, requested: data.entries.length, }; } async getMessageShredStatus(jobId: string): Promise { const {cache: cacheService} = this.deps.apiContext.services; const statusKey = `message_shred_status:${jobId}`; const status = await cacheService.get(statusKey); if (!status) { return { status: 'not_found', }; } return status; } }