feat(admin): 新增选手信息编辑接口 PUT /api/admin/contestants/:id(R108)

此前 contestants 表只有 Excel 导入一个写入口 —— 证件号/姓名/手机号录错后
改不了也删不掉。本提交新增受控编辑通道。

service.updateContestant(id, payload):
  · 白名单仅 name / phone / id_number;白名单外字段(group / 成绩类 /
    password / test_flag / team_id / activated)显式 400 拒绝,非静默忽略
    (静默忽略会让管理员误以为改成功)
  · id_number 判据复用 lib/idCard.js(与导入通道同源):长度 != 18 拒绝;
    校验位不符 / 含非法字符 => 放行 + warnings[](生产库现存正式考生中
    即有 4 条校验位不符,硬拦截会造成合法数据改不动)
  · 唯一性冲突 => 409
  · 改号连带重算口令:导入时 password = sha256(证号后 6 位),而证号即登录
    账号 => 不同步重算则学生「账号对、口令错」(UI 上看不出来)。4 分支:
    测试数据 / 已激活 / 已是 bcrypt => 不动 + 告警;未激活派生值 => 重算
  · 同值提交(含仅大小写差异)不发 UPDATE
  · 审计日志证号脱敏、口令只记「是否重算」

controller.updateContestant:映射 400/404/409。
router:新增 PUT /api/admin/contestants/:id(adminAuth)。破坏面定级 =
非破坏性(白名单编辑 + 可逆 + 带审计)=> 未登记 DESTRUCTIVE_ENDPOINTS.json。

判据:scripts/verify_update_contestant.mjs(42 判据 + 4 变异自证)。
行号漂移纪律:controller/admin.js 被 3 个脚本按行号引用(:33/:1556/:1570)
=> 新增一律追加文件末尾,改后 3/3 未漂移。
This commit is contained in:
2026-10-12 17:20:41 +08:00
parent f87ed306d2
commit 706ff2507e
3 changed files with 308 additions and 15 deletions

View File

@@ -119,6 +119,12 @@ module.exports = app => {
// ⚠️ 路径段固定、无通配,与既有路由无匹配冲突。
router.get('/api/admin/teams', adminAuth, controller.admin.teams);
router.post('/api/admin/import-excel', adminAuth, controller.admin.importExcel);
// 选手信息编辑(R108):让「导入之后就改不了」的历史限制成为过去 —— 身份证号 / 姓名 /
// 手机号可事后纠正(典型场景:报名表证号录入错误,人工核实后修正)。
// ⚠️ 路径段固定(/contestants/:id),与既有路由无匹配冲突;PUT 方法在 admin 段内唯一。
// 破坏面定级:非破坏性(白名单字段编辑、无批量清除语义、改动可逆且带脱敏审计日志)
// ⇒ 不登记进 DESTRUCTIVE_ENDPOINTS.json;登记册口径见该文件 note。
router.put('/api/admin/contestants/:id', adminAuth, controller.admin.updateContestant);
// 提交管理
router.get('/api/admin/submissions/:group', adminAuth, controller.admin.submissions);