.omo/ 下 12 个 run-continuation/ses_*.json 是 agent 会话续跑状态, 机器生成,本不该进版本库;文件库-开工计划.md 一并删除。 《文件库-接口契约.md》(C/D 编号)同时删除。两份文档的内容都可从 git 历史取回。 代码注释里的 C/D 编号(契约 8.1、C2、C4、D11–D19 等)因此不再有在库 文档可查,分布在 filelib 的 model / grantService / treeService / guards、prisma schema 与迁移、以及 ADR-0028。README 原先按路径引用 这两份文档,现改为说明出处与取回方式。
src/database/
/database/* HTTP 面。代码写在这个目录里,hub.ts 通过 plugin.ts 挂载它,
所以服务器启动时能正确识别这些路由。
前后端分离:页面全部在 SvelteKit 静态 SPA hub/filelib-web/(与 hub/admin-web/
同一套框架)。老师端 /app 与管理后台 /database 共用这一份工程和这一份构建产物 ——
两个挂载前缀,一个 SPA。本目录的后端只保留三件事:鉴权透传、JSON 数据端点、
以及把构建产物托管出去。服务端不渲染任何 HTML。
后端路由:
GET /database/config—— 免鉴权。返回{ orgSlug, devLoginEnabled }, 给 SPA 登录页拼飞书链接、决定是否显示 dev 按钮用。不含任何敏感数据。 (/database/api/login-info是同形状的既有端点,由routes/teacherApp.ts注册。)GET /database/api/stats—— 概览页统计。需登录 且 是 silo org OWNER/ADMIN。GET /database/dev-login—— 仅开发。见下。GET /database、GET /database/*、GET /app、GET /app/*—— SPA shell / 客户端路由 fallback(static.ts的registerDatabaseSpa)。GET /_filelib/*—— 构建产物资源。SvelteKit 的appDir改名为_filelib, 以避开admin-web在根上注册的/_app/*(同名会让 Fastify 启动即抛重复路由)。
SPA 页面(filelib-web,真 URL 路由、无 hash):
/app—— 老师端文件库。未登录显示登录卡片。/database/admin—— 管理员飞书登录页。按钮指向/auth/feishu/<orgSlug>, 回调由src/admin/routes/authRoutes.ts处理并种 session cookie。/database/dashboard—— 后台外壳(侧栏 + 权限门)。未登录跳登录页; 登录但非 OWNER/ADMIN 显示无权提示。六个 tab 都是子路由:/database/dashboard(概览)、/library、/users、/groups、/search、/settings。
注册顺序要点:concrete 路由(
/database/config、/database/api/*、/database/dev-login、/app/dev-login-teacher)必须在registerDatabaseSpa的/database/*、/app/*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: true,SPA 登录页据此显示 「⚡ 一键登录管理员」按钮- 后端注册
/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.ts 调 registerDatabasePlugin() |
routes/databaseRoutes.ts |
/database/config、/database/api/stats、dev 旁路 + 各子路由装配点 |
routes/filelibRoutes.ts |
文件库 树/授权 API |
routes/fileRoutes.ts |
文件库 文件内容/导出 API |
routes/memberGroupRoutes.ts |
成员组管理 API + /groups/search + /users/search(ADR-0028) |
routes/teacherApp.ts |
/database/api/login-info + 老师端 DEV 一键登录 |
static.ts |
filelib-web 构建产物托管:/_filelib/* 资源 + /app、/database 两个 SPA 回退 |
filelib/ |
文件库领域层(见下) |
新增一类数据端点时:要么直接往 databaseRoutes.ts 加 app.get("/database/api/..."),
要么新建 routes/xxxRoutes.ts 并在 databaseRoutes.ts 里 registerXxxRoutes(app, {...})
注册一次。不要在后端拼 HTML —— 页面一律加在 hub/filelib-web/src/routes/ 下。
文件库(filelib/)
独立文件库模块。代码注释里的 C/D 编号(契约 8.1、C2、C4、D11–D19 等)
出自两份已删除的文档:《文件库-接口契约.md》与 .omo/文件库-开工计划.md,
内容可从 git 历史取回。其中 D19(网站管理员 = silo org OWNER/ADMIN)
另见 ADR-0028。与 hub 自己的 Folder/Project(ADR-0021 explorer)是
两套体系,不复用。
| 文件 | 职责 |
|---|---|
filelib/model.ts |
角色秩(MANAGE>EDIT>VIEW)、D14 命名规则、FileLibError |
filelib/permission.ts |
纯权限 reducer(取最高/不降权/祖先继承/D11 冻结),不碰 IO |
filelib/treeService.ts |
树增删改查;每个写操作同事务落审计 |
filelib/grantService.ts |
授权管理 + 契约 8.1 矩阵强制 + force_adjust |
filelib/fileService.ts |
文件路径安全 + 版本化读写(先 git 后审计的顺序铁律) |
filelib/exportService.ts |
导出 job 状态机(D10 异步)+ ExportAdapter port |
filelib/versionStore.ts |
契约 C1 port + 内存实现(版本团队 npm 包到位后替换) |
filelib/groupResolver.ts |
契约 C2 port(+ 已弃用的 Team 过渡实现,ADR-0028) |
filelib/memberGroupResolver.ts |
默认 C2 实现:读 in-hub MemberGroup 闭包(ADR-0028) |
filelib/memberGroupService.ts |
成员组 CRUD(含改名)+ 成员增删 + 闭包维护 + 搜索(ADR-0028) |
filelib/groupResolverHttp.ts |
C2 HTTP 实现(HUB_GROUP_SERVICE_URL 启用;失败 → 503) |
filelib/audit.ts |
审计动作词表(C3 §6.3)+ 同事务写入 |
filelib/guards.ts |
session → FileLibActor;网站管理员 = org OWNER/ADMIN(D19) |
filelib/routeShared.ts |
路由共享件(依赖装配/错误映射/请求体校验) |
环境变量:
HUB_FILELIB_STORAGE_ROOT— 项目 git 仓库根目录(默认./.filelib-repos)HUB_GROUP_SERVICE_URL— 外部 Group 服务地址(C2);未配置时读 in-hub MemberGroup 闭包(ADR-0028 起的默认;此前是扁平 hub Team)
⚠️ 开发期注意:当前 VersionStore 是进程内存实现,服务重启后仓库全失, 此前创建的项目再访问文件会报
repo_not_found(需重建项目)。版本团队的 持久化 git 包到位后此问题消失。
关键语义速查:
- D8:无权限 → 404(不泄露存在性);越权 → 403;Group 服务故障 → 503
- D11:creator 不可变 + 自动 MANAGE;独立权限关闭时项目级非创建者 grant 冻结
- D12:move = 本节点 MANAGE + 目标父 EDIT+,事务 + pg 咨询锁
- D15:删除只打标本节点,"任一祖先已删"即整支不可见
- 8.1:MANAGE 仅创建者可授/收;creator grant 不可动
- 审计:一切写操作在业务事务内写 AuditEntry(同事务,失败即回滚); 文件内容写先 versionStore.commit 再审计(宁多版本,不造假审计)
约定(与 admin 面一致)
- 路由用绝对路径
"/database/...",不用 Fastify prefix —— 每条路由 grep 得到。 - guard 前置、fail closed:凡碰数据的端点第一行先跑
requireSession/requireOrgRole/requireProjectPermission(都在../admin/auth/guards.js)。 - 租户隔离(ADR-0020):每个 Prisma 查询都 scope 到
auth.organization.id, 不得跨 org。禁止无鉴权的数据路由。 - 数据库通过传入的
config.prisma访问(全进程单例,见../db.ts); 不要在这里new PrismaClient()。
为什么代码在 src/ 下
tsconfig.json 固定 rootDir: "src" 且 include: ["src/**/*.ts"]。只有
src/ 下的 .ts 会被 tsc 编译、被 tsx watch(npm run dev)加载。放在
src/ 之外的目录不会被构建,外部识别不到。