WuYongJie 542c7bcc49 feat: 优化自定义上传模板系统
- 修复内置模板验证问题(builtin-video-material等)
- 添加模板验证工具 lib/template-validator.ts
- 创建模板系统优化需求文档(30个详细需求)
- 优化上传API的模板验证逻辑
- 改进错误提示信息的清晰度
- 添加字段池和分类管理相关需求
- 完善自定义字段的创建、验证、存储逻辑
- 清理过期的 .specstory 和 .trae 文件
2025-12-13 14:26:43 +08:00

107 lines
3.1 KiB
TypeScript
Raw 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.

/**
* 用户素材列表API
*
* GET: 获取当前登录用户上传的所有素材
*
* @created 2025-11-25
* @api
*/
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { rewriteMaterialStorageUrlsForClient } from '@/lib/url-rewrite'
function parseDisplayImages(value: string | null): string[] {
if (!value) return []
try {
const parsed = JSON.parse(value)
if (Array.isArray(parsed)) {
return parsed.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
}
} catch (error) {
console.warn('[UserMaterials] Failed to parse displayImages JSON:', error)
}
return value ? [value] : []
}
export async function GET(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json(
{ error: '未登录' },
{ status: 401 }
)
}
const currentUserId = session.user.id
const materials = await (prisma as any).material.findMany({
where: {
// 个人中心「我的素材」:
// 1)我上传的:createdByUserId = 当前用户(新字段);
// 2)当前由我负责的:ownerType = 'USER' 且 ownerId = 当前用户;
// 3)兜底兼容:userId = 当前用户(历史/遗留字段,且新上传目前也会写入)。
OR: [
{ createdByUserId: currentUserId },
{ ownerType: 'USER', ownerId: currentUserId },
{ userId: currentUserId },
],
deletedAt: null,
} as any,
select: {
id: true,
userId: true,
productModel: true,
previewUrl: true,
displayImages: true,
materialType: true,
materialKind: true,
categoryName: true,
sceneName: true,
renderer: true,
downloadCount: true,
likeCount: true,
createdAt: true,
isPremium: true,
videoCoverUrl: true,
videoPreview540Url: true,
videoPreviewClipUrl: true,
videoProcessingStatus: true,
videoDuration: true,
zipFileName: true,
zipFileSize: true,
},
orderBy: { createdAt: 'desc' },
})
const formatted = materials.map((material: any) => {
// 统一处理 BigInt 等前端无法直接消费的类型
const safeZipFileSize =
typeof material.zipFileSize === 'bigint'
? material.zipFileSize.toString()
: material.zipFileSize ?? null
const base = {
...material,
zipFileSize: safeZipFileSize,
displayImages: parseDisplayImages(material.displayImages as unknown as string | null),
}
// 统一修正存储相关 URL,避免 localhost/127.0.0.1 导致客户端 404
return rewriteMaterialStorageUrlsForClient(base)
})
return NextResponse.json(formatted)
} catch (error) {
console.error('[UserMaterials] GET error:', error)
return NextResponse.json(
{ error: '获取素材列表失败' },
{ status: 500 }
)
}
}