refactor(契约): 契约闸门铺到其余学生端接口,并清掉与契约等价的重复类型
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
## 闸门覆盖 从 7 条扩到 20 条:竞赛列表/详情/口令/榜单、题单列表/详情/题目/徽章/进度、 全服榜与活跃榜、公告列表/详情、教程、用户度量、流程图历史/当前/统计、 提交统计与统计明细、相似题目、题目 AC 榜。 ## 顺手把两处形状收进契约 - `contestRankItemSchema.submissionInfo` 原来只是 `Record<string, unknown>`, 前端被迫再声明一份 `SubmissionInfo` 去覆盖它。现在 JSONB 的 snake_case 形状 (is_ac / ac_time / is_first_ac / error_number / checked)进了契约,那层覆盖随之消失。 - 竞赛题目列表用 `problemListItemSchema.array()` 而不是契约里的 `contestProblemsSchema` —— 后者是 `array(union([列表项, 详情]))`,联合类型会让 `filterResult` 的类型收窄落到详情分支,而且学生侧这条接口只下发列表项。 ## 清掉两处确证重复的派生 用类型探针(双向可赋值)逐对验证,`Problem` 与 `Message` 的本地派生与契约**完全等价**, 是没有内容的重复:`Problem` 的 languages/template 收窄、`Message` 的 submission 收窄 都已经在前两轮搬进契约。其余七处派生(Submission 的 result|9、Profile、AdminProblem、 Exercise、ContestRank、BlankProblem、TutorialEdit 等)探针确认**不等价**,都有真实理由, 一律保留。 `Problem` 原来还挂着 hasAstRules / visible / answers 三个可选字段,探针证明它们对 赋值没有影响(契约里本来就有 hasAstRules),去掉后没有任何调用点报错。 ## 验证 - vue-tsc 与 tsc -p apps/api 均 exit 0;vite build 通过;check:routes 无遮蔽; - 契约 schema 直跑真实接口:学生会话 13 通过 / 0 失败,管理员会话 15 通过 / 0 失败; 跳过的三条都是预期的权限或空数据(/submissions/statistics 与 /flowcharts/statistics 要教师权限、/users/:id/metrics 只统计公开提交)。
This commit is contained in:
@@ -29,13 +29,32 @@ export const contestPasswordRequestSchema = z.object({
|
||||
export const contestAccessSchema = z.object({ access: z.boolean() })
|
||||
export const contestProblemsSchema = z.array(z.union([problemListItemSchema, problemDetailSchema]))
|
||||
|
||||
/**
|
||||
* `acm_contest_rank.submission_info` 的 JSONB 原文。
|
||||
*
|
||||
* 键名是**判题链路写进去的 snake_case**(历史比赛的榜单行也是这个形状),
|
||||
* 不要跟着响应字段一起改成 camelCase。字段全部可选:只有真正提交过的题目键
|
||||
* 才会出现,`checked` 更是前端在本地标「已看」时补的。
|
||||
*
|
||||
* 原来契约这里是 `z.record(z.string(), z.unknown())`,于是前端不得不
|
||||
* 自己再声明一份 `SubmissionInfo` 去覆盖它(utils/types 的 ContestRank)。
|
||||
* 形状搬进来之后那个覆盖就没有内容了。
|
||||
*/
|
||||
export const contestSubmissionInfoSchema = z.object({
|
||||
is_ac: z.boolean(),
|
||||
ac_time: z.number(),
|
||||
is_first_ac: z.boolean(),
|
||||
error_number: z.number().int(),
|
||||
checked: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const contestRankItemSchema = z.object({
|
||||
id: z.number().int(),
|
||||
user: sampleUserSchema,
|
||||
submissionNumber: z.number().int(),
|
||||
acceptedNumber: z.number().int(),
|
||||
totalTime: z.number().int(),
|
||||
submissionInfo: z.record(z.string(), z.unknown()),
|
||||
submissionInfo: z.record(z.string(), contestSubmissionInfoSchema),
|
||||
contestId: z.number().int(),
|
||||
})
|
||||
|
||||
@@ -46,6 +65,7 @@ export type ContestList = z.infer<typeof contestListSchema>
|
||||
export type ContestRankItem = z.infer<typeof contestRankItemSchema>
|
||||
export type ContestRank = z.infer<typeof contestRankSchema>
|
||||
export type ContestAccess = z.infer<typeof contestAccessSchema>
|
||||
export type ContestSubmissionInfo = z.infer<typeof contestSubmissionInfoSchema>
|
||||
|
||||
export type ContestStatus = z.infer<typeof contestStatusSchema>
|
||||
export type ContestPasswordRequest = z.infer<typeof contestPasswordRequestSchema>
|
||||
|
||||
Reference in New Issue
Block a user