- 修复内置模板验证问题(builtin-video-material等) - 添加模板验证工具 lib/template-validator.ts - 创建模板系统优化需求文档(30个详细需求) - 优化上传API的模板验证逻辑 - 改进错误提示信息的清晰度 - 添加字段池和分类管理相关需求 - 完善自定义字段的创建、验证、存储逻辑 - 清理过期的 .specstory 和 .trae 文件
160 lines
4.1 KiB
TypeScript
160 lines
4.1 KiB
TypeScript
/**
|
||
* 模板验证工具
|
||
*
|
||
* 提供统一的模板验证逻辑,支持内置模板和自定义模板
|
||
*/
|
||
|
||
import { prisma } from '@/lib/prisma'
|
||
|
||
// 内置模板key列表
|
||
export const BUILTIN_TEMPLATE_KEYS = [
|
||
'3dmax-material',
|
||
'psd-material',
|
||
'ai-material',
|
||
'video-material',
|
||
'model-material',
|
||
'other-material',
|
||
] as const
|
||
|
||
export type BuiltinTemplateKey = typeof BUILTIN_TEMPLATE_KEYS[number]
|
||
|
||
/**
|
||
* 模板验证结果
|
||
*/
|
||
export type TemplateValidationResult = {
|
||
valid: boolean
|
||
isBuiltin: boolean
|
||
templateKey?: string
|
||
error?: string
|
||
}
|
||
|
||
/**
|
||
* 验证模板ID是否有效
|
||
*
|
||
* @param templateId - 模板ID(可以是内置模板或自定义模板)
|
||
* @returns 验证结果
|
||
*/
|
||
export async function validateTemplateId(
|
||
templateId: string | null | undefined
|
||
): Promise<TemplateValidationResult> {
|
||
// 如果没有提供 templateId,视为有效(向后兼容)
|
||
if (!templateId) {
|
||
return { valid: true, isBuiltin: false }
|
||
}
|
||
|
||
// 检查是否为内置模板(builtin-* 格式)
|
||
const isBuiltinTemplate = templateId.startsWith('builtin-')
|
||
|
||
if (isBuiltinTemplate) {
|
||
// 内置模板:验证模板key是否在预设列表中
|
||
const templateKey = templateId.replace('builtin-', '') as BuiltinTemplateKey
|
||
|
||
if (!BUILTIN_TEMPLATE_KEYS.includes(templateKey)) {
|
||
return {
|
||
valid: false,
|
||
isBuiltin: true,
|
||
templateKey,
|
||
error: `无效的内置模板:${templateId}。有效的内置模板包括:${BUILTIN_TEMPLATE_KEYS.map(k => `builtin-${k}`).join(', ')}`,
|
||
}
|
||
}
|
||
|
||
return {
|
||
valid: true,
|
||
isBuiltin: true,
|
||
templateKey,
|
||
}
|
||
}
|
||
|
||
// 自定义模板:从数据库验证
|
||
try {
|
||
const template = await prisma.uploadTemplate.findUnique({
|
||
where: { id: templateId },
|
||
select: {
|
||
id: true,
|
||
name: true,
|
||
status: true,
|
||
},
|
||
})
|
||
|
||
if (!template) {
|
||
return {
|
||
valid: false,
|
||
isBuiltin: false,
|
||
error: `自定义模板不存在:${templateId}。请检查模板是否已被删除或ID是否正确。`,
|
||
}
|
||
}
|
||
|
||
// 检查模板状态
|
||
if (template.status !== 'active') {
|
||
return {
|
||
valid: false,
|
||
isBuiltin: false,
|
||
error: `模板"${template.name}"当前状态为"${template.status}",无法使用。请联系管理员启用该模板。`,
|
||
}
|
||
}
|
||
|
||
return {
|
||
valid: true,
|
||
isBuiltin: false,
|
||
}
|
||
} catch (error) {
|
||
console.error('[TemplateValidator] Error checking custom template:', error)
|
||
return {
|
||
valid: false,
|
||
isBuiltin: false,
|
||
error: `验证模板时发生错误:${error instanceof Error ? error.message : '未知错误'}`,
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查模板ID是否为内置模板
|
||
*
|
||
* @param templateId - 模板ID
|
||
* @returns 是否为内置模板
|
||
*/
|
||
export function isBuiltinTemplate(templateId: string | null | undefined): boolean {
|
||
if (!templateId) return false
|
||
return templateId.startsWith('builtin-')
|
||
}
|
||
|
||
/**
|
||
* 从内置模板ID提取模板key
|
||
*
|
||
* @param templateId - 内置模板ID(如 builtin-video-material)
|
||
* @returns 模板key(如 video-material)
|
||
*/
|
||
export function extractBuiltinTemplateKey(templateId: string): BuiltinTemplateKey | null {
|
||
if (!isBuiltinTemplate(templateId)) return null
|
||
const key = templateId.replace('builtin-', '')
|
||
return BUILTIN_TEMPLATE_KEYS.includes(key as BuiltinTemplateKey)
|
||
? (key as BuiltinTemplateKey)
|
||
: null
|
||
}
|
||
|
||
/**
|
||
* 获取模板的完整信息(包括字段)
|
||
*
|
||
* @param templateId - 模板ID
|
||
* @returns 模板信息,如果不存在返回 null
|
||
*/
|
||
export async function getTemplateWithFields(templateId: string) {
|
||
if (isBuiltinTemplate(templateId)) {
|
||
// 内置模板需要动态生成
|
||
const { createBuiltinTemplate } = await import('@/lib/upload-template-presets')
|
||
const templateKey = extractBuiltinTemplateKey(templateId)
|
||
if (!templateKey) return null
|
||
return createBuiltinTemplate(templateKey)
|
||
}
|
||
|
||
// 自定义模板从数据库读取
|
||
return prisma.uploadTemplate.findUnique({
|
||
where: { id: templateId },
|
||
include: {
|
||
fields: {
|
||
orderBy: { orderIndex: 'asc' },
|
||
},
|
||
},
|
||
})
|
||
}
|