Files
curriculum-project-hub/hub/src/database
12628c9233 feat(hub): migrate database admin pages to SPA (database-admin)
- scaffold hub/database-admin as SvelteKit 2 + Svelte 5 static SPA
  with aurora/glass visual style (paths.base='/database')
- add lib/{api,session,org}.ts + Aurora.svelte component
- add routes: root redirect, /admin login page, /dashboard (OWNER/ADMIN only)
- backend: replace server-rendered HTML routes with /database/config JSON endpoint
- add hub/src/database/static.ts to serve SPA under /database/*
- wire registerDatabaseSpa into plugin.ts
- exempt /database/* from silo rate-limit (same treatment as /admin/*)
- add database:dev + database:build npm scripts; update deploy scripts
- update hub/src/database/README.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:51:53 +08:00
..

src/database/

/database/* HTTP 面。代码写在这个目录里,hub.ts 通过 plugin.ts 挂载它, 所以服务器启动时能正确识别这些路由。

前后端分离:页面已迁到独立的 SvelteKit 静态 SPA hub/database-admin/ (与 hub/admin-web/ 同一套框架)。本目录的后端只保留三件事:鉴权透传、一个 免鉴权配置端点、以及把 SPA 构建产物托管出去。页面全部由 SPA 客户端渲染。

后端路由:

  • GET /database/config —— 免鉴权。返回 { siloOrganizationSlug, devLoginEnabled }, 给 SPA 登录页拼飞书链接、决定是否显示 dev 按钮用。不含任何敏感数据。
  • GET /database/dev-login —— 仅开发。见下。
  • GET /databaseGET /database/* —— SPA shell / 客户端路由 fallback static.tsregisterDatabaseSpa);资产在 /database/_app/*

SPA 页面(database-adminpaths.base='/database'):

  • /database/admin —— 飞书登录页。按钮指向 /auth/feishu/<orgSlug>slug 来自 /database/config),回调由 src/admin/routes/authRoutes.ts 处理并种 session cookie。
  • /database/dashboard —— 后台壳。未登录跳登录页;登录但非 OWNER/ADMIN 显示无权提示

注册顺序要点concrete 路由(/database/config/database/dev-login)必须在 registerDatabaseSpa/database/* fallback 之前注册(已在 plugin.ts 保证), 否则通配会 shadow 它们。

开发模式:用环境变量开启一键登录

本地开发没有真实飞书 app 时,可以用环境变量开启一键登录,跳过飞书 OAuth, 直接以现有 OWNER/ADMIN 身份登入后台。仅限开发,不是生产登录路径。

怎么开

hub/.env 里设:

HUB_DEV_LOGIN_BYPASS="true"

改完重启服务(npm run dev,或本地手动 npx tsx src/server.ts)。启动日志会 打印一行 DEV login bypass enabled: /database/dev-login ... 作为确认。

开启后:

  • /database/config 返回 devLoginEnabled: trueSPA 登录页据此显示 「 一键登录管理员」按钮
  • 后端注册 /database/dev-login 端点:按钮就是打它,它签发一个和飞书 OAuth 回调完全一样的 session,然后跳到 /database/dashboard

怎么关

把值设成 false(或 0 / no / off),或删掉这一行。关闭后按钮消失、 /database/dev-login 返回 404 —— 按钮和端点同进同退。

双重门禁(重要)

真正的开关是两个条件的(判断在 plugin.ts):

allowDevLoginBypass = (NODE_ENV !== "production") && HUB_DEV_LOGIN_BYPASS 为真

即:只要 NODE_ENV=production,无论 HUB_DEV_LOGIN_BYPASS 设成什么,一键登录 都强制关闭。 生产始终只能走真实飞书 OAuth。

提醒:HUB_DEV_LOGIN_BYPASS 是敏感开关,别把开着它的 .env 带到任何联网 / 共享环境。整个旁路逻辑自包含在本目录(plugin.ts + routes/databaseRoutes.ts), src/admin 的登录路由未受影响。

文件

文件 职责
plugin.ts 模块对外入口,hub.tsregisterDatabasePlugin();先挂 concrete 路由再挂 SPA
routes/databaseRoutes.ts 后端 JSON / redirect 路由(/database/config/database/dev-login),数据端点加在这里
static.ts registerDatabaseSpa:托管 database-admin/build 的 SPA + /database/* fallback

新增一类数据端点时:要么直接往 databaseRoutes.tsapp.get("/database/...") 要么新建 routes/xxxRoutes.ts 并在 databaseRoutes.tsregisterXxxRoutes(app, {...}) 注册一次。页面则加在 database-admin/src/routes/ 下(SvelteKit 路由)。

SPA 构建与托管

  • 前端在 hub/database-admin/npm run build(或 hub 根的 npm run database:build 产出到 database-admin/build/。hub 的 npm run build 会把两个 SPA 一起带出来。
  • static.ts 默认从 ../../database-admin/build 读产物;可用 CPH_DATABASE_UI_DIR 覆盖。产物缺失时降级:只 warn,不挂 SPA,/database/config/database/dev-login 仍可用。
  • 本地开发:hub 根 npm run database:dev 起 Vite,它把 /api/auth/database 代理到 127.0.0.1:8788

约定(与 admin 面一致)

  1. 路由用绝对路径 "/database/...",不用 Fastify prefix —— 每条路由 grep 得到。
  2. guard 前置、fail closed:凡碰数据的端点第一行先跑 requireSession / requireOrgRole / requireProjectPermission (都在 ../admin/auth/guards.js)。
  3. 租户隔离ADR-0020):每个 Prisma 查询都 scope 到 auth.organization.id, 不得跨 org。禁止无鉴权的数据路由。
  4. 数据库通过传入的 config.prisma 访问(全进程单例,见 ../db.ts); 不要在这里 new PrismaClient()

为什么代码在 src/

tsconfig.json 固定 rootDir: "src"include: ["src/**/*.ts"]。只有 src/ 下的 .ts 会被 tsc 编译、被 tsx watchnpm run dev)加载。放在 src/ 之外的目录不会被构建,外部识别不到。