151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
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<typeof createLibraryResourceSchema>;
|
|
export type UpdateLibraryResourcePayload = z.infer<typeof updateLibraryResourceSchema>;
|
|
|
|
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<typeof backendLibraryResourceTypeSchema>,
|
|
) {
|
|
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 }
|
|
: {}),
|
|
};
|
|
}
|