refactor(契约): 展示题号统一叫 problemDisplayId,题单 id 统一叫 problemSetId
Deploy / deploy (push) Canceled after 0s

前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。

- 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成
  problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items /
  今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象
  或数字 id,这几处是字符串,最容易看错
- 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成
  ?problemDisplayId=(提交列表、流程图列表、三个统计接口)
- 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存,
  统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets
  路径、前端路由参数一致。drizzle 的列键跟表名走,没动
- aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。
  ai_analysis.data 里存的历史快照仍是 user,没有代码读回它
- 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-27 16:08:31 +08:00
1 parent 5ae830a7f9
commit 9412ba3cf6
24 files changed
+80 -82

No files matched your search

+3 -3
View File
@@ -421,9 +421,9 @@ export const updateProblemSetStatusRequestSchema = z.object({
export const adminProblemSetProblemSchema = z.object({
id: z.number().int(),
problemsetId: z.number().int(),
problemSetId: z.number().int(),
problemId: z.number().int(),
displayId: z.string(),
problemDisplayId: z.string(),
title: z.string(),
difficulty: z.string(),
order: z.number().int(),
@@ -450,7 +450,7 @@ export const updateProblemInSetRequestSchema = z.object({
export const adminProblemSetBadgeSchema = z.object({
id: z.number().int(),
problemsetId: z.number().int(),
problemSetId: z.number().int(),
name: z.string(),
description: z.string(),
icon: z.string(),
+1 -1
View File
@@ -59,7 +59,7 @@ export const activityBucketSchema = z.object({
})
export const aiDetailSchema = z.object({
user: z.string(),
username: z.string(),
className: z.string().nullable(),
start: z.string(),
end: z.string(),
+1 -1
View File
@@ -45,7 +45,7 @@ export const flowchartSubmissionSchema = z.object({
export const flowchartListItemSchema = z.object({
id: z.string(),
username: z.string(),
problem: z.string(),
problemDisplayId: z.string(),
problemTitle: z.string(),
status: flowchartStatusSchema,
createTime: z.string(),
+4 -4
View File
@@ -13,7 +13,7 @@ export const problemSetUserProgressSummarySchema = z.object({
export const problemSetBadgeSchema = z.object({
id: z.number().int(),
problemsetId: z.number().int(),
problemSetId: z.number().int(),
name: z.string(),
description: z.string(),
icon: z.string(),
@@ -58,7 +58,7 @@ export const problemSetProblemItemSchema = z.object({
export const problemSetProblemSchema = z.object({
id: z.number().int(),
problemsetId: z.number().int(),
problemSetId: z.number().int(),
problem: problemSetProblemItemSchema,
order: z.number().int(),
isRequired: z.boolean(),
@@ -79,7 +79,7 @@ export const completedProblemSchema = z.object({
export const problemSetProgressSchema = z.object({
id: z.number().int(),
problemsetId: z.number().int(),
problemSetId: z.number().int(),
user: sampleUserSchema,
joinTime: z.string(),
completeTime: z.string().nullable(),
@@ -107,7 +107,7 @@ export const userBadgeSchema = z.object({
userId: z.number().int(),
badge: problemSetBadgeSchema,
earnedTime: z.string(),
problemset: z.object({ id: z.number().int(), title: z.string() }),
problemSet: z.object({ id: z.number().int(), title: z.string() }),
})
export type ProblemSet = z.infer<typeof problemSetSchema>
+7 -13
View File
@@ -191,20 +191,14 @@ export const submissionDetailSchema = z.object({
* 将来有人「顺手」把空值改成真值就不会变成泄露,因为这里压根没有这些字段。
*/
export const embeddedSubmissionSchema = submissionDetailSchema
// problemDisplayId 也要去掉:下面的 problem 就是它,同一个值留两份,
// 而路由只填了 problem —— 这里漏 omit 的那阵子,凡是收到过站内信的人
// 打开消息页都是 500(parse 抛在缺失的 problemDisplayId 上,列表为空时才碰巧不炸)。
// 留下 problemDisplayId(展示用题号)而不是数字主键:站内信页面拿它拼
// `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
.omit({
info: true,
contestId: true,
problemId: true,
problemDisplayId: true,
caseSummary: true,
})
// 旧 SubmissionSafeModelSerializer 里 problem 是
// `SlugRelatedField(slug_field="_id")`,即**展示用题号**而非数字主键。
// 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
.extend({ problem: z.string() })
/**
* 判题进度推送。**只带前端真正要用的东西**:靠 submissionId 认领、靠 result /
@@ -224,7 +218,7 @@ export const submissionUpdateSchema = z.object({
export const submissionListItemSchema = z.object({
id: z.string(),
problem: z.string(),
problemDisplayId: z.string(),
problemTitle: z.string(),
showLink: z.boolean(),
createTime: z.string(),
@@ -277,7 +271,7 @@ export const attemptedStudentSchema = unacceptedStudentSchema.extend({
.object({
id: z.string(),
/** 题目的展示编号,用来告诉老师错在哪道题 */
problem: z.string(),
problemDisplayId: z.string(),
result: judgeStatusSchema,
error: z.string().nullable(),
})
@@ -325,7 +319,7 @@ export const submissionStatisticsItemsSchema = z.object({
* 展开某个学生时列出他这段时间的提交。**带上题目**:一节课里学生往往在好几道题
* 之间来回跳,一串只有编号的按钮看不出他卡在哪一道 —— 前端按题目分组展示。
*
* 字段名沿用 submissionListItemSchema 的口径:`problem` 是展示用题号(problem._id),
* 字段名沿用 submissionListItemSchema 的口径:`problemDisplayId` 是展示用题号(problem._id),
* `problemTitle` 是标题。
*/
items: z.array(
@@ -333,7 +327,7 @@ export const submissionStatisticsItemsSchema = z.object({
id: z.string(),
result: judgeStatusSchema,
createTime: z.string(),
problem: z.string(),
problemDisplayId: z.string(),
problemTitle: z.string(),
}),
),
@@ -405,7 +399,7 @@ export const todaySubmissionStatisticsSchema = z.object({
*/
problems: z.array(
z.object({
problem: z.string(),
problemDisplayId: z.string(),
problemTitle: z.string(),
count: z.number().int(),
acceptedCount: z.number().int(),