Files
Cyrene_v2/internal/db/migrations/0014_documents.up.sql
T
AskaEth 407c934a05 feat(gateway): 文档存储端点 POST/GET /api/v1/documents(design/24 S1)
- 字节嗅探唯一裁决 + 扩展名白名单 deny-by-default(23 个文本/代码扩展名);
  客户端 Content-Type 与文件名只作参考、可被否决。
- 内容寻址落盘名 sha256[:32]+ext ⇒ 同字节同扩展名天然去重;首次 201/deduped:false,
  命中 200/同一 name/deduped:true。
- 独立限额 CHAT_DOCUMENT_MAX_BYTES(默认 20 MiB,与图片 5 MiB 刻意解耦)+ MaxBytesReader。
- 读侧 Bearer owner|admin + Content-Disposition: attachment + nosniff(与图片读侧刻意不同);
  写侧审计 document.upload;错误码 unsupported_media_type/document_too_large/document_not_found。
- 元数据表 documents(0014,含 idx_documents_name 唯一、idx_documents_sha256);
  guardDocumentPaths 防路径穿越(否则 ServeMux 会 307 泄漏路径)。
- 既有 /api/v1/upload 与 /api/v1/files/{name} 零改动(upload.go 零 diff)。

验收(主会话独立复跑,非自报):隔离 worktree(HEAD + 本切片)go build/vet 绿、
全树 58 包 0 FAIL、linux/amd64 + darwin/arm64 交叉编译绿、9 条新单测 PASS。
变异独立复现 3 条:白名单加 ".js" ⇒ 415 用例红;嗅探裁决一律放行 ⇒ 谎报类型用例红;
摘掉 guardDocumentPaths ⇒ 穿越用例红(307 重定向,正是那道防护存在的理由)。
2026-10-04 17:07:20 +08:00

36 lines
2.0 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 0014_documents: 文档存储元数据(design/24 §3.2 / §3.3 / §6.1,切片 S1)。
--
-- 一行 = 一份内容寻址落盘的文档。落盘名 name = sha256(bytes)[:32] + ext,
-- 因此「同字节同扩展名」必然同名 —— 去重是内容寻址的自然结果,本表只负责
-- 记账(谁传的、多大、什么类型、什么时候),不参与去重判定。
--
-- 为什么必须有这张表:文件系统只回答「文件在不在」,回答不了「这份文档是谁
-- 传的、最后一次被消息引用是什么时候」。切片 S7 的引用计数与 GC(未被任何
-- 消息引用且超过保留期者删除)据此工作,因此本表从一开始就留足列;S1 只写。
--
-- 口径:
-- - name 唯一索引承载 ON CONFLICT(name) DO NOTHING(重复上传不刷新首次记录);
-- - mime 是**服务端字节嗅探所得**的基类型,不是客户端声明的 Content-Type;
-- - size 是落盘字节数;uploader 是 JWT sub,取不到时为 'anonymous';
-- - last_referenced_at / ref_count 由 S7 维护,此处只建列(S1 不填)。
-- 表名用复数,援引 AGENTS §10 已登记的例外(既有迁移口径一致)。
CREATE TABLE IF NOT EXISTS documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
sha256 TEXT NOT NULL,
mime TEXT NOT NULL,
size INTEGER NOT NULL,
uploader TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
last_referenced_at TEXT,
ref_count INTEGER NOT NULL DEFAULT 0
);
-- 唯一索引同时承担 upsert 的冲突目标(ON CONFLICT(name) 需要唯一约束/索引)。
CREATE UNIQUE INDEX IF NOT EXISTS idx_documents_name
ON documents (name);
-- 按内容哈希查找(同字节不同扩展名时也想知道「这份内容还存着哪些名字」)。
CREATE INDEX IF NOT EXISTS idx_documents_sha256
ON documents (sha256);