feat(自学): 教程有了「所属项目」和课末的相关题,正文里的代码能直接运行
- 0031 迁移:tutorial 加 project 列(从正文「所属项目:」回填),新表 tutorial_problem 挂每课的相关题(两头 CASCADE) - 学完一课 = 练一练全对(契约 isLessonDone),不再按停留满 3 分钟算 - /tutorials/:id 带相关题和做对没有,/tutorials/:id/exercises 带每道做对没有 - /trial-runs 收 tutorialId:教程里的代码块走同一台判题机跑一组 - 后台:教程列表带练一练 / 相关题数 / 打开过的人,拖动排序、项目改名、挑题候选(必会题在前) - 自学情况换成学生 × 课的方块格(/learn-analytics/board),默认班和排名页同一套猜法 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6d1bfc522d
commit
11194543a3
14 files changed
+4621
-268
No files matched your search
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE "tutorial_problem" (
|
||||
"tutorial_id" integer NOT NULL,
|
||||
"problem_id" integer NOT NULL,
|
||||
"order" integer NOT NULL,
|
||||
CONSTRAINT "tutorial_problem_pkey" PRIMARY KEY("tutorial_id","problem_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "tutorial" ADD COLUMN "project" varchar(64) DEFAULT '' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "tutorial_problem" ADD CONSTRAINT "tutorial_problem_tutorial_id_fk_tutorial_id" FOREIGN KEY ("tutorial_id") REFERENCES "public"."tutorial"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "tutorial_problem" ADD CONSTRAINT "tutorial_problem_problem_id_fk_problem_id" FOREIGN KEY ("problem_id") REFERENCES "public"."problem"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
-- 项目原来只写在正文第一段(「**所属项目:项目三 · 智慧农业·蜜橘园**」),抽出来回填。
|
||||
-- 没写的(9 篇没公开的老 Python 教程)留空,自学目录里归到「其他」
|
||||
UPDATE "tutorial"
|
||||
SET "project" = left(trim(substring("content" from '\*\*所属项目[::]\s*([^*\n]+)\*\*')), 64)
|
||||
WHERE "content" ~ '\*\*所属项目[::]';
|
||||
File diff suppressed because it is too large.
Load diff
@@ -218,6 +218,13 @@
|
||||
"when": 1791587559546,
|
||||
"tag": "0030_add_user_rank_hidden",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 31,
|
||||
"version": "7",
|
||||
"when": 1791766110876,
|
||||
"tag": "0031_tutorial_project_and_problems",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1042,6 +1042,9 @@ export const tutorial = pgTable(
|
||||
createdById: integer("created_by_id").notNull(),
|
||||
code: text(),
|
||||
type: varchar({ length: 10 }).notNull().$type<TutorialType>(),
|
||||
// 所属项目(「项目三 · 智慧农业·蜜橘园」),自学目录按它分组。0031 从正文第一段的
|
||||
// 「**所属项目:…**」回填;发布脚本不传它,后端照样从正文里抽,见 admin/tutorial.ts
|
||||
project: varchar({ length: 64 }).default("").notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("tutorial_created_by_id_07973cab").using(
|
||||
@@ -1056,6 +1059,37 @@ export const tutorial = pgTable(
|
||||
],
|
||||
)
|
||||
|
||||
/**
|
||||
* 一课挂的「去做几道题」:老师在后台手挑的题目,按 `order` 排。
|
||||
*
|
||||
* 两头都 CASCADE:教程删了这串题就没意义了;题目删了,教程少挂一道题也不是学生的
|
||||
* 做题证据(证据在 submission 那边,它对 problem 是拦住的)。
|
||||
*/
|
||||
export const tutorialProblem = pgTable(
|
||||
"tutorial_problem",
|
||||
{
|
||||
tutorialId: integer("tutorial_id").notNull(),
|
||||
problemId: integer("problem_id").notNull(),
|
||||
order: integer().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
primaryKey({
|
||||
columns: [table.tutorialId, table.problemId],
|
||||
name: "tutorial_problem_pkey",
|
||||
}),
|
||||
foreignKey({
|
||||
columns: [table.tutorialId],
|
||||
foreignColumns: [tutorial.id],
|
||||
name: "tutorial_problem_tutorial_id_fk_tutorial_id",
|
||||
}).onDelete("cascade"),
|
||||
foreignKey({
|
||||
columns: [table.problemId],
|
||||
foreignColumns: [problem.id],
|
||||
name: "tutorial_problem_problem_id_fk_problem_id",
|
||||
}).onDelete("cascade"),
|
||||
],
|
||||
)
|
||||
|
||||
export const userStat = pgTable(
|
||||
"user_stat",
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user