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
This commit is contained in:
xuyueandClaude Opus 5 committed 2026-09-02 22:57:45 -06:00
1 parent e624c62502
commit 2c4d56b29a
41 files changed
+12081 -491

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE "contest" DROP COLUMN "allowed_ip_ranges";--> statement-breakpoint
ALTER TABLE "submission" DROP COLUMN "ip";
@@ -0,0 +1,9 @@
ALTER TABLE "user" DROP COLUMN "auth_token";--> statement-breakpoint
ALTER TABLE "user" DROP COLUMN "open_api";--> statement-breakpoint
ALTER TABLE "user" DROP COLUMN "open_api_appkey";--> statement-breakpoint
ALTER TABLE "user" DROP COLUMN "session_keys";--> statement-breakpoint
ALTER TABLE "user_profile" DROP COLUMN "blog";--> statement-breakpoint
ALTER TABLE "user_profile" DROP COLUMN "github";--> statement-breakpoint
ALTER TABLE "user_profile" DROP COLUMN "school";--> statement-breakpoint
ALTER TABLE "user_profile" DROP COLUMN "major";--> statement-breakpoint
ALTER TABLE "user_profile" DROP COLUMN "language";
@@ -0,0 +1,51 @@
ALTER TABLE "exercise" DROP CONSTRAINT "exercise_tutorial_id_6fd04055_fk_tutorial_id";
--> statement-breakpoint
ALTER TABLE "flowchart_submission" DROP CONSTRAINT "flowchart_submission_problem_id_8551edbf_fk_problem_id";
--> statement-breakpoint
ALTER TABLE "message" DROP CONSTRAINT "message_submission_id_2fdf8a47_fk_submission_id";
--> statement-breakpoint
ALTER TABLE "problem_tags" DROP CONSTRAINT "problem_tags_problem_id_866ecb8d_fk_problem_id";
--> statement-breakpoint
ALTER TABLE "problem_tags" DROP CONSTRAINT "problem_tags_problemtag_id_72d20571_fk_problem_tag_id";
--> statement-breakpoint
ALTER TABLE "problemset_badge" DROP CONSTRAINT "problemset_badge_problemset_id_6cb6c74f_fk_problemset_id";
--> statement-breakpoint
ALTER TABLE "problemset_problem" DROP CONSTRAINT "problemset_problem_problem_id_fff2d686_fk_problem_id";
--> statement-breakpoint
ALTER TABLE "problemset_problem" DROP CONSTRAINT "problemset_problem_problemset_id_350d17fb_fk_problemset_id";
--> statement-breakpoint
ALTER TABLE "problemset_progress" DROP CONSTRAINT "problemset_progress_problemset_id_20a9632e_fk_problemset_id";
--> statement-breakpoint
ALTER TABLE "problemset_submission" DROP CONSTRAINT "problemset_submission_problem_id_5629b105_fk_problem_id";
--> statement-breakpoint
ALTER TABLE "problemset_submission" DROP CONSTRAINT "problemset_submission_problemset_id_85290e17_fk_problemset_id";
--> statement-breakpoint
ALTER TABLE "problemset_submission" DROP CONSTRAINT "problemset_submission_submission_id_78e2b807_fk_submission_id";
--> statement-breakpoint
ALTER TABLE "reaction" DROP CONSTRAINT "reaction_problem_id_a7f3b9f3_fk_problem_id";
--> statement-breakpoint
ALTER TABLE "user_achievement" DROP CONSTRAINT "user_achievement_achievement_id_29db600d_fk_achievement_id";
--> statement-breakpoint
ALTER TABLE "user_badge" DROP CONSTRAINT "user_badge_badge_id_92a983e9_fk_problemset_badge_id";
--> statement-breakpoint
ALTER TABLE "user_profile" DROP CONSTRAINT "user_profile_user_id_8fdce8e2_fk_user_id";
--> statement-breakpoint
ALTER TABLE "user_stat" DROP CONSTRAINT "user_stat_user_id_73337fc0_fk_user_id";
--> statement-breakpoint
ALTER TABLE "exercise" ADD CONSTRAINT "exercise_tutorial_id_6fd04055_fk_tutorial_id" FOREIGN KEY ("tutorial_id") REFERENCES "public"."tutorial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "flowchart_submission" ADD CONSTRAINT "flowchart_submission_problem_id_8551edbf_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "message" ADD CONSTRAINT "message_submission_id_2fdf8a47_fk_submission_id" FOREIGN KEY ("submission_id") REFERENCES "public"."submission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problem_tags" ADD CONSTRAINT "problem_tags_problem_id_866ecb8d_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problem_tags" ADD CONSTRAINT "problem_tags_problemtag_id_72d20571_fk_problem_tag_id" FOREIGN KEY ("problemtag_id") REFERENCES "public"."problem_tag"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_badge" ADD CONSTRAINT "problemset_badge_problemset_id_6cb6c74f_fk_problemset_id" FOREIGN KEY ("problemset_id") REFERENCES "public"."problemset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_problem" ADD CONSTRAINT "problemset_problem_problem_id_fff2d686_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_problem" ADD CONSTRAINT "problemset_problem_problemset_id_350d17fb_fk_problemset_id" FOREIGN KEY ("problemset_id") REFERENCES "public"."problemset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_progress" ADD CONSTRAINT "problemset_progress_problemset_id_20a9632e_fk_problemset_id" FOREIGN KEY ("problemset_id") REFERENCES "public"."problemset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_submission" ADD CONSTRAINT "problemset_submission_problem_id_5629b105_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_submission" ADD CONSTRAINT "problemset_submission_problemset_id_85290e17_fk_problemset_id" FOREIGN KEY ("problemset_id") REFERENCES "public"."problemset"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "problemset_submission" ADD CONSTRAINT "problemset_submission_submission_id_78e2b807_fk_submission_id" FOREIGN KEY ("submission_id") REFERENCES "public"."submission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reaction" ADD CONSTRAINT "reaction_problem_id_a7f3b9f3_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_achievement" ADD CONSTRAINT "user_achievement_achievement_id_29db600d_fk_achievement_id" FOREIGN KEY ("achievement_id") REFERENCES "public"."achievement"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_badge" ADD CONSTRAINT "user_badge_badge_id_92a983e9_fk_problemset_badge_id" FOREIGN KEY ("badge_id") REFERENCES "public"."problemset_badge"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_profile" ADD CONSTRAINT "user_profile_user_id_8fdce8e2_fk_user_id" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_stat" ADD CONSTRAINT "user_stat_user_id_73337fc0_fk_user_id" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+21
View File
@@ -57,6 +57,27 @@
"when": 1788408053304,
"tag": "0007_add_submission_problemset_id",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1788409130862,
"tag": "0008_drop_ip_columns",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1788409690565,
"tag": "0009_drop_django_dead_columns",
"breakpoints": true
},
{
"idx": 10,
"version": "7",
"when": 1788409961010,
"tag": "0010_fk_cascade_on_delete",
"breakpoints": true
}
]
}
-260
View File
@@ -1,260 +0,0 @@
import { relations } from "drizzle-orm/relations";
import { user, aiAnalysis, announcement, contest, problem, flowchartSubmission, message, submission, tutorial, exercise, problemset, problemsetProblem, problemsetProgress, problemsetSubmission, reaction, problemTags, problemTag, userStat, achievement, userAchievement, problemsetBadge, userBadge, userProfile, acmContestRank } from "./schema";
export const aiAnalysisRelations = relations(aiAnalysis, ({one}) => ({
user: one(user, {
fields: [aiAnalysis.userId],
references: [user.id]
}),
}));
export const userRelations = relations(user, ({many}) => ({
aiAnalyses: many(aiAnalysis),
announcements: many(announcement),
contests: many(contest),
flowchartSubmissions: many(flowchartSubmission),
messages_recipientId: many(message, {
relationName: "message_recipientId_user_id"
}),
messages_senderId: many(message, {
relationName: "message_senderId_user_id"
}),
problemsets: many(problemset),
problemsetProgresses: many(problemsetProgress),
problemsetSubmissions: many(problemsetSubmission),
reactions: many(reaction),
problems: many(problem),
tutorials: many(tutorial),
userStats: many(userStat),
userAchievements: many(userAchievement),
userBadges: many(userBadge),
userProfiles: many(userProfile),
acmContestRanks: many(acmContestRank),
}));
export const announcementRelations = relations(announcement, ({one}) => ({
user: one(user, {
fields: [announcement.createdById],
references: [user.id]
}),
}));
export const contestRelations = relations(contest, ({one, many}) => ({
user: one(user, {
fields: [contest.createdById],
references: [user.id]
}),
problems: many(problem),
submissions: many(submission),
acmContestRanks: many(acmContestRank),
}));
export const flowchartSubmissionRelations = relations(flowchartSubmission, ({one}) => ({
problem: one(problem, {
fields: [flowchartSubmission.problemId],
references: [problem.id]
}),
user: one(user, {
fields: [flowchartSubmission.userId],
references: [user.id]
}),
}));
export const problemRelations = relations(problem, ({one, many}) => ({
flowchartSubmissions: many(flowchartSubmission),
problemsetProblems: many(problemsetProblem),
problemsetSubmissions: many(problemsetSubmission),
reactions: many(reaction),
contest: one(contest, {
fields: [problem.contestId],
references: [contest.id]
}),
user: one(user, {
fields: [problem.createdById],
references: [user.id]
}),
problemTags: many(problemTags),
submissions: many(submission),
}));
export const messageRelations = relations(message, ({one}) => ({
user_recipientId: one(user, {
fields: [message.recipientId],
references: [user.id],
relationName: "message_recipientId_user_id"
}),
user_senderId: one(user, {
fields: [message.senderId],
references: [user.id],
relationName: "message_senderId_user_id"
}),
submission: one(submission, {
fields: [message.submissionId],
references: [submission.id]
}),
}));
export const submissionRelations = relations(submission, ({one, many}) => ({
messages: many(message),
problemsetSubmissions: many(problemsetSubmission),
contest: one(contest, {
fields: [submission.contestId],
references: [contest.id]
}),
problem: one(problem, {
fields: [submission.problemId],
references: [problem.id]
}),
}));
export const exerciseRelations = relations(exercise, ({one}) => ({
tutorial: one(tutorial, {
fields: [exercise.tutorialId],
references: [tutorial.id]
}),
}));
export const tutorialRelations = relations(tutorial, ({one, many}) => ({
exercises: many(exercise),
user: one(user, {
fields: [tutorial.createdById],
references: [user.id]
}),
}));
export const problemsetRelations = relations(problemset, ({one, many}) => ({
user: one(user, {
fields: [problemset.createdById],
references: [user.id]
}),
problemsetProblems: many(problemsetProblem),
problemsetProgresses: many(problemsetProgress),
problemsetSubmissions: many(problemsetSubmission),
problemsetBadges: many(problemsetBadge),
}));
export const problemsetProblemRelations = relations(problemsetProblem, ({one}) => ({
problem: one(problem, {
fields: [problemsetProblem.problemId],
references: [problem.id]
}),
problemset: one(problemset, {
fields: [problemsetProblem.problemsetId],
references: [problemset.id]
}),
}));
export const problemsetProgressRelations = relations(problemsetProgress, ({one}) => ({
problemset: one(problemset, {
fields: [problemsetProgress.problemsetId],
references: [problemset.id]
}),
user: one(user, {
fields: [problemsetProgress.userId],
references: [user.id]
}),
}));
export const problemsetSubmissionRelations = relations(problemsetSubmission, ({one}) => ({
problem: one(problem, {
fields: [problemsetSubmission.problemId],
references: [problem.id]
}),
problemset: one(problemset, {
fields: [problemsetSubmission.problemsetId],
references: [problemset.id]
}),
submission: one(submission, {
fields: [problemsetSubmission.submissionId],
references: [submission.id]
}),
user: one(user, {
fields: [problemsetSubmission.userId],
references: [user.id]
}),
}));
export const reactionRelations = relations(reaction, ({one}) => ({
problem: one(problem, {
fields: [reaction.problemId],
references: [problem.id]
}),
user: one(user, {
fields: [reaction.userId],
references: [user.id]
}),
}));
export const problemTagsRelations = relations(problemTags, ({one}) => ({
problem: one(problem, {
fields: [problemTags.problemId],
references: [problem.id]
}),
problemTag: one(problemTag, {
fields: [problemTags.problemtagId],
references: [problemTag.id]
}),
}));
export const problemTagRelations = relations(problemTag, ({many}) => ({
problemTags: many(problemTags),
}));
export const userStatRelations = relations(userStat, ({one}) => ({
user: one(user, {
fields: [userStat.userId],
references: [user.id]
}),
}));
export const userAchievementRelations = relations(userAchievement, ({one}) => ({
achievement: one(achievement, {
fields: [userAchievement.achievementId],
references: [achievement.id]
}),
user: one(user, {
fields: [userAchievement.userId],
references: [user.id]
}),
}));
export const achievementRelations = relations(achievement, ({many}) => ({
userAchievements: many(userAchievement),
}));
export const userBadgeRelations = relations(userBadge, ({one}) => ({
problemsetBadge: one(problemsetBadge, {
fields: [userBadge.badgeId],
references: [problemsetBadge.id]
}),
user: one(user, {
fields: [userBadge.userId],
references: [user.id]
}),
}));
export const problemsetBadgeRelations = relations(problemsetBadge, ({one, many}) => ({
userBadges: many(userBadge),
problemset: one(problemset, {
fields: [problemsetBadge.problemsetId],
references: [problemset.id]
}),
}));
export const userProfileRelations = relations(userProfile, ({one}) => ({
user: one(user, {
fields: [userProfile.userId],
references: [user.id]
}),
}));
export const acmContestRankRelations = relations(acmContestRank, ({one}) => ({
contest: one(contest, {
fields: [acmContestRank.contestId],
references: [contest.id]
}),
user: one(user, {
fields: [acmContestRank.userId],
references: [user.id]
}),
}));
+28 -30
View File
@@ -5,6 +5,12 @@
// django_session)已由 0002_drop_django_leftovers 删除,drizzle.config.ts 的
// tablesFilter 随之移除。库里现在就是这 27 张业务表。
//
// 2026-09-02:又清了一批只有 Django 时代写过、OJ2 一次都没读过的列 ——
// 0008 删比赛 IP 白名单与 submission.ip(判题机的 judge_server.ip 保留,那是运维数据),
// 0009 删 user 的 auth_token / open_api / open_api_appkey / session_keys 和
// user_profile 的 blog / github / school / major / language。**删的判据是「全仓零读取」**,
// 不是「看着没用」:raw_password 同样刺眼却是在用的(老师要能查学生密码),别一起清掉。
//
// 手工修正(都是 `pull` 自己没法无损 round-trip 的地方,改回去会让 generate 产生假 diff,
// 详见 CLAUDE.md「改 schema 走 drizzle migration」):
// * bigint identity 的 maxValue 用字符串,不能写成 JS number 字面量(会丢精度)。
@@ -16,6 +22,7 @@
// problem、contest、submission)都是 int4。现存最大 id 一万出头,确实都用不上 bigint,
// 但 2026-08-26 评估后决定**不改**:省 4 字节/行毫无意义,ALTER TYPE 要重写整表并拿
// ACCESS EXCLUSIVE 锁,而且其中 6 处 id 被外键绑着得连坐。别再提这件事了。
import type { AdminType, ProblemPermission } from "@oj2/contract"
import { pgTable, index, foreignKey, primaryKey, bigint, text, jsonb, timestamp, integer, boolean, serial, doublePrecision, varchar, unique, uniqueIndex } from "drizzle-orm/pg-core"
import { sql } from "drizzle-orm"
@@ -90,7 +97,6 @@ export const contest = pgTable("contest", {
lastUpdateTime: timestamp("last_update_time", { withTimezone: true, mode: 'string' }).notNull(),
visible: boolean().notNull(),
createdById: integer("created_by_id").notNull(),
allowedIpRanges: jsonb("allowed_ip_ranges").notNull(),
tag: text().notNull(),
}, (table) => [
index("contest_created_by_id_a763ca7e").using("btree", table.createdById.asc().nullsLast().op("int4_ops")),
@@ -129,7 +135,7 @@ export const flowchartSubmission = pgTable("flowchart_submission", {
columns: [table.problemId],
foreignColumns: [problem.id],
name: "flowchart_submission_problem_id_8551edbf_fk_problem_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -164,7 +170,7 @@ export const message = pgTable("message", {
columns: [table.submissionId],
foreignColumns: [submission.id],
name: "message_submission_id_2fdf8a47_fk_submission_id"
}),
}).onDelete("cascade"),
]);
export const judgeServer = pgTable("judge_server", {
@@ -195,7 +201,7 @@ export const exercise = pgTable("exercise", {
columns: [table.tutorialId],
foreignColumns: [tutorial.id],
name: "exercise_tutorial_id_6fd04055_fk_tutorial_id"
}),
}).onDelete("cascade"),
]);
export const optionsSysoptions = pgTable("options_sysoptions", {
@@ -244,12 +250,12 @@ export const problemsetProblem = pgTable("problemset_problem", {
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problemset_problem_problem_id_fff2d686_fk_problem_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_problem_problemset_id_350d17fb_fk_problemset_id"
}),
}).onDelete("cascade"),
unique("unique_problemset_problem").on(table.problemId, table.problemsetId),
]);
@@ -274,7 +280,7 @@ export const problemsetProgress = pgTable("problemset_progress", {
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_progress_problemset_id_20a9632e_fk_problemset_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -304,17 +310,17 @@ export const problemsetSubmission = pgTable("problemset_submission", {
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problemset_submission_problem_id_5629b105_fk_problem_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_submission_problemset_id_85290e17_fk_problemset_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.submissionId],
foreignColumns: [submission.id],
name: "problemset_submission_submission_id_78e2b807_fk_submission_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -336,7 +342,7 @@ export const reaction = pgTable("reaction", {
columns: [table.problemId],
foreignColumns: [problem.id],
name: "reaction_problem_id_a7f3b9f3_fk_problem_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -414,12 +420,12 @@ export const problemTags = pgTable("problem_tags", {
columns: [table.problemId],
foreignColumns: [problem.id],
name: "problem_tags_problem_id_866ecb8d_fk_problem_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.problemtagId],
foreignColumns: [problemTag.id],
name: "problem_tags_problemtag_id_72d20571_fk_problem_tag_id"
}),
}).onDelete("cascade"),
unique("problem_tags_problem_id_problemtag_id_318459d1_uniq").on(table.problemId, table.problemtagId),
]);
@@ -443,7 +449,6 @@ export const submission = pgTable("submission", {
shared: boolean().default(false).notNull(),
statisticInfo: jsonb("statistic_info").default({}).notNull(),
username: text().notNull(),
ip: text(),
// 来源题单:学生从题单入口(/problemset/:id/problem/:pid)提交时记下来,
// 提交列表据此标出「这条来自题单」。**只是来源标记**,题单进度、奖章一概不看它,
// 那些由判完之后的 recordSolvedProblem 按「已加入且含这道题的所有题单」记账。
@@ -534,7 +539,7 @@ export const userStat = pgTable("user_stat", {
columns: [table.userId],
foreignColumns: [user.id],
name: "user_stat_user_id_73337fc0_fk_user_id"
}),
}).onDelete("cascade"),
unique("user_stat_user_id_key").on(table.userId),
]);
@@ -556,7 +561,7 @@ export const userAchievement = pgTable("user_achievement", {
columns: [table.achievementId],
foreignColumns: [achievement.id],
name: "user_achievement_achievement_id_29db600d_fk_achievement_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -579,7 +584,7 @@ export const userBadge = pgTable("user_badge", {
columns: [table.badgeId],
foreignColumns: [problemsetBadge.id],
name: "user_badge_badge_id_92a983e9_fk_problemset_badge_id"
}),
}).onDelete("cascade"),
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
@@ -592,22 +597,17 @@ export const userProfile = pgTable("user_profile", {
id: serial().primaryKey().notNull(),
acmProblemsStatus: jsonb("acm_problems_status").default({}).notNull(),
avatar: text().notNull(),
blog: varchar({ length: 200 }),
mood: text(),
acceptedNumber: integer("accepted_number").default(0).notNull(),
submissionNumber: integer("submission_number").default(0).notNull(),
github: text(),
school: text(),
major: text(),
userId: integer("user_id").notNull(),
realName: text("real_name"),
language: text(),
}, (table) => [
foreignKey({
columns: [table.userId],
foreignColumns: [user.id],
name: "user_profile_user_id_8fdce8e2_fk_user_id"
}),
}).onDelete("cascade"),
unique("user_profile_user_id_key").on(table.userId),
]);
@@ -644,13 +644,11 @@ export const user = pgTable("user", {
username: text().notNull(),
email: text(),
createTime: timestamp("create_time", { withTimezone: true, mode: 'string' }),
adminType: text("admin_type").notNull(),
authToken: text("auth_token"),
openApi: boolean("open_api").default(false).notNull(),
openApiAppkey: text("open_api_appkey"),
// $type 只是 TS 层的收窄,不产生任何 SQL —— 让 eq(schema.user.adminType, "...")
// 里的角色名也受类型检查。运行时的兜底仍在 auth/session.ts 的 toAdminType。
adminType: text("admin_type").notNull().$type<AdminType>(),
isDisabled: boolean("is_disabled").default(false).notNull(),
problemPermission: text("problem_permission").notNull(),
sessionKeys: jsonb("session_keys").default([]).notNull(),
problemPermission: text("problem_permission").notNull().$type<ProblemPermission>(),
rawPassword: varchar("raw_password", { length: 20 }),
className: text("class_name"),
}, (table) => [
@@ -673,7 +671,7 @@ export const problemsetBadge = pgTable("problemset_badge", {
columns: [table.problemsetId],
foreignColumns: [problemset.id],
name: "problemset_badge_problemset_id_6cb6c74f_fk_problemset_id"
}),
}).onDelete("cascade"),
]);
/**