gongxiangp2/lib/task-material-completion-safety.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

48 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { prisma } from '@/lib/prisma'
export type TaskMaterialCompletionState =
| { state: 'completed'; taskStatus: 'completed'; previewUrl: string; zipFilePath: string }
| { state: 'completed_incomplete'; taskStatus: 'completed'; previewUrl: string; zipFilePath: string }
| { state: 'processing'; taskStatus: 'processing' }
| { state: 'other'; taskStatus: string | null | undefined }
| { state: 'unknown'; error: unknown }
/**
* 完成接口遇到数据库异常时,不能凭本地布尔值判断事务是否提交。
* 只有再次确认仍是 processing,调用方才可以删除本请求产物;查询失败时必须
* 保留全部文件,交给重试/人工核查,避免“记录 completed、文件却被回滚”。
*/
export async function inspectTaskMaterialCompletionAfterError(
materialId: string,
): Promise<TaskMaterialCompletionState> {
try {
const material = await prisma.material.findUnique({
where: { id: materialId },
select: {
taskStatus: true,
previewUrl: true,
zipFilePath: true,
assets: {
where: { assetType: { in: ['preview', 'zip'] } },
select: { assetType: true },
},
},
})
if (material?.taskStatus === 'completed') {
const previewUrl = String(material.previewUrl || '').trim()
const zipFilePath = String(material.zipFilePath || '').trim()
const assetTypes = new Set(material.assets.map((asset) => asset.assetType))
if (previewUrl && zipFilePath && assetTypes.has('preview') && assetTypes.has('zip')) {
return { state: 'completed', taskStatus: 'completed', previewUrl, zipFilePath }
}
return { state: 'completed_incomplete', taskStatus: 'completed', previewUrl, zipFilePath }
}
if (material?.taskStatus === 'processing') {
return { state: 'processing', taskStatus: 'processing' }
}
return { state: 'other', taskStatus: material?.taskStatus }
} catch (error) {
return { state: 'unknown', error }
}
}