import { z } from "zod"; import { RouteError } from "@/lib/http/response"; export const backendLibraryResourceTypeSchema = z.enum(["model", "scene", "garment"]); export const backendLibraryFileRoleSchema = z.enum(["original", "thumbnail", "gallery"]); const presignUploadSchema = z.object({ resourceType: backendLibraryResourceTypeSchema, fileRole: backendLibraryFileRoleSchema, fileName: z.string().trim().min(1), contentType: z.string().trim().min(1), }); const createLibraryFileSchema = z.object({ fileRole: backendLibraryFileRoleSchema, storageKey: z.string().trim().min(1), publicUrl: z.string().trim().min(1), mimeType: z.string().trim().min(1), sizeBytes: z.number().int().nonnegative(), sortOrder: z.number().int().nonnegative().default(0), width: z.number().int().positive().optional(), height: z.number().int().positive().optional(), }); const createLibraryResourceSchema = z.object({ name: z.string().trim().min(1), description: z.string().trim().optional().default(""), tags: z.array(z.string().trim()).default([]), gender: z.string().trim().optional(), ageGroup: z.string().trim().optional(), poseId: z.number().int().positive().optional(), environment: z.string().trim().optional(), category: z.string().trim().optional(), files: z.array(createLibraryFileSchema).min(2), }); const updateLibraryResourceSchema = z .object({ name: z.string().trim().min(1).optional(), description: z.string().trim().optional(), tags: z.array(z.string().trim()).optional(), gender: z.string().trim().optional(), ageGroup: z.string().trim().optional(), poseId: z.number().int().positive().optional(), environment: z.string().trim().optional(), category: z.string().trim().optional(), coverFileId: z.number().int().positive().optional(), }) .refine((value) => Object.keys(value).length > 0, { message: "至少提供一个可更新字段。", }); export type CreateLibraryResourcePayload = z.infer; export type UpdateLibraryResourcePayload = z.infer; export function parseLibraryUploadPresignPayload(payload: unknown) { const result = presignUploadSchema.safeParse(payload); if (!result.success) { throw new RouteError( 400, "VALIDATION_ERROR", "上传参数不合法。", result.error.flatten(), ); } return { resource_type: result.data.resourceType, file_role: result.data.fileRole, file_name: result.data.fileName, content_type: result.data.contentType, }; } export function parseCreateLibraryResourcePayload( payload: unknown, resourceType: z.infer, ) { const result = createLibraryResourceSchema.safeParse(payload); if (!result.success) { throw new RouteError( 400, "VALIDATION_ERROR", "资源参数不合法。", result.error.flatten(), ); } return { resource_type: resourceType, name: result.data.name, description: result.data.description, tags: result.data.tags.filter(Boolean), gender: result.data.gender, age_group: result.data.ageGroup, pose_id: result.data.poseId, environment: result.data.environment, category: result.data.category, files: result.data.files.map((file) => ({ file_role: file.fileRole, storage_key: file.storageKey, public_url: file.publicUrl, mime_type: file.mimeType, size_bytes: file.sizeBytes, sort_order: file.sortOrder, width: file.width, height: file.height, })), }; } export function parseUpdateLibraryResourcePayload(payload: unknown) { const result = updateLibraryResourceSchema.safeParse(payload); if (!result.success) { throw new RouteError( 400, "VALIDATION_ERROR", "资源更新参数不合法。", result.error.flatten(), ); } return { ...(result.data.name !== undefined ? { name: result.data.name } : {}), ...(result.data.description !== undefined ? { description: result.data.description } : {}), ...(result.data.tags !== undefined ? { tags: result.data.tags.filter(Boolean) } : {}), ...(result.data.gender !== undefined ? { gender: result.data.gender } : {}), ...(result.data.ageGroup !== undefined ? { age_group: result.data.ageGroup } : {}), ...(result.data.poseId !== undefined ? { pose_id: result.data.poseId } : {}), ...(result.data.environment !== undefined ? { environment: result.data.environment } : {}), ...(result.data.category !== undefined ? { category: result.data.category } : {}), ...(result.data.coverFileId !== undefined ? { cover_file_id: result.data.coverFileId } : {}), }; }