import { z } from "zod" import { paginatedSchema } from "./common" export const judgeStatusSchema = z.union([ z.literal(-2), z.literal(-1), z.literal(0), z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6), z.literal(7), z.literal(8), z.literal(10), ]) export const createSubmissionRequestSchema = z.object({ problemId: z.number().int().positive(), language: z.string().min(1).max(32), code: z.string().min(1).max(1024 * 1024), contestId: z.number().int().positive().optional(), }) export const createSubmissionResponseSchema = z.object({ submissionId: z.string(), }) export const submissionDetailSchema = z.object({ id: z.string(), createTime: z.string(), userId: z.number().int(), username: z.string(), code: z.string(), result: judgeStatusSchema, info: z.unknown(), language: z.string(), shared: z.boolean(), statisticInfo: z.record(z.string(), z.unknown()), ip: z.string().nullable(), contestId: z.number().int().nullable(), problemId: z.number().int(), showLink: z.boolean(), canUnshare: z.boolean(), }) /** * 内嵌在别处(目前只有站内信)的提交对象。对齐旧后端的 * `SubmissionSafeModelSerializer(exclude=("info", "contest", "ip"))` —— * 这三个键**根本不出现**,而不是出现但值为空。 * * 独立成一个 schema 而不是复用 submissionDetailSchema 传空值:形状一致了, * 将来有人「顺手」把空值改成真值就不会变成泄露,因为这里压根没有这三个字段。 */ export const embeddedSubmissionSchema = submissionDetailSchema .omit({ info: true, ip: true, contestId: true, problemId: true }) // 旧 SubmissionSafeModelSerializer 里 problem 是 // `SlugRelatedField(slug_field="_id")`,即**展示用题号**而非数字主键。 // 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。 .extend({ problem: z.string() }) export const submissionUpdateSchema = z.object({ type: z.literal("submission_update"), submission_id: z.string(), result: judgeStatusSchema, status: z.enum(["pending", "judging", "finished", "error"]), time_cost: z.number().optional(), memory_cost: z.number().optional(), score: z.number().optional(), err_info: z.string().optional(), }) export const submissionListItemSchema = z.object({ id: z.string(), problem: z.string(), problemTitle: z.string(), showLink: z.boolean(), createTime: z.string(), userId: z.number().int(), username: z.string(), result: judgeStatusSchema, language: z.string(), shared: z.boolean(), statisticInfo: z.record(z.string(), z.unknown()), }) export const submissionListSchema = paginatedSchema(submissionListItemSchema) export const shareSubmissionRequestSchema = z.object({ shared: z.boolean() }) /** * 未完成学生。`realName` 是从用户名里剥掉 `ks<班级号>` 前缀后剩下的那一段, * 不是 user.real_name 列 —— 与 F2「真名默认不下发」不冲突:这里只有教师能看到, * 且教师面板的用途正是点名谁没做。 */ export const unacceptedStudentSchema = z.object({ username: z.string(), realName: z.string(), }) export const submissionStatisticsUserSchema = z.object({ username: z.string(), className: z.string().nullable(), submissionCount: z.number().int(), acceptedCount: z.number().int(), // 百分比数值,不带 %。旧后端返回 "85.5%" 字符串,展示格式化交给前端。 correctRate: z.number(), submissionItems: z.array( z.object({ id: z.string(), result: judgeStatusSchema }), ), }) export const submissionStatisticsSchema = z.object({ submissionCount: z.number().int(), acceptedCount: z.number().int(), correctRate: z.number(), personCount: z.number().int(), personRate: z.number(), data: z.array(submissionStatisticsUserSchema), dataUnaccepted: z.array(unacceptedStudentSchema), }) export const formatCodeRequestSchema = z.object({ code: z.string().max(1024 * 1024), language: z.enum(["python", "c", "cpp", "sql"]), }) export const formatCodeResponseSchema = z.object({ code: z.string() }) export type JudgeStatus = z.infer export type CreateSubmissionRequest = z.infer< typeof createSubmissionRequestSchema > export type SubmissionDetail = z.infer export type SubmissionUpdate = z.infer export type SubmissionStatistics = z.infer export type SubmissionStatisticsUser = z.infer< typeof submissionStatisticsUserSchema > export type UnacceptedStudent = z.infer