import { z } from "zod" import { MAX_FILE_SIZE } from "@/shared/lib/file-storage" import type { FileTargetType } from "./types" /** * files 模块 Zod 校验 schema * * 用于 Server Action 与 API 路由的输入校验,替代手写 typeof 检查与 as 断言。 */ // FileTargetType 枚举值同步到 Zod(保持单一来源:types.ts) const FILE_TARGET_TYPES: readonly FileTargetType[] = [ "exam", "textbook", "question", "announcement", "homework", "user_avatar", "message", ] export const FileTargetTypeSchema = z.enum( FILE_TARGET_TYPES as unknown as [FileTargetType, ...FileTargetType[]] ) /** * 文件上传元数据校验(targetType / targetId 来自 FormData) * * targetType 可选;targetId 仅在 targetType 提供时才校验长度。 */ export const UploadMetadataSchema = z.object({ targetType: FileTargetTypeSchema.optional().nullable(), targetId: z .string() .trim() .max(128) .optional() .nullable() .transform((v) => (v && v.length > 0 ? v : null)), }) export type UploadMetadata = z.infer /** * 批量删除请求体校验 * * - ids 必须为非空字符串数组 * - 单次最多 100 条,防止超长 SQL * - 每条 id 长度上限 128(与 schema.id varchar(128) 一致) */ export const BatchDeleteSchema = z.object({ ids: z .array(z.string().min(1).max(128)) .min(1, "No file ids provided") .max(100, "Cannot delete more than 100 files at once"), }) export type BatchDeleteInput = z.infer /** * 管理员文件列表筛选参数校验 * * - mimeType:精确或前缀匹配("image/") * - search:文件名模糊匹配 * - limit:1..200,默认 100 * - offset:>=0,默认 0 */ export const FileListQuerySchema = z.object({ mimeType: z.string().trim().max(128).optional().nullable(), search: z.string().trim().max(255).optional().nullable(), limit: z.number().int().min(1).max(200).default(100), offset: z.number().int().min(0).default(0), }) export type FileListQuery = z.infer /** * 文件大小校验(用于客户端/服务端一致校验) */ export function validateFileSize(size: number): boolean { return size > 0 && size <= MAX_FILE_SIZE }