feat(阶段4): 标签管理 / 批量打标签 / 题目可见性 / 卡点与 AC 趋势 / 流程图 AI

GET/PUT/DELETE    admin/problem-tags[/:id]
  POST              admin/problems/batch-tag
  PUT               admin/problems/:id/visibility
  GET               admin/problems/stuck
  GET               admin/problems/ac-trend
  POST              admin/problems/flowchart

要点:

- 后台标签列表用 leftJoin 且不加 having,能看到 problemCount=0 的标签 ——
  那正是要清理的那些。oj 侧的 /problem-tags 才过滤 >0。
- 标签改名撞上已有标签视为合并:只给「还没挂目标标签」的题目补关系,
  否则会撞 (problem_id, problemtag_id) 唯一约束。
- 批量打标签:add 时按需新建标签、remove 时只认已有标签 ——
  否则「移除」会顺手造出一堆空标签。名字去重且大小写不敏感。
- **旧 ProblemVisibleAPI 的 `self.error(...)` 少写了 return**,题目不存在时会继续
  往下跑并抛 AttributeError(500)。这里正常返回 404。

顺带记下一条阶段 5 的必做项(见 phase3-coverage.md 文末):本地库按显式 id 从生产
导入,序列没跟着走,第一次新建标签就撞 problem_tag_pkey。只要切换流程里有
「导出→导入到新库」这一步,就必须重置全部序列,否则读全正常、第一次写才炸。

实测:学生 403;大小写重复与空白名去重后 tagCount=1、重复 add 幂等、
remove 不存在的标签 404 且不新建;纯改名 merged=false、合并 merged=true
affectedCount=1 且只剩一个标签;可见性取反两次复原、不存在的题 404。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 16:30:48 -06:00
co-authored by Claude Opus 5
parent 5e0229db12
commit 764e0d28cc
5 changed files with 404 additions and 11 deletions
+2
View File
@@ -7,6 +7,7 @@ import { adminAiRoutes } from "./ai"
import { adminConfRoutes } from "./conf"
import { adminContestRoutes } from "./contest"
import { adminProblemSetRoutes } from "./problemset"
import { adminTagRoutes } from "./tag"
import { adminAnnouncementRoutes } from "./announcement"
import { adminTutorialRoutes } from "./tutorial"
@@ -26,5 +27,6 @@ adminRoutes.route("/", adminAiRoutes)
adminRoutes.route("/", adminConfRoutes)
adminRoutes.route("/", adminContestRoutes)
adminRoutes.route("/", adminProblemSetRoutes)
adminRoutes.route("/", adminTagRoutes)
adminRoutes.route("/", adminAnnouncementRoutes)
adminRoutes.route("/", adminTutorialRoutes)