parent: - Add parent-student-attendance-detail component auth: - Add actions, data-access, schema, services, types onboarding: - Add parent-children-form and hooks directory files: - Add actions, schema, hooks directory notifications: - Add schema and schema test adaptive-practice: - Add answer-input, answer-result, practice-result-view, practice-starter-with-nav - Add question-card, question-content, lib and services directories ai: - Add context/create-ai-client-service, hooks/use-drag-position, hooks/use-position-persistence
85 lines
2.2 KiB
TypeScript
85 lines
2.2 KiB
TypeScript
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<typeof UploadMetadataSchema>
|
||
|
||
/**
|
||
* 批量删除请求体校验
|
||
*
|
||
* - 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<typeof BatchDeleteSchema>
|
||
|
||
/**
|
||
* 管理员文件列表筛选参数校验
|
||
*
|
||
* - 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<typeof FileListQuerySchema>
|
||
|
||
/**
|
||
* 文件大小校验(用于客户端/服务端一致校验)
|
||
*/
|
||
export function validateFileSize(size: number): boolean {
|
||
return size > 0 && size <= MAX_FILE_SIZE
|
||
}
|