feat(parent,auth,onboarding,files,notifications,adaptive-practice,ai): add module updates

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
This commit is contained in:
SpecialX
2026-07-03 10:26:12 +08:00
parent f3c223d914
commit e9a5264fe7
84 changed files with 6060 additions and 2530 deletions

View File

@@ -0,0 +1,84 @@
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文件名模糊匹配
* - limit1..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
}