Files
OJ2/packages/contract/src/roles.ts
yuetsh 2c4d56b29a refactor(后端): 清掉 Django 遗留的死列与手工级联,角色字符串收成一份
三条迁移,一次部署(0008/0009 含 DROP COLUMN,需要 OJ2_ALLOW_DESTRUCTIVE=1):

- 0008 删 IP 相关:比赛 IP 白名单(前端本来就没有输入框,detail.vue 无条件置空)、
  submission.ip(前端从未显示过)、以及一次都没被调用过的 IP 限流桶。
  judge_server.ip 是运维数据,保留。
- 0009 删九个只有 Django 时代写过、OJ2 一次都没读过的列:user 的 auth_token /
  open_api / open_api_appkey / session_keys,user_profile 的 blog / github /
  school / major / language。open_api 后台连开关都没有,那段「已经开着就不重置
  appkey」的逻辑从上线起没进过 if。判据是「全仓零读取」而不是「看着没用」——
  raw_password 同样刺眼却是在用的,别一起清掉。
- 0010 给 17 条外键补上删除动作,不再是 Django 留下的一律 NO ACTION。父行消失后
  必然无意义、且不构成学生留痕的走 CASCADE(中间表、题单/教程/成就的组成部分、
  user_profile 与 user_stat);需要人看见的继续拦着——submission.problem_id、
  以及 user 的绝大多数外键,删用户撞外键会被 handler 翻译成「请改为禁用账号」,
  这是有意的:全 CASCADE 会静默抹掉成就与进度,而 submission.user_id 压根没有
  外键,结果是一半删一半留。六处手工级联随之删掉。

角色字符串收进 packages/contract/src/roles.ts:原先 ADMIN_ROLES / TEACHER_ROLES
在两个文件各抄一份、学生口径在四个文件各写一遍、前端 USER_TYPE 是第三份副本。
AuthUser.adminType 与 drizzle 的列都收窄成联合类型,二十多处 `=== "Super Admin"`
从此受编译器管着($type 是纯 TS 层的,generate 确认不产生任何 SQL 变更)。

顺带删掉 db/relations.ts —— drizzle-kit pull 的产物,全仓零引用。

一处行为变化:后台用户列表传非法的 ?type= 回 400,不再静默返回空列表;界面上的
下拉只有合法值,打不到这条。

验证:tsc / vue-tsc / vite build / check:routes 全过;三条迁移在 dev 库执行,
并逐条建 fixture 走 HTTP 接口验过删除连坐与拦截(题单五张子表连坐、user_badge
二级连坐、删有提交的题目仍 409、删有表情的用户仍 409)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeJoYc2t2d7cThVqMBYrBF
2026-09-02 22:57:45 -06:00

80 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod"
/**
* 角色与题目权限的字面量。**全仓唯一的定义处。**
*
* 这些字符串是**落库的值** —— Django choices 的遗产,带空格、带大小写,`user` 表
* 一千多行存的就是它们,前端也按这些值判断菜单。所以只能新增、不能改写已有的。
*
* 收到这里之前:`auth/middleware.ts` 和 `routes/helpers.ts` 各抄了一份
* ADMIN_ROLES / TEACHER_ROLES`["Regular User", "Student Admin"]` 这个学生口径
* 在四个文件里各写一遍,另有二十多处散落的 `=== "Super Admin"`。拼错一个字母
* TypeScript 一个字都不会说,只会在运行时静默放行或静默拒绝。
*/
export const ADMIN_TYPES = [
"Regular User",
"Student Admin",
"Teacher Admin",
"Super Admin",
] as const
export type AdminType = (typeof ADMIN_TYPES)[number]
export const adminTypeSchema = z.enum(ADMIN_TYPES)
export const PROBLEM_PERMISSIONS = ["None", "Own", "All"] as const
export type ProblemPermission = (typeof PROBLEM_PERMISSIONS)[number]
export const problemPermissionSchema = z.enum(PROBLEM_PERMISSIONS)
/**
* 三个分组一律是**白名单**,对齐旧后端 `account/models.py:65-73` 的显式列举写法。
*
* 不要改成黑名单(`!== "Regular User"`):当前四种角色下两者等价,但将来新增任何角色
* (助教、家长……)都会**默认拿到管理员权限**。白名单则默认拒绝,加角色的人必须
* 回到这里才能放行,而这正是应该被逼着想一遍的地方。
*/
/** 能进后台的三种 */
export const ADMIN_ROLES: readonly AdminType[] = ["Student Admin", "Teacher Admin", "Super Admin"]
/** 老师及以上 */
export const TEACHER_ROLES: readonly AdminType[] = ["Teacher Admin", "Super Admin"]
/**
* 学生口径:排行榜、班级榜、比赛榜、自学统计都只算这两种,教师和超管不入榜。
* 注意 Student Admin 算学生 —— 他要参赛、要上榜,只是多了个后台入口。
*/
export const STUDENT_ROLES: readonly AdminType[] = ["Regular User", "Student Admin"]
/**
* 把库里读出来的裸字符串收成联合类型。**认不出来的一律降成最低权限**
* 不抛错:脏数据不该让人登不上,但更不该让人凭一个拼错的角色名拿到权限。
*/
export function toAdminType(value: string): AdminType {
return (ADMIN_TYPES as readonly string[]).includes(value) ? (value as AdminType) : "Regular User"
}
export function toProblemPermission(value: string): ProblemPermission {
return (PROBLEM_PERMISSIONS as readonly string[]).includes(value)
? (value as ProblemPermission)
: "None"
}
/**
* 按名字取值的写法(`USER_TYPE.SUPER_ADMIN`),给前端用 —— 它到处是这种比较,
* 换成数组反而更难读。和上面 ADMIN_TYPES 是同一批字符串,
* `satisfies` 保证两边不会各自漂走。
*
* 原先 `apps/web/src/utils/constants.ts` 里另抄了一份,那是这四个字符串的第三份副本。
*/
export const USER_TYPE = {
REGULAR_USER: "Regular User",
STUDENT_ADMIN: "Student Admin",
TEACHER_ADMIN: "Teacher Admin",
SUPER_ADMIN: "Super Admin",
} as const satisfies Record<string, AdminType>
export const PROBLEM_PERMISSION = {
NONE: "None",
OWN: "Own",
ALL: "All",
} as const satisfies Record<string, ProblemPermission>