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>
This commit is contained in:
2026-08-07 07:10:34 -06:00
parent 9a6c3ba225
commit af9cb796bd
5 changed files with 441 additions and 19 deletions

View File

@@ -1,7 +1,9 @@
import { Hono } from "hono"
import type { AppEnv } from "../../auth/middleware"
import { adminAiRoutes } from "./ai"
import { adminAnnouncementRoutes } from "./announcement"
import { adminTutorialRoutes } from "./tutorial"
/**
* 后台路由总入口,挂在 `/api/admin` 下。
@@ -13,4 +15,6 @@ import { adminAnnouncementRoutes } from "./announcement"
*/
export const adminRoutes = new Hono<AppEnv>()
adminRoutes.route("/", adminAiRoutes)
adminRoutes.route("/", adminAnnouncementRoutes)
adminRoutes.route("/", adminTutorialRoutes)