20ecc05f63
fix(Minor M2): 比赛权限加中间件兜底
...
旧后端用 @check_contest_permission 装饰器,漏挂一眼看得出来;新后端手工在 handler
里调 canAccessContest,漏调一次就是静默放行,而且这类路由挂的是 optionalAuth
(本身不拦人),从路由注册那一行完全看不出它受保护。
新增 requireContestAccess(checkType, paramName),把「取比赛 → 404 → 鉴权 →
401/403」四步收进注册行。手工调用点 5 → 2:
- GET /contests/:id/access —— 报告权限而非强制,不能 403,留手工
- POST /submissions —— 比赛 id 来自请求体,中间件跑时 body 还没解析,留手工
两处都就地写了说明。canAccessContest 改成泛型,因为 Hono 的 Context 在
Variables 上逆变,写死 Context<AppEnv> 与 ContestEnv 不兼容。
实测拦截行为不变:匿名 401、登录 200、密码赛未过密码 403。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 07:00:58 -06:00
97c54d38b5
fix(Minor M1): 角色判断改回白名单
...
isAdminRole 之前写成 `adminType !== "Regular User"`。当前四种角色下与旧后端等价,
但将来新增任何角色(助教、家长……)都会默认拿到管理员权限,包括 canViewSubmission
里的「看所有人代码」——加角色的人多半想不到要回来改这里。
改成显式列举,对齐 account/models.py:65-73。实测四种已知角色行为不变,
虚构的新角色「助教」现在默认不是管理员。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 07:00:58 -06:00
44f6f27841
chore(阶段3): 删除阶段 1 的临时验证物
...
GET /api/dev/problems、dev-problems.vue、对应路由项,以及只服务于它的
problemSummarySchema。阶段 1 用来验证「能从真实库读出一道题」,已完成使命。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 06:48:20 -06:00
836d97847e
feat(阶段3): 补齐用户侧依赖的三个 admin 端点
...
重判、提交统计、流程图统计这三条挂在旧后端的 admin 路由下,权限也确实是
teacher/super admin,但入口在用户侧页面里(提交列表页的重判按钮、两个统计面板)。
不做完,阶段 3 的出口标准「用户侧全部功能跑在新后端上」就不成立 —— 按 URL
前缀切阶段会漏掉它们。
- POST submissions/:id/rejudge ← GET admin/submission/rejudge
- GET submissions/statistics ← GET admin/submission/statistics
- GET flowcharts/statistics ← GET admin/flowchart/statistics
几处对齐旧后端的细节:
- AST_CHECK_FAILED(10) 与 ACCEPTED(0) 同算通过
- 完成度先用原始花名册人数算、再修正 person_count,顺序照搬,兜住「学生已删号
但提交记录还在」
- 有提交但零通过的学生,两个名单里都不出现(旧后端同样口径)
- 词云用 @node-rs/jieba,STOPWORDS 与 38 个自定义词逐词照搬;jieba@2 没有
insertWord,改用 loadDict 加载用户词典
- avgScore 分母是有分数的条数,对齐 Django Avg() 跳过 NULL
rejudge 的 jobId 带时间戳。队列保留最近 100 个已完成任务,沿用 submissionId
做 jobId 的话 BullMQ 会认为任务已存在,重判会静默变成空操作。
correctRate 改成数值不带 %,展示格式化交给前端。stripClassPrefix 用
startsWith+slice 而不是 replace,前缀对不上时不会从中间截出乱码;site.ts
原有的 replace 写法一并改掉。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 06:48:05 -06:00
f548aef9f0
fix(阶段3): 补齐 contestId 脱敏与仓库根 .env 加载
...
两处是首轮六条修复的收尾,均由验证过程暴露:
1. contestId 未脱敏 —— 旧后端 SubmissionSafeModelSerializer 排除的是
info/contest/ip 三个字段,首轮只处理了 info 与 ip。
2. 根 .env 读不到 —— Bun 只自动加载 cwd 下的 .env,而启动方式
(bun run --filter '@oj2/api' dev) 会把 cwd 切到 apps/api/,
于是 .env.example 教人写在仓库根的 JUDGE_SERVER_TOKEN 静默失效,
后端降级成随机 token,判题全部卡在 PENDING。config 改为显式补读
根 .env,且只填充未设置的键(真实环境变量与 cwd 下的 .env 优先)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 02:12:31 -06:00
b7adf2993d
fix(阶段3): 去掉判题机 token 的弱默认值
...
F5:token 校验实现本身是对的(用了 timingSafeEqual),问题是缺省值
"oj2-dev-token" 写死在仓库里 —— 写死在仓库里的 token 等于没有 token。
后端改为对齐旧后端 options/options.py:93 的 fail-safe:
env 缺失时生成随机值并在启动日志里告警,判题机连不上,但不会静默用弱默认值。
compose 改为 ${OJ2_JUDGE_TOKEN:?...},未设置直接报错退出。
本地开发怎么设写在 docker/compose.dev.yml 顶部与 .env.example 里:
两个变量名不同(判题机镜像认 TOKEN,后端认 JUDGE_SERVER_TOKEN)但值必须相同。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 01:59:27 -06:00
8237909d00
fix(阶段3): 提交可见性守卫补上匿名,详情脱敏,提交接口加限流
...
F3:`isRegularUser(user)` 对匿名用户(user 为 null)返回 false,
`submission_list_show_all` 关闭时守卫整体短路 —— 匿名能看到全部提交,
权限反而大于登录学生。实测开关关闭时匿名 total=23、登录学生 total=0。
而关闭这个开关的典型场景正是考试。改为 `!isAdminRole(user)`(非管理员即受限),
并删掉 isRegularUser(全仓只有这一处调用,留着就是下一个坑)。
F4:提交详情把 info(含每个测试点的 test_case 编号与 output_md5)与 ip
按「是不是自己的提交」下发。旧后端 submission/views/oj.py 把关的是角色:
is_admin_role() 决定用 SubmissionModelSerializer 还是
SubmissionSafeModelSerializer(exclude=("info", "contest", "ip"))。改为仅管理员可见。
F6:旧后端 SubmissionAPI.throttling 的 TokenBucket 没搬过来。
按旧参数重建(options/options.py:120 的 user 桶:capacity 20、fill_rate 0.03、
default_capacity 10,实际值仍以数据库 throttling 配置项为准),
落在 redis 上并改用 Lua 脚本做成原子操作 —— 旧实现自己注明「不是线程安全的」,
而限流要挡的正是并发突发。挂点位置与旧后端一致:比赛权限校验之后、取题目之前。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 01:59:19 -06:00
b4b61af6b0
fix(阶段3): 匿名不可读用户档案,真名改为默认不下发
...
F1:GET /profiles/:username 只挂了 optionalAuth、handler 内无登录判断,
匿名可读 email、adminType、className、lastLogin。用户名又能经 /rankings/users
公开枚举,等于可以无 cookie 批量收集全校学生的邮箱与最后登录时间。
handler 开头补上未登录即返回空,对齐旧后端 account/views/oj.py 的
UserProfileAPI.get 首行 `if not user.is_authenticated: return self.success()`。
F2:旧后端把「是否下发真名」做成 UsernameSerializer(need_real_name=False)
的默认关闭开关,全仓 11 处调用只有比赛榜单一处显式打开;新后端没搬这一层,
真名随用户对象无条件下发,13 个下发点里 8 个匿名可达。
这里补回同一层:helpers.ts 新增 sampleUser(),realName 默认不下发,
需要的地方显式传 { includeRealName: true }。12 个下发点改为走这个函数,
只有比赛榜单一处打开(对齐 contest/serializers.py:84 的 is_contest_admin)。
没有逐处删字段 —— 那样下次新增端点还会重犯。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 01:59:05 -06:00
8c00cdc947
feat(阶段3): oj 侧端点铺开(基线提交,未经评审)
...
由外部 agent (Codex) 在本会话额度中断期间完成。原样提交作为基线,
后续修复单独成 commit,便于区分与回退。
覆盖 oj 侧 65 个端点,新增 9 组路由(account/achievement/ai/classroom/
content/contest/flowchart/problemset/site)与对应 Zod 契约。
已核验:tsc --noEmit 退出码 0;API 可启动;/api/problems 返回真实数据;
judge 与 flowchart worker 均 ready。
未核验:权限边界与数据泄露,评审进行中。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-07 01:25:36 -06:00
ec274419c3
Build Phase 2 judge vertical slice
2026-08-06 22:42:39 -06:00
3c975e85ee
feat(阶段1): Hono 应用与题目列表接口,读通本地真实数据
2026-08-06 21:14:58 -06:00
0f0a5cb65a
fix(阶段1): 修正 problem 主键与展示编号映射
2026-08-06 21:11:44 -06:00
f635737453
feat(阶段1): drizzle schema 从本地库生成并剪掉 Django 框架表
...
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-08-06 20:45:51 -06:00