|
|
bc54fbff98
|
feat(阶段4): 站点配置 / 判题机 / 孤儿用例 / 概览 / 图片上传
GET/POST admin/website
GET admin/judge-servers
PUT admin/judge-servers/:id
DELETE admin/judge-servers/:hostname
GET/DELETE admin/orphan-test-cases
GET admin/dashboard
GET admin/random-usernames
POST admin/upload-image
顺带补上配置广播:旧后端改配置会经 WebSocket 推给所有开着页面的人,改完立刻生效。
新后端只服务 /ws/submissions,前端的 ConfigWebSocket 还连着旧 Django Channels。
现在加了 /ws/config 通道(同一个 Bun.serve 只能挂一个 handler,用 socket data 上的
kind 区分),前端 ConfigWebSocket 改走 /ws2/config。
几处判断:
- **判活不能比字符串**。库里 timestamptz 形如 `2026-08-07 13:42:50+00`(空格分隔),
toISOString() 是 `...T13:42:44.000Z`(T 分隔),字典序空格 < 'T',同一天的心跳永远
小于阈值 —— 所有判题机都会显示离线。实测确实复现(dashboard 说 1 台在线、列表却
两台全 abnormal),已改为 Date.parse 后比较。
- 删指定的孤儿用例时**先确认它确实是孤儿**。旧后端不校验,一个手抖的 id 就能删掉在用
题目的测试数据,而测试数据没有别处备份。
- 图片上传的文件名完全由服务端生成,不带用户提供的任何一段;另加 10MB 上限 ——
旧后端靠 nginx 兜,但机房那台机器盘写满之后判题也会一起挂。
- 停用判题机后不再 process_pending_task():任务在 BullMQ 里排着,worker 恢复自己接着
消费,不存在旧自研分发器那种「没有新提交就一直 waiting」的问题。
- dashboard 不再下发 env.FORCE_HTTPS / STATIC_CDN_HOST,前端从未读过。
实测:学生 403;配置读写回读 + oj 侧 /site 同步生效 + 还原;判题机列表带 token、
状态判定正确(一台 normal 一台 abnormal,与 dashboard 计数一致);删不存在 404;
删非孤儿用例 404;随机点名缺班级号 400。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-07 07:43:38 -06:00 |
|
|
|
7e1a782747
|
feat(阶段4): 用户管理 + 成就管理;补上 rescan 与 contest_joined
GET/POST/DELETE admin/users (DELETE 走 body 传 ids)
GET/PUT admin/users/:id
POST admin/users/:id/reset-password
GET admin/achievement-metrics
GET/POST admin/achievements
GET/PUT/DELETE admin/achievements/:id
顺带补了新后端缺失的两块(不补的话成就后台建出来的东西是坏的):
1. **rescanAchievement**:旧后端 `rescan_achievement` 的对应实现。判定平时只在判题
结算时发生,后台新建成就或调低阈值不会自动补发,必须显式扫一遍。补发标记
backfilled=true —— 前端据此只显示「已获得」不显示日期,否则一次补发会给几百人
盖同一个时间戳,把「最近获得」板块冲垮。
触发判据包含 metric 与 visible 的变化,不只看 operator/threshold:换维度、
以及从下架改上架(草稿期已达标的人)这两种都会漏。
2. **contest_joined 指标整个漏掉了**。旧 METRIC_REGISTRY 有 18 个指标,新后端只算
17 个,配在这个指标上的成就永远解锁不了。已补上计算,并把注册表抽成
services/achievement-metrics.ts 作为单一事实源 —— 后台下拉框和参数校验都读它,
避免「下拉框里选得到但没人算」这种组合。
用户管理的几处要点:
- className 解析位数不对**直接报错不猜**。猜错会把 class_name 存歪,而剥前缀显示
姓名、班级下拉、统计页都依赖它准确。
- problem_permission 按 admin_type 归一(超管恒 All、普通用户恒 None),否则把超管
降级成普通用户后他还留着 All。
- 改用户名要同步 submission.username 这个冗余列,否则历史提交查不到。
- openApi 已开着就不重置 appkey,否则每次保存用户都把对方的 key 换掉。
- **删除用户不复刻 Django 的应用层级联硬删**。用户是被引用最广的一张表,改成让
数据库外键拦下来:撞外键说明还有历史数据,应当禁用而不是删除,返回 409 并说明。
实测:学生 403;导入 2 人 / 重复 409 / 班级号位数报错文案正确;改名+降权后
permission 归一为 None、重名 409;重置密码 6 位无 0;删自己 400;
成就指标 18 项、新建后补发 unlockCount=2、野指标与野稀有度均 400。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-07 07:27:36 -06:00 |
|
|
|
af9cb796bd
|
feat(阶段4): 教程 / 练习 / AI 学情报告的后台接口
GET/POST admin/tutorials
GET/PUT/DELETE admin/tutorials/:id
PUT admin/tutorials/:id/visibility
GET admin/tutorials/:id/exercises
POST admin/exercises
PUT/DELETE admin/exercises/:id
GET admin/ai/reports (?pinnedOnly=true 不分页)
GET admin/ai/reports/:id
POST admin/ai/reports/:id/pin
几处判断:
- 练习改成挂在教程下的嵌套路径,旧后端是 ?tutorial_id= 查询参数。本来就是一对多的
从属关系,嵌套更贴事实,也省掉「忘了传 tutorial_id」这类错误。
- 删教程必须先删练习。Django 的 on_delete=CASCADE 是应用层实现的,库里外键实际是
NO ACTION(核对过 pg_constraint.confdeltype='a'),直接删会撞外键变 500。
已实测:带 2 个练习的教程能正常删掉且练习一并清除。**后台每个 DELETE 都要照此
核一遍子表**,这是本阶段的通用陷阱。
- 改可见性不动 updatedAt —— 上下架不是内容修改,改了会打乱按更新时间排序的直觉。
- AI 报告的 data / systemPrompt / userPrompt 一律不下发,里面是喂给模型的原始
学情数据与提示词。列表只给 120 字摘要,与旧 AIAnalysisListSerializer 一致。
实测:匿名 401 / 学生 403 / 超管 200;教程练习增删改查、非法 type 400、
挂到不存在的教程 404、置顶互斥(同一学生至多一份)、再钉一次取消,全部符合预期。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-07 07:10:34 -06:00 |
|
|
|
9a6c3ba225
|
feat(阶段4): 后台地基 + 公告管理
地基:
- auth/middleware.ts 加四个角色守卫 requireAdmin / requireTeacher /
requireSuperAdmin / requireProblemPermission,对应旧 account/decorators.py 的
四个装饰器。未登录 401 login-required、角色不够 403 permission-denied,
与前端 api2 拦截器按 code 分流的两支对上。
- routes/admin/ 目录 + 总入口挂在 /api/admin。角色守卫由各子路由自己挂,
不在总入口兜一层 —— 否则「这个接口要什么角色」从注册行看不出来,
正是阶段 3 Minor M2 踩过的坑。
- packages/contract/src/admin.ts 独立放后台契约。同一张表两侧下发的字段集不同
(后台要 visible,oj 侧连键都不该出现),混在一起迟早有人在 oj 侧复用后台那个。
- utils/legacy.ts:把 toLegacy / legacyResponse 从 oj/api.ts 抽出来共用。
admin 侧组件同样读 snake_case,走同一层适配,组件不动。
公告管理(旧 /api/admin/announcement 一个路径四个动词)拆成:
GET/POST admin/announcements
GET/PUT/DELETE admin/announcements/:id
一处有意不对齐旧后端:删除不存在的公告,旧后端 filter().delete() 静默成功,
这里返回 404 —— 后台是人手点删除,静默成功会让人以为删掉了,刷新后它还在。
实测:匿名 401 / 学生 403 / 超管 200;增删改查、列表不含 content、
空标题 400、不存在 404 全部符合预期。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
2026-08-07 07:06:22 -06:00 |
|