# src/database/

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

页面：

- `/database/admin` —— 飞书登录页（唯一登录方式）
- `/database/dashboard` —— 左菜单 + 右内容的后台，未登录会跳回 `/database/admin`

登录走平台既有的飞书 OAuth：登录页的按钮指向 `/auth/feishu/<orgSlug>`，
回调由 `src/admin/routes/authRoutes.ts` 处理并种下 session cookie。

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

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

### 怎么开

在 `hub/.env` 里设：

```sh
HUB_DEV_LOGIN_BYPASS="true"
```

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

开启后：

- `/database/admin` 登录页显示「⚡ 一键登录管理员」按钮
- 后端注册 `/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` | 登录页/dashboard + 各子路由装配点 |
| `routes/filelibRoutes.ts` | 文件库 树/授权/搜索 API |
| `routes/fileRoutes.ts` | 文件库 文件内容/导出 API |
| `routes/libraryPage.ts` | `/database/library` 文件库浏览页 |
| `filelib/` | 文件库领域层(见下) |

新增一类端点时：要么直接往 `databaseRoutes.ts` 加 `app.get("/database/...")`，
要么新建 `routes/xxxRoutes.ts` 并在 `databaseRoutes.ts` 里 `registerXxxRoutes(app, {...})`
注册一次。

## 文件库(filelib/)

独立文件库模块,语义由仓库根《文件库-接口契约.md》(C/D 编号)与
`.omo/文件库-开工计划.md`(D11–D19)锚定。**与 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 过渡实现 |
| `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);未配置时读 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 面一致）

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 watch`（`npm run dev`）加载。放在
`src/` 之外的目录不会被构建，外部识别不到。
