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
@@ -293,9 +293,9 @@ adminProblemSetRoutes.get(
({ item, problem }) =>
({
id: item.id,
problemsetId: item.problemsetId,
problemSetId: item.problemsetId,
problemId: item.problemId,
displayId: problem.displayId,
problemDisplayId: problem.displayId,
title: problem.title,
difficulty: problem.difficulty,
order: item.order,
@@ -456,7 +456,7 @@ async function badgesWithCount(badges: BadgeRow[]) {
(badge) =>
({
id: badge.id,
problemsetId: badge.problemsetId,
problemSetId: badge.problemsetId,
name: badge.name,
description: badge.description,
icon: badge.icon,
+1 -1
View File
@@ -169,7 +169,7 @@ contentRoutes.get("/messages", requireAuth, async (c) => {
language: submission.language,
statisticInfo: asRecord(submission.statisticInfo),
// 展示用题号而非数字主键,站内信页面拿它拼 /problem/<题号>
problem: displayId,
problemDisplayId: displayId,
showLink: true,
} satisfies EmbeddedSubmission,
}) satisfies Message,
+3 -3
View File
@@ -244,7 +244,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => {
const user = c.get("user")!
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
const displayId = c.req.query("problemId")?.trim()
const displayId = c.req.query("problemDisplayId")?.trim()
const username = c.req.query("username")?.trim()
const grade = c.req.query("grade")
// 与代码提交列表同一套口径(submission.ts 的 GET /submissions):关掉
@@ -298,7 +298,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => {
({
id: flowchart.id,
username,
problem: problem.displayId,
problemDisplayId: problem.displayId,
problemTitle: problem.title,
status: flowchart.status,
createTime: flowchart.createTime,
@@ -349,7 +349,7 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
if (start)
filters.push(sql`${schema.flowchartSubmission.createTime} >= ${start}`)
const displayId = c.req.query("problemId")?.trim()
const displayId = c.req.query("problemDisplayId")?.trim()
if (displayId) {
const [problem] = await db
.select({ id: schema.problem.id })
+4 -4
View File
@@ -81,7 +81,7 @@ function badgeData(
) {
return {
id: badge.id,
problemsetId: badge.problemsetId,
problemSetId: badge.problemsetId,
name: badge.name,
description: badge.description,
icon: badge.icon,
@@ -306,7 +306,7 @@ problemsetRoutes.get("/problem-sets/:id/problems", optionalAuth, async (c) => {
({ link, problemId, displayId, title, difficulty }) =>
({
id: link.id,
problemsetId: link.problemsetId,
problemSetId: link.problemsetId,
problem: { id: problemId, _id: displayId, title, difficulty },
order: link.order,
isRequired: link.isRequired,
@@ -435,7 +435,7 @@ problemsetRoutes.get("/users/:username/badges", optionalAuth, async (c) => {
userId: userBadge.userId,
badge: badgeData(badge),
earnedTime: userBadge.earnedTime,
problemset: { id: problemSet.id, title: problemSet.title },
problemSet: { id: problemSet.id, title: problemSet.title },
}) satisfies UserBadge,
),
)
@@ -572,7 +572,7 @@ problemsetRoutes.get(
({ progress, user: progressUser, realName }) =>
({
id: progress.id,
problemsetId: progress.problemsetId,
problemSetId: progress.problemsetId,
user: sampleUser(progressUser, realName),
joinTime: progress.joinTime,
completeTime: progress.completeTime,
+5 -5
View File
@@ -151,7 +151,7 @@ submissionStatisticsRoutes.get(
count: row.value,
})),
problems: problemRows.map((row) => ({
problem: row.displayId,
problemDisplayId: row.displayId,
problemTitle: row.title,
count: row.value,
acceptedCount: row.accepted,
@@ -240,7 +240,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) {
number,
{
id: string
problem: string
problemDisplayId: string
result: JudgeStatusValue
error: string | null
}
@@ -276,7 +276,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) {
for (const row of rows) {
byUser.set(row.user_id, {
id: row.id,
problem: row.problem,
problemDisplayId: row.problem,
result: row.result,
error: row.error,
})
@@ -412,7 +412,7 @@ async function statisticsScope(c: {
if (range.start)
filters.push(sql`${schema.submission.createTime} >= ${range.start}`)
const displayIds = parseDisplayIds(c.req.query("problemId") ?? "")
const displayIds = parseDisplayIds(c.req.query("problemDisplayId") ?? "")
if (displayIds.length > STATISTICS_MAX_PROBLEMS) {
return {
ok: false,
@@ -682,7 +682,7 @@ submissionStatisticsRoutes.get(
id: schema.submission.id,
result: schema.submission.result,
createTime: schema.submission.createTime,
problem: schema.problem.displayId,
problemDisplayId: schema.problem.displayId,
problemTitle: schema.problem.title,
})
.from(schema.submission)
+4 -4
View File
@@ -627,7 +627,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => {
) {
return success(c, { results: [], total: 0 } satisfies SubmissionList)
}
const displayId = c.req.query("problemId")?.trim()
const displayId = c.req.query("problemDisplayId")?.trim()
const myself = c.req.query("myself") === "1" ? user : null
// 「只看自己」盖过用户名
const username = myself ? undefined : c.req.query("username")?.trim()
@@ -673,7 +673,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => {
({ submission, problem }) =>
({
id: submission.id,
problem: problem.displayId,
problemDisplayId: problem.displayId,
problemTitle: problem.title,
showLink: user
? canViewSubmission(user, submission, problem, null, joinTimes)
@@ -708,7 +708,7 @@ submissionRoutes.get(
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
const user = c.get("user")
const displayId = c.req.query("problemId")?.trim()
const displayId = c.req.query("problemDisplayId")?.trim()
const myself = c.req.query("myself") === "1" ? user : null
const username = myself ? undefined : c.req.query("username")?.trim()
const result = c.req.query("result")
@@ -757,7 +757,7 @@ submissionRoutes.get(
({ submission, problem }) =>
({
id: submission.id,
problem: problem.displayId,
problemDisplayId: problem.displayId,
problemTitle: problem.title,
showLink: user
? canViewSubmission(user, submission, problem, contest)
+2 -2
View File
@@ -314,7 +314,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) {
const problemIds = firstAc.map((item) => item.problemId)
if (!problemIds.length)
return {
user: user.username,
username: user.username,
className: user.className,
start,
end,
@@ -415,7 +415,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) {
b.latestSubmissionTime.localeCompare(a.latestSubmissionTime),
)
return {
user: user.username,
username: user.username,
className: user.className,
start,
end,