import { z } from "zod" import { adminTypeSchema, problemPermissionSchema } from "./roles" import { achievementRaritySchema } from "./achievement" import { rankProfileSchema } from "./account" import { paginatedSchema, sampleUserSchema } from "./common" import { reactionKeySchema } from "./content" import { astRulesSchema, problemDifficultySchema, sqlConfigSchema, sqlDisplaySchema, } from "./problem" /** * 后台侧的契约。与 oj 侧分开放:同一张表在两侧下发的字段集通常不同 * (后台要 `visible` 这类管理字段,oj 侧连键都不该出现), * 混在一个 schema 里迟早会有人为了省事在 oj 侧复用后台那个。 */ export const adminAnnouncementSchema = z.object({ id: z.number().int(), title: z.string(), tag: z.string(), content: z.string(), visible: z.boolean(), top: z.boolean(), createdBy: sampleUserSchema, createTime: z.string(), lastUpdateTime: z.string(), }) // 列表不带 content:公告正文是 8MB 上限的富文本,列表页只显示标题 export const adminAnnouncementListItemSchema = adminAnnouncementSchema.omit({ content: true, }) export const adminAnnouncementListSchema = paginatedSchema( adminAnnouncementListItemSchema, ) export const createAnnouncementRequestSchema = z.object({ title: z.string().trim().min(1).max(64), tag: z.string().max(64), content: z.string().max(1024 * 1024 * 8), visible: z.boolean(), top: z.boolean(), }) export const updateAnnouncementRequestSchema = createAnnouncementRequestSchema export type ProblemSample = z.infer export type ProblemAnswer = z.infer export type ProblemTestCaseScore = z.infer export type AdminAnnouncement = z.infer export type AdminAnnouncementListItem = z.infer< typeof adminAnnouncementListItemSchema > // ---------------------------------------------------------------- 教程 / 练习 export const tutorialTypeSchema = z.enum(["python", "c"]) export const adminTutorialSchema = z.object({ id: z.number().int(), title: z.string(), content: z.string(), code: z.string().nullable(), isPublic: z.boolean(), order: z.number().int(), type: tutorialTypeSchema, createdBy: sampleUserSchema, createdAt: z.string(), updatedAt: z.string(), }) export const adminTutorialListItemSchema = adminTutorialSchema.omit({ // 教程正文是整篇 markdown,列表页只排序和切换可见性,不需要它 content: true, code: true, }) /** 后台教程列表按语言分组返回,对齐旧 TutorialAdminAPI.get 的 `{python, c}` 形状 */ export const adminTutorialGroupsSchema = z.object({ python: z.array(adminTutorialListItemSchema), c: z.array(adminTutorialListItemSchema), }) export const createTutorialRequestSchema = z.object({ title: z.string().trim().min(1).max(128), content: z.string(), code: z.string().nullable().default(null), isPublic: z.boolean().default(false), order: z.number().int().default(0), type: tutorialTypeSchema, }) export const updateTutorialRequestSchema = createTutorialRequestSchema export const setTutorialVisibilityRequestSchema = z.object({ isPublic: z.boolean() }) export const exerciseTypeSchema = z.enum([ "mcq", "sort", "fill", "match", "predict", "debug", "group", ]) export const adminExerciseSchema = z.object({ id: z.number().int(), type: exerciseTypeSchema, data: z.record(z.string(), z.unknown()), order: z.number().int(), }) export const createExerciseRequestSchema = z.object({ tutorialId: z.number().int().positive(), type: exerciseTypeSchema, data: z.record(z.string(), z.unknown()), order: z.number().int().default(0), }) export const updateExerciseRequestSchema = createExerciseRequestSchema.omit({ // 练习不支持改挂到别的教程下 —— 旧 EditExerciseSerializer 也没有 tutorial_id tutorialId: true, }) // ---------------------------------------------------------------- AI 报告 /** * 列表只给摘要,正文要点开详情才拿 —— 与旧 AIAnalysisListSerializer 一致。 * `data`(原始 prompt 与结构化输入)和 `system_prompt` / `user_prompt` 两侧都不下发。 */ export const adminAiReportListItemSchema = z.object({ id: z.number().int(), username: z.string(), createTime: z.string(), analysisExcerpt: z.string(), isPinned: z.boolean(), }) export const adminAiReportSchema = z.object({ id: z.number().int(), username: z.string(), className: z.string().nullable(), createTime: z.string(), analysis: z.string(), }) export const adminAiReportListSchema = paginatedSchema(adminAiReportListItemSchema) export const toggleAiReportPinResponseSchema = z.object({ isPinned: z.boolean() }) // ---------------------------------------------------------------- 成就 // rarity 复用 achievement.ts 里已有的定义,不重复声明 —— 两份枚举迟早会长歪 export const achievementOperatorSchema = z.enum(["gte", "lte"]) export const achievementMetricSchema = z.object({ key: z.string(), name: z.string(), helpText: z.string(), }) export const adminAchievementSchema = z.object({ id: z.number().int(), name: z.string(), description: z.string(), icon: z.string(), rarity: achievementRaritySchema, hidden: z.boolean(), metric: z.string(), metricName: z.string(), operator: achievementOperatorSchema, threshold: z.number().int(), visible: z.boolean(), // 后台列表必须显示:阈值配错时学生永远拿不到也永远不会来问,这个计数器是唯一的仪表盘 unlockCount: z.number().int(), order: z.number().int(), createTime: z.string().nullable(), }) export const createAchievementRequestSchema = z.object({ name: z.string().trim().min(1).max(64), description: z.string().trim().min(1), icon: z.string().trim().min(1), rarity: achievementRaritySchema, hidden: z.boolean().default(false), metric: z.string().min(1), operator: achievementOperatorSchema, threshold: z.number().int(), visible: z.boolean().default(true), order: z.number().int().default(0), }) export const updateAchievementRequestSchema = createAchievementRequestSchema // ---------------------------------------------------------------- 用户管理 export const adminUserSchema = z.object({ id: z.number().int(), username: z.string(), email: z.string().nullable(), adminType: z.string(), problemPermission: z.string(), realName: z.string().nullable(), createTime: z.string().nullable(), lastLogin: z.string().nullable(), isDisabled: z.boolean(), // 在线与否不在库里,是从 Redis 的活动时间戳算出来的(api 的 auth/presence.ts) isOnline: z.boolean(), // 明文密码。是有意保留的运营需求:老师要能查学生的密码。 // 只在超管专属的这一个接口下发,别往任何其它地方复制。 rawPassword: z.string().nullable(), className: z.string().nullable(), }) export const adminUserListSchema = paginatedSchema(adminUserSchema) export const updateUserRequestSchema = z.object({ username: z.string().trim().min(1).max(32), email: z.email().max(64), adminType: adminTypeSchema, problemPermission: problemPermissionSchema, realName: z.string().max(32).nullable().default(null), isDisabled: z.boolean(), // 空串表示不改密码,与旧 EditUserSerializer 的 allow_blank 一致 password: z.string().max(128).default(""), }) /** 导入用户:每行 [用户名, 密码, 邮箱, 真名],与前端粘贴的 Excel 列序一致 */ export const importUsersRequestSchema = z.object({ users: z.array(z.tuple([ z.string().trim().min(1).max(32), z.string().min(1), z.string(), z.string(), ])).min(1), }) export const deleteUsersRequestSchema = z.object({ ids: z.array(z.number().int().positive()).min(1), }) export const resetPasswordResponseSchema = z.object({ password: z.string() }) // ---------------------------------------------------------------- 站点配置 / 运维 export const updateWebsiteConfigRequestSchema = z.object({ websiteBaseUrl: z.string().max(256), websiteName: z.string().trim().min(1).max(64), websiteNameShortcut: z.string().trim().min(1).max(32), websiteFooter: z.string().max(1024 * 64), allowRegister: z.boolean(), submissionListShowAll: z.boolean(), classList: z.array(z.string()), enableMaxkb: z.boolean(), }) export const judgeServerSchema = z.object({ id: z.number().int(), hostname: z.string(), ip: z.string().nullable(), judgerVersion: z.string(), cpuCore: z.number().int(), memoryUsage: z.number(), cpuUsage: z.number(), lastHeartbeat: z.string(), createTime: z.string(), taskNumber: z.number().int(), serviceUrl: z.string().nullable(), isDisabled: z.boolean(), /** 心跳在 6 秒内才算在线,与 dashboard 的判活口径一致 */ status: z.enum(["normal", "abnormal"]), }) export const judgeServerListSchema = z.object({ token: z.string(), servers: z.array(judgeServerSchema), }) export const updateJudgeServerRequestSchema = z.object({ isDisabled: z.boolean() }) export const orphanTestCaseSchema = z.object({ id: z.string(), createTime: z.number(), }) /** * 后台的用户排名列表。行的形状与 oj 侧榜单相同,但**没有 100 名的上限**: * 这里不是榜单,是老师按班级前缀翻全班学生的管理视图,要能一直翻到底。 * * 独立成一条端点而不是给公开榜单加个「不限量」开关:那个开关一旦存在, * 匿名请求就能拉走全校学生名单和个性签名。 */ export const adminUserRankSchema = paginatedSchema(rankProfileSchema) export const dashboardInfoSchema = z.object({ userCount: z.number().int(), recentContestCount: z.number().int(), todaySubmissionCount: z.number().int(), judgeServerCount: z.number().int(), }) export const uploadImageResponseSchema = z.object({ success: z.boolean(), msg: z.string(), filePath: z.string(), }) // ---------------------------------------------------------------- 比赛管理 export const adminContestSchema = z.object({ id: z.number().int(), title: z.string(), description: z.string(), tag: z.string(), startTime: z.string(), endTime: z.string(), createTime: z.string(), lastUpdateTime: z.string(), // 后台要能看到自己设的密码(用来告诉学生),oj 侧的 contestSchema 则永远不含它 password: z.string().nullable(), visible: z.boolean(), createdBy: sampleUserSchema, status: z.enum(["1", "0", "-1"]), contestType: z.enum(["Public", "Password Protected"]), }) export const adminContestListSchema = paginatedSchema(adminContestSchema) export const createContestRequestSchema = z.object({ title: z.string().trim().min(1).max(128), description: z.string(), tag: z.string().max(64), startTime: z.string().min(1), endTime: z.string().min(1), // 空串等同于「不设密码」,与旧 CreateConetestSeriaizer 的 allow_blank 一致 password: z.string().max(32).nullable().default(null), visible: z.boolean(), }) export const updateContestRequestSchema = createContestRequestSchema export const acmHelperItemSchema = z.object({ id: z.number().int(), username: z.string(), realName: z.string().nullable(), problemId: z.string(), problemDisplayId: z.string(), acInfo: z.record(z.string(), z.unknown()), checked: z.boolean(), }) export const updateAcmHelperRequestSchema = z.object({ rankId: z.number().int().positive(), problemId: z.string().min(1), checked: z.boolean(), }) // ---------------------------------------------------------------- 题单管理 export const problemSetDifficultySchema = z.enum(["Easy", "Medium", "Hard"]) export const problemSetStatusSchema = z.enum(["draft", "active", "archived"]) export const badgeConditionTypeSchema = z.enum(["all_problems", "problem_count", "score"]) export const adminProblemSetSchema = z.object({ id: z.number().int(), title: z.string(), description: z.string(), difficulty: problemSetDifficultySchema, status: problemSetStatusSchema, endTime: z.string().nullable(), visible: z.boolean(), createdBy: sampleUserSchema, createTime: z.string(), lastUpdateTime: z.string(), problemsCount: z.number().int(), /** 加入这份题单的人数,后台用来判断改动会影响多少人 */ participantCount: z.number().int(), }) export const adminProblemSetListSchema = paginatedSchema(adminProblemSetSchema) export const createProblemSetRequestSchema = z.object({ title: z.string().trim().min(1).max(200), description: z.string(), difficulty: problemSetDifficultySchema.default("Easy"), status: problemSetStatusSchema.default("active"), endTime: z.string().nullable().default(null), visible: z.boolean().default(true), }) export const updateProblemSetRequestSchema = createProblemSetRequestSchema export const updateProblemSetStatusRequestSchema = z.object({ status: problemSetStatusSchema }) export const adminProblemSetProblemSchema = z.object({ id: z.number().int(), problemsetId: z.number().int(), problemId: z.number().int(), displayId: z.string(), title: z.string(), difficulty: z.string(), order: z.number().int(), isRequired: z.boolean(), score: z.number().int(), hint: z.string().nullable(), }) export const addProblemToSetRequestSchema = z.object({ /** 展示用题号(_id),不是自增主键 —— 老师手里只有题号 */ problemId: z.string().trim().min(1), order: z.number().int().default(0), isRequired: z.boolean().default(true), score: z.number().int().default(0), hint: z.string().default(""), }) export const updateProblemInSetRequestSchema = z.object({ order: z.number().int().optional(), isRequired: z.boolean().optional(), score: z.number().int().optional(), hint: z.string().optional(), }) export const adminProblemSetBadgeSchema = z.object({ id: z.number().int(), problemsetId: z.number().int(), name: z.string(), description: z.string(), icon: z.string(), conditionType: badgeConditionTypeSchema, conditionValue: z.number().int(), /** 已获得该奖章的人数,后台改条件前要能看到影响面 */ earnedCount: z.number().int(), }) export const createProblemSetBadgeRequestSchema = z.object({ name: z.string().trim().min(1).max(100), description: z.string(), icon: z.string(), conditionType: badgeConditionTypeSchema, conditionValue: z.number().int().default(0), }) export const updateProblemSetBadgeRequestSchema = createProblemSetBadgeRequestSchema export const adminProblemSetProgressSchema = z.object({ id: z.number().int(), userId: z.number().int(), username: z.string(), realName: z.string().nullable(), joinTime: z.string(), completeTime: z.string().nullable(), isCompleted: z.boolean(), progressPercentage: z.number(), completedProblemsCount: z.number().int(), totalProblemsCount: z.number().int(), totalScore: z.number().int(), }) // ---------------------------------------------------------------- 标签与题目分析 export const adminTagSchema = z.object({ id: z.number().int(), name: z.string(), problemCount: z.number().int(), }) export const renameTagRequestSchema = z.object({ name: z.string().trim().min(1).max(64) }) export const renameTagResponseSchema = z.object({ /** 改名撞上已有标签时视为合并:题目关系转移过去、原标签删除 */ merged: z.boolean(), id: z.number().int(), name: z.string(), affectedCount: z.number().int(), }) export const batchProblemTagRequestSchema = z.object({ problemIds: z.array(z.number().int().positive()).min(1), tagNames: z.array(z.string()).min(1), action: z.enum(["add", "remove"]), }) export const batchProblemTagResponseSchema = z.object({ problemCount: z.number().int(), tagCount: z.number().int(), }) export const stuckProblemSchema = z.object({ problemId: z.string(), problemTitle: z.string(), total: z.number().int(), failed: z.number().int(), failedUsers: z.number().int(), acRate: z.number(), }) export const acTrendYearSchema = z.object({ year: z.number().int(), total: z.number().int(), accepted: z.number().int(), acRate: z.number(), }) export const acTrendSchema = z.object({ problemId: z.string(), problemTitle: z.string(), yearly: z.array(acTrendYearSchema), }) // ---------------------------------------------------------------- 自学情况 /** * 「自学情况」按学生的一行。**没读过任何一课的学生也要有一行**(readCount = 0)—— * 这张表首要回答的问题是「谁还没开始」,只列有记录的人等于把该看的人筛掉了。 */ export const learnStudentProgressSchema = z.object({ userId: z.number().int(), username: z.string(), realName: z.string().nullable(), className: z.string().nullable(), readCount: z.number().int(), totalSeconds: z.number().int(), lastViewedAt: z.string().nullable(), // 练一练:做过几道、做对几道、一共点了几次提交 exerciseTried: z.number().int(), exerciseSolved: z.number().int(), exerciseAttempts: z.number().int(), }) export const learnStudentProgressListSchema = z.object({ // 分母:该语言下已公开的教程篇数,前端拿它显示 3/17 tutorialCount: z.number().int(), // 分母:这些教程里一共有多少道练一练 exerciseCount: z.number().int(), results: z.array(learnStudentProgressSchema), }) /** * 「按练习」一行。回答的是「哪道练习卡住了全班」—— * `solvedUsers / triedUsers` 是正确率,`avgAttemptsToSolve` 是做对的人平均试了几次, * `firstTryUsers` 是一次就做对的人数。三个一起看才分得清「题目难」和「题目有歧义」: * 前者是试了几次终于做对,后者是很多人第一次就掉进同一个坑。 */ export const learnExerciseProgressSchema = z.object({ exerciseId: z.number().int(), tutorialId: z.number().int(), tutorialTitle: z.string(), tutorialOrder: z.number().int(), type: exerciseTypeSchema, order: z.number().int(), question: z.string(), triedUsers: z.number().int(), solvedUsers: z.number().int(), firstTryUsers: z.number().int(), attempts: z.number().int(), avgAttemptsToSolve: z.number(), }) export const learnExerciseProgressListSchema = z.object({ studentCount: z.number().int(), results: z.array(learnExerciseProgressSchema), }) /** 单道练习的逐人明细,后台表格展开时才拉 */ export const learnExerciseAttemptSchema = z.object({ userId: z.number().int(), username: z.string(), realName: z.string().nullable(), className: z.string().nullable(), attempts: z.number().int(), wrongAttempts: z.number().int(), solved: z.boolean(), attemptsToSolve: z.number().int().nullable(), lastWrongAnswer: z.string().nullable(), lastAttemptAt: z.string(), }) /** 「自学情况」按课的一行 */ export const learnTutorialProgressSchema = z.object({ tutorialId: z.number().int(), title: z.string(), order: z.number().int(), readers: z.number().int(), totalSeconds: z.number().int(), avgSeconds: z.number().int(), }) export const learnTutorialProgressListSchema = z.object({ // 分母:统计范围内的学生数(受班级筛选影响) studentCount: z.number().int(), results: z.array(learnTutorialProgressSchema), }) export const generateFlowchartRequestSchema = z.object({ python: z.string().min(1).max(64 * 1024) }) export const generateFlowchartResponseSchema = z.object({ flowchart: z.string() }) // ---------------------------------------------------------------- 题目管理 export const adminProblemListItemSchema = z.object({ id: z.number().int(), _id: z.string(), title: z.string(), createdBy: sampleUserSchema, visible: z.boolean(), createTime: z.string(), difficulty: problemDifficultySchema, tags: z.array(z.string()), hasAstRules: z.boolean(), allowFlowchart: z.boolean(), showFlowchart: z.boolean(), // 最高票评价 {type, count}。**只有公开题列表下发**,比赛题列表恒传 null —— // 与旧后端 reaction/services.py:get_top_reactions 的口径一致。 topReaction: z .object({ type: reactionKeySchema, count: z.number().int() }) .nullable(), }) export const adminProblemListSchema = paginatedSchema(adminProblemListItemSchema) /** * 题面样例 / 标准答案 / 测试点分值。这三个旧后端都是逐字段校验的 * (CreateSampleSerializer / CreateAnswerSerializer / CreateTestCaseScoreSerializer), * 新后端一路写成 `z.record(z.unknown())` **反而比旧的松**。 * * 生产库 956 道题逐条比对过:samples 947 条是 {input,output}(另外 9 条是空数组, * SQL 题没样例)、answers 268 条是 {language,code}(633 条 null、55 条空数组)。 */ export const problemSampleSchema = z.object({ input: z.string(), output: z.string(), }) export const problemAnswerSchema = z.object({ language: z.string(), code: z.string(), }) /** * 测试点分值。**只有这三个键** —— 上传接口的响应比这多三个 * (stripped_output_md5 / input_size / output_size),旧后端的 DRF serializer * 收下时把多的丢掉,生产库 956 行全是三键。zod 的 object 默认也剥未知键, * 行为一致。 * * score 前端算出来是字符串(`(100 / n).toFixed(0)`),旧后端 IntegerField 转成 * 整数落库,7990 条全是 int —— 这里用 coerce 保持同一个落库形状。 */ export const problemTestCaseScoreSchema = z.object({ input_name: z.string().max(32), output_name: z.string().max(32), score: z.coerce.number().int().min(0), }) /** 后台题目详情:包含 oj 侧永不下发的 answers / testCase* / astRules */ export const adminProblemSchema = z.object({ id: z.number().int(), _id: z.string(), title: z.string(), description: z.string(), inputDescription: z.string(), outputDescription: z.string(), samples: z.array(problemSampleSchema), testCaseId: z.string(), testCaseScore: z.array(problemTestCaseScoreSchema), hint: z.string().nullable(), languages: z.array(z.string()), template: z.record(z.string(), z.string()), createTime: z.string(), lastUpdateTime: z.string(), timeLimit: z.number().int(), memoryLimit: z.number().int(), visible: z.boolean(), difficulty: problemDifficultySchema, source: z.string().nullable(), submissionNumber: z.number().int(), acceptedNumber: z.number().int(), statisticInfo: z.record(z.string(), z.unknown()), contestId: z.number().int().nullable(), createdBy: sampleUserSchema, isPublic: z.boolean(), tags: z.array(z.string()), allowFlowchart: z.boolean(), showFlowchart: z.boolean(), mermaidCode: z.string().nullable(), flowchartHint: z.string().nullable(), astRules: astRulesSchema.nullable(), answers: z.array(problemAnswerSchema), prompt: z.string().nullable(), sqlConfig: sqlConfigSchema.nullable(), sqlDisplay: sqlDisplaySchema.nullable(), }) /** * 出题页新建标签时的名字上限,沿用旧 CreateProblemSerializer 的 32。 * 出题页的输入框拿它做 maxlength —— 不共享一个常量的话,超长标签只会在保存时 * 撞出 zod 的英文报错("Too big: expected string to have <=32 characters"), * 老师看不懂也不知道该改哪里。 * * 列本身是 text 没有长度限制,标签管理页(createTagRequestSchema / * renameTagRequestSchema)放到 64 —— 两处口径不同是旧后端就有的,这里只是把 * 出题页这一侧的数字挪到一处,没有改任何校验的松紧。 */ export const PROBLEM_TAG_MAX_LENGTH = 32 export const createProblemRequestSchema = z.object({ _id: z.string().trim().min(1).max(32), title: z.string().trim().min(1).max(1024), description: z.string(), inputDescription: z.string(), outputDescription: z.string(), samples: z.array(problemSampleSchema), testCaseId: z.string().regex(/^[a-zA-Z0-9]+$/).max(32), testCaseScore: z.array(problemTestCaseScoreSchema), timeLimit: z.number().int().min(1).max(1000 * 60), memoryLimit: z.number().int().min(1).max(1024), languages: z.array(z.string()).min(1), template: z.record(z.string(), z.string()), visible: z.boolean(), difficulty: z.enum(["Low", "Mid", "High"]), tags: z.array(z.string().max(PROBLEM_TAG_MAX_LENGTH)).min(1), hint: z.string().nullable().default(null), source: z.string().max(256).nullable().default(null), prompt: z.string().nullable().default(null), answers: z.array(problemAnswerSchema).default([]), allowFlowchart: z.boolean().default(false), showFlowchart: z.boolean().default(false), mermaidCode: z.string().nullable().default(null), flowchartHint: z.string().nullable().default(null), astRules: astRulesSchema.nullable().default(null), // order_sensitive 缺省补 false —— 与旧后端 SQLConfigSerializer 的 // `BooleanField(default=False)` 一致 sqlConfig: sqlConfigSchema .extend({ order_sensitive: z.boolean().default(false) }) .nullable() .default(null), }) export const updateProblemRequestSchema = createProblemRequestSchema export const makeProblemPublicRequestSchema = z.object({ displayId: z.string().trim().min(1).max(32), }) export const addContestProblemRequestSchema = z.object({ problemId: z.number().int().positive(), displayId: z.string().trim().min(1).max(32), }) export const testCaseEntrySchema = z.object({ stripped_output_md5: z.string(), input_size: z.number().int(), output_size: z.number().int(), input_name: z.string(), output_name: z.string(), }) /** * 测试点上传的返回。字段名保持 snake_case —— 它会被原样存进 problem.test_case_score * 和落盘的 info 文件,而判题沙箱读的就是这套键名,改成 camelCase 会判不了题。 */ export const uploadTestCaseResponseSchema = z.object({ id: z.string(), info: z.array(testCaseEntrySchema), }) export const sqlTestCaseScriptSchema = z.object({ name: z.string(), content: z.string(), }) export const sqlPreviewRequestSchema = z.object({ initSql: z.string().min(1).max(1024 * 1024), refSql: z.string().min(1).max(1024 * 1024), mode: z.enum(["query", "modify"]), }) export const generateSqlTestCaseRequestSchema = z.object({ refSql: z.string().min(1).max(64 * 1024), mode: z.enum(["query", "modify"]), }) export const generateSqlTestCaseResponseSchema = z.object({ sql: z.string() }) export type AdminAchievement = z.infer export type AchievementMetric = z.infer export type AdminProblem = z.infer export type AdminProblemListItem = z.infer export type AdminProblemList = z.infer export type CreateProblemRequest = z.infer export type AdminContest = z.infer export type AdminContestList = z.infer export type AcmHelperItem = z.infer export type AdminUser = z.infer export type AdminUserList = z.infer export type JudgeServer = z.infer export type DashboardInfo = z.infer export type JudgeServerList = z.infer export type OrphanTestCase = z.infer export type AdminUserRank = z.infer export type AdminAiReportListItem = z.infer export type AdminAiReport = z.infer export type AdminAiReportList = z.infer export type StuckProblem = z.infer export type LearnStudentProgress = z.infer export type LearnStudentProgressList = z.infer export type LearnTutorialProgress = z.infer export type LearnTutorialProgressList = z.infer export type LearnExerciseProgress = z.infer export type LearnExerciseProgressList = z.infer export type LearnExerciseAttempt = z.infer export type AcTrend = z.infer export type AdminTag = z.infer export type RenameTagResponse = z.infer export type BatchProblemTagResponse = z.infer export type SqlTestCaseScript = z.infer export type GenerateSqlTestCaseResponse = z.infer export type AdminProblemSetProgress = z.infer export type AdminAnnouncementList = z.infer export type CreateAnnouncementRequest = z.infer export type UpdateAnnouncementRequest = z.infer export type TutorialType = z.infer export type AdminTutorial = z.infer export type AdminTutorialListItem = z.infer export type AdminTutorialGroups = z.infer export type CreateTutorialRequest = z.infer export type UpdateTutorialRequest = z.infer export type SetTutorialVisibilityRequest = z.infer export type ExerciseType = z.infer export type AdminExercise = z.infer export type CreateExerciseRequest = z.infer export type UpdateExerciseRequest = z.infer export type ToggleAiReportPinResponse = z.infer export type AchievementOperator = z.infer export type CreateAchievementRequest = z.infer export type UpdateAchievementRequest = z.infer export type UpdateUserRequest = z.infer export type ImportUsersRequest = z.infer export type DeleteUsersRequest = z.infer export type ResetPasswordResponse = z.infer export type UpdateWebsiteConfigRequest = z.infer export type UpdateJudgeServerRequest = z.infer export type UploadImageResponse = z.infer export type CreateContestRequest = z.infer export type UpdateContestRequest = z.infer export type UpdateAcmHelperRequest = z.infer export type ProblemSetDifficulty = z.infer export type ProblemSetStatus = z.infer export type BadgeConditionType = z.infer export type AdminProblemSet = z.infer export type AdminProblemSetList = z.infer export type CreateProblemSetRequest = z.infer export type UpdateProblemSetRequest = z.infer export type UpdateProblemSetStatusRequest = z.infer export type AdminProblemSetProblem = z.infer export type AddProblemToSetRequest = z.infer export type UpdateProblemInSetRequest = z.infer export type AdminProblemSetBadge = z.infer export type CreateProblemSetBadgeRequest = z.infer export type UpdateProblemSetBadgeRequest = z.infer export type RenameTagRequest = z.infer export type BatchProblemTagRequest = z.infer export type AcTrendYear = z.infer export type GenerateFlowchartRequest = z.infer export type GenerateFlowchartResponse = z.infer export type UpdateProblemRequest = z.infer export type MakeProblemPublicRequest = z.infer export type AddContestProblemRequest = z.infer export type TestCaseEntry = z.infer export type UploadTestCaseResponse = z.infer export type SqlPreviewRequest = z.infer export type GenerateSqlTestCaseRequest = z.infer