fix(题单): 归属校验、进度分母、截止时间可见性等六处零碎
Some checks failed
Deploy / deploy (push) Has been cancelled

## user-progress 缺归属校验

学生端那条 GET /problem-sets/:id/user-progress 只有 requireTeacher,没有归属校验,
任何 Teacher Admin 都能读到别人建的题单的学生名单与进度。补上,口径与后台
loadOwned 一致(超管放行,其余人只能看自己建的),越权报 404。

顺带订正 docs/specs/phase4-review-authz.md:449:那条写着题单进度「显式下发真名」,
与代码不符 —— 学生端这条走 sampleUser 且没传 includeRealName,realName 恒为 null
(SQL 里那次 leftJoin userProfile 是白查的)。真正下发真名的是后台那条
GET /admin/problem-sets/:id/progress,结论仍成立,但当时漏掉了归属校验这个缺口。

## 头部进度的分母

分母只算必做题之后,ProblemSetHeader 还在拿 completedCount / problemsCount 算 ——
前者是必做完成数,后者是总题数。做完全部必做题的人会看到「9 / 10、90%」,而同一张
卡片上又标着「已完成」,题单 6 那 10 个人正是这种。改成读 userProgress,另外把
「另有 N 道选做」标出来,否则「共 10 道题目」和「9 / 9」对不上。

## 截止时间

end_time 管的不是「到点不能做了」,是「到点之前看不到自己加入题单之前的旧代码」,
而学生端一个字都不显示 —— 被挡住的人不知道为什么,也不知道什么时候解锁。头部加一个
带解释的「截止 …」标签;提交列表那个锁图标的说明也补上另外两条解锁路径。

## 两个必然筛空的筛选器

学生端题单列表的难度、状态两个下拉:线上 16 个题单全是 Easy / active,选「中等」
「困难」「已归档」永远是空列表。撤掉,保留关键词搜索。接口那两个 query 参数留着,
哪天真的用起这两个字段再把 select 加回来。

## 题目移出题单时的提交记录

旧栈 problemset/signals.py 的 post_delete 会清掉该题在本题单的 ProblemSetSubmission,
OJ2 没做,于是那张表一直在攒指向已移出题单的孤儿行。补上。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
This commit is contained in:
2026-09-01 00:01:58 -06:00
parent 9d9e104df6
commit e5a6f2d1e7
6 changed files with 62 additions and 61 deletions

View File

@@ -446,7 +446,17 @@ POST /api/admin/problems/batch-tag {problemIds:[40]} → 404 no-problems
- `DELETE /problem-tags/:id``tag.ts:92-102`)的事务里先删 `problemTags` 再删 `problemTag`,但删的是同一个 id标签不存在时前一句是空操作**不存在 C1 那种连带破坏**
- `DELETE /users``account.ts:237-257`)禁止删除自己,已实跑确认返回 400 `cannot-delete-self`
- `PUT /users/:id``normalizePermission``account.ts:49-53`)与 `account/views/admin.py:98-105` 的归一逻辑一致,降级超管会同步清掉 All
- 真名下发受控:`sampleUser``routes/helpers.ts:15-25`)默认 `realName: null`,只有 `acm-helper``contest.ts:272`和题单进度(`problemset.ts:426`)两处显式下发,两处都在 requireTeacher + 归属校验之后
- 真名下发受控:`sampleUser``routes/helpers.ts:15-25`)默认 `realName: null`,只有 `acm-helper``contest.ts:272`)显式下发,在 requireTeacher + 归属校验之后
> **2026-08-31 订正**:本条原先还写了「题单进度(`problemset.ts:426`)」,与代码不符 ——
> 学生端那条 `GET /problem-sets/:id/user-progress` 走的是 `sampleUser(progressUser, realName)`
> 没传 `includeRealName``realName` 恒为 `null`SQL 里那次 `leftJoin userProfile` 是白查的)。
> 真正显式下发真名的是**后台**那条 `GET /admin/problem-sets/:id/progress`,它手写
> `adminProblemSetProgressSchema`,在 requireTeacher + `loadOwned` 归属校验之后,结论仍成立。
>
> 另外当时漏了一条:学生端那条 `user-progress` 只有 `requireTeacher`、**没有归属校验**
> 任何 Teacher Admin 都能读到别人建的题单的学生名单与进度(不含真名)。已补上归属校验,
> 口径与后台的 `loadOwned` 一致,越权报 404。
- `GET /ai/reports/:id``ai.ts:70-83`)不下发 `data` / `systemPrompt` / `userPrompt`
- `GET /judge-servers``conf.ts:90-100`)下发 judge token但在 requireSuperAdmin 之后,与旧 `conf/views.py:66-74` 一致
- `DELETE /orphan-test-cases``conf.ts:147-160`)对指定 id 也先确认是孤儿,比旧 `conf/views.py:162-171` 严 —— 合理收紧