import { DeleteObjectCommand, type S3Client } from '@aws-sdk/client-s3'; import type { Database } from '@atlas/database'; import type { OutboxJob } from './outbox-dispatcher.js'; type DeletionRow = { status: string; objects: Array<{ storage_provider: string; bucket: string; object_key: string }> }; /** 清理对象可重复执行;保留数据库事实,只有全部存储删除成功才报告完成。 */ export async function runMediaAssetDeletion(database: Database, storageFor: (provider: string) => S3Client, job: OutboxJob) { const id = String(job.payload.deletion_id); await database.workspaceTransaction(job.workspaceId, async tx => { const result = await tx.query('SELECT status, objects FROM atlas.product_image_deletions WHERE id = $1 FOR UPDATE', [id]); const row = result.rows[0]; if (!row || row.status === 'succeeded') return; await tx.query("UPDATE atlas.operations SET status = 'running', started_at = coalesce(started_at, now()), attempt_count = attempt_count + 1 WHERE id = $1", [id]); for (const object of row.objects) await storageFor(object.storage_provider).send(new DeleteObjectCommand({ Bucket: object.bucket, Key: object.object_key })); await tx.query("UPDATE atlas.operations SET status = 'succeeded', error_code = NULL, error_message = NULL, completed_at = now() WHERE id = $1", [id]); await tx.query("UPDATE atlas.product_image_deletions SET status = 'succeeded', error_code = NULL, completed_at = now() WHERE id = $1", [id]); }); } export async function markMediaAssetDeletionFailed(database: Database, job: OutboxJob) { await database.workspaceTransaction(job.workspaceId, async tx => { const id = String(job.payload.deletion_id); const result = await tx.query("UPDATE atlas.product_image_deletions SET status = 'failed', error_code = 'MEDIA_STORAGE_DELETE_FAILED', completed_at = now() WHERE id = $1 AND status <> 'succeeded'", [id]); if (result.rowCount) await tx.query("UPDATE atlas.operations SET status = 'failed', error_code = 'MEDIA_STORAGE_DELETE_FAILED', error_message = '图片存储文件清理失败,可重试', completed_at = now() WHERE id = $1", [id]); }); }