两件事: 1. 内嵌的 submission 之前复用 submissionDetailSchema 并把 info / ip 写死成空值, 于是键仍留在响应里。旧 SubmissionSafeModelSerializer 是 exclude,这三个键 根本不出现。改成独立的 embeddedSubmissionSchema —— 形状对上了,将来有人 把空值改成真值也不会变成泄露,因为这里压根没有这三个字段。 2. 顺带修掉一个评审没覆盖的真回归:内嵌 submission 给的是 problemId(数字主键), 而旧 serializer 的 problem 是 SlugRelatedField(slug_field="_id"),即展示用题号。 oj/user/message.vue:20 拿它拼 /problem/<题号>,迁移后拼出的是 /problem/undefined, 题号那一栏也是空的。改为下发 problem,实测返回 "1004"。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
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<typeof judgeStatusSchema>
|
||
export type CreateSubmissionRequest = z.infer<
|
||
typeof createSubmissionRequestSchema
|
||
>
|
||
export type SubmissionDetail = z.infer<typeof submissionDetailSchema>
|
||
export type SubmissionUpdate = z.infer<typeof submissionUpdateSchema>
|
||
export type SubmissionStatistics = z.infer<typeof submissionStatisticsSchema>
|
||
export type SubmissionStatisticsUser = z.infer<
|
||
typeof submissionStatisticsUserSchema
|
||
>
|
||
export type UnacceptedStudent = z.infer<typeof unacceptedStudentSchema>
|