File size: 24,350 Bytes
3b1ab70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
-- =====================================================================
-- 知識庫系統 V5 — DB Schema(L1, PRD §5 / §27.5 / §34 / §49 對齊)
-- SQLite WAL, foreign_keys=ON, busy_timeout=30000
-- 對 PRD 模糊處的補完,皆於該表上方註解 "PRD-FILL" 標註。
-- =====================================================================

-- ---- 系統 / migration ----
CREATE TABLE IF NOT EXISTS schema_migrations (
    version     INTEGER PRIMARY KEY,
    name        TEXT NOT NULL,
    applied_at  TEXT NOT NULL
);

-- =====================================================================
-- 核心 7 張(PRD §5.1)
-- =====================================================================

-- ---- users(PRD §5.1)----
CREATE TABLE IF NOT EXISTS users (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    username                TEXT NOT NULL UNIQUE,
    email                   TEXT NOT NULL UNIQUE,
    password_hash           TEXT NOT NULL,
    role                    TEXT NOT NULL CHECK(role IN ('admin','senior_manager','manager','user','guest')),
    permissions             TEXT NOT NULL DEFAULT '{"categories":["all"]}',
    department              TEXT NOT NULL DEFAULT '',
    expires_at              TEXT,                                    -- §50.2.2 guest 帳號自動失效時間
    guest_allowed_depts     TEXT NOT NULL DEFAULT '[]',             -- §50.2.2 guest 可看部門 JSON
    created_at              TEXT NOT NULL,
    last_login              TEXT,
    is_active               INTEGER NOT NULL DEFAULT 1,
    is_deleted              INTEGER NOT NULL DEFAULT 0,
    deleted_at              TEXT,
    deleted_by              INTEGER REFERENCES users(id),
    force_password_change   INTEGER NOT NULL DEFAULT 0,
    jwt_version             INTEGER NOT NULL DEFAULT 1,
    totp_secret             TEXT,
    totp_enabled            INTEGER NOT NULL DEFAULT 0,
    totp_grace_until        TEXT,
    failed_login_count      INTEGER NOT NULL DEFAULT 0,
    locked_until            TEXT
);

-- PRD-FILL: documents 表 PRD §5.1 僅列擴充欄位,核心欄位依 §6 frontmatter + §11 visibility + §15 embedding 推導
CREATE TABLE IF NOT EXISTS documents (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    title                   TEXT NOT NULL,
    category                TEXT NOT NULL,
    tags                    TEXT NOT NULL DEFAULT '[]',     -- JSON array
    summary                 TEXT,
    body                    TEXT NOT NULL,                  -- Markdown
    vault_path              TEXT UNIQUE,                    -- vault/ 對應 md 路徑
    author_id               INTEGER NOT NULL REFERENCES users(id),
    owner_department        TEXT NOT NULL DEFAULT '',
    visibility              TEXT NOT NULL DEFAULT 'department'
                            CHECK(visibility IN ('public','department','selected_departments','restricted','confidential')),
    allowed_departments     TEXT NOT NULL DEFAULT '[]',     -- JSON array of dept names
    access_list             TEXT NOT NULL DEFAULT '[]',     -- JSON array of user_ids
    related                 TEXT NOT NULL DEFAULT '[]',     -- §41 JSON array of related doc ids(防護C 跨部門互掛)
    up_moc_id               INTEGER REFERENCES documents(id),  -- §41 所屬統一 MOC 文件 id(防護D)
    keywords                TEXT NOT NULL DEFAULT '[]',     -- §8.5 JSON array of ai_keywords
    last_restored_from      INTEGER,                        -- §7.5 最近一次還原的來源版次(任何 edit 後清空)
    template_id             INTEGER REFERENCES document_templates(id),
    referrer_count          INTEGER NOT NULL DEFAULT 0,
    view_count              INTEGER NOT NULL DEFAULT 0,
    favorite_count          INTEGER NOT NULL DEFAULT 0,
    status                  TEXT NOT NULL DEFAULT 'active'
                            CHECK(status IN ('active','archived')),
    embedding               BLOB,                            -- 384-dim float32
    created_at              TEXT NOT NULL,
    updated_at              TEXT NOT NULL,
    is_deleted              INTEGER NOT NULL DEFAULT 0,
    deleted_at              TEXT,
    deleted_by              INTEGER REFERENCES users(id)
);

-- PRD-FILL: pending_documents 核心欄位(status CHECK 依 PRD §5.1 完整列舉)
CREATE TABLE IF NOT EXISTS pending_documents (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    submitter_id            INTEGER NOT NULL REFERENCES users(id),
    submitter_department    TEXT NOT NULL DEFAULT '',
    original_content        TEXT NOT NULL,
    ai_result               TEXT,                           -- AI 整理後完整 markdown
    ai_meta_json            TEXT,                           -- AI 提取的 frontmatter(標題/分類/標籤)
    target_document_id      INTEGER REFERENCES documents(id),
    merge_target_title      TEXT,
    status                  TEXT NOT NULL DEFAULT 'processing'
                            CHECK(status IN ('processing','ai_ready','pending','approved','rejected','draft','failed')),
    reject_reason           TEXT,
    reviewer_id             INTEGER REFERENCES users(id),
    reviewed_at             TEXT,
    created_at              TEXT NOT NULL,
    updated_at              TEXT NOT NULL
);

-- PRD-FILL: document_history 版本追蹤
CREATE TABLE IF NOT EXISTS document_history (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    document_id     INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    version         INTEGER NOT NULL,
    title           TEXT NOT NULL,
    body            TEXT NOT NULL,
    summary         TEXT,
    tags            TEXT NOT NULL DEFAULT '[]',
    edited_by       INTEGER NOT NULL REFERENCES users(id),
    edited_at       TEXT NOT NULL,
    change_note     TEXT,
    UNIQUE(document_id, version)
);

-- PRD-FILL: detection_rules(PRD §5.1 補欄位 + §49 layer1 規則)
CREATE TABLE IF NOT EXISTS detection_rules (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rule_name       TEXT NOT NULL UNIQUE,
    pattern         TEXT NOT NULL,                          -- regex
    layer           INTEGER NOT NULL DEFAULT 1,             -- 1=regex, 2=variant, 3=topic, 4=sample
    action          TEXT NOT NULL DEFAULT 'block'
                    CHECK(action IN ('block','warn','mask','log')),
    is_active       INTEGER NOT NULL DEFAULT 1,
    is_deleted      INTEGER NOT NULL DEFAULT 0,
    created_by      INTEGER REFERENCES users(id),
    created_at      TEXT NOT NULL,
    updated_at      TEXT,
    updated_by      INTEGER REFERENCES users(id)
);

-- PRD-FILL: audit_log(PRD §5.1 + §10.7 永久保留)
CREATE TABLE IF NOT EXISTS audit_log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER REFERENCES users(id),
    action          TEXT NOT NULL,
    resource_type   TEXT,
    resource_id     TEXT,
    detail          TEXT,                                   -- JSON
    ip_address      TEXT,
    user_agent      TEXT,
    result          TEXT CHECK(result IN ('success','fail','blocked')) DEFAULT 'success',
    timestamp       TEXT NOT NULL
);

-- PRD-FILL: notifications(PRD §5.1 30 天 TTL)
CREATE TABLE IF NOT EXISTS notifications (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    type            TEXT NOT NULL,                          -- pending_review / approved / rejected / ...
    title           TEXT NOT NULL,
    body            TEXT,
    link            TEXT,                                   -- 點擊跳轉路徑
    is_read         INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT NOT NULL,
    read_at         TEXT
);

-- =====================================================================
-- 衍生 5 張(PRD §5.2 / §27.3 / §30.2 / §31.1 / §32.2)
-- =====================================================================

CREATE TABLE IF NOT EXISTS detection_rules_history (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rule_id         INTEGER REFERENCES detection_rules(id),
    action          TEXT NOT NULL CHECK(action IN ('create','update','delete','restore')),
    old_pattern     TEXT,
    new_pattern     TEXT,
    old_rule_name   TEXT,
    new_rule_name   TEXT,
    changed_by      INTEGER REFERENCES users(id),
    changed_at      TEXT NOT NULL,
    created_at      TEXT NOT NULL
);

-- PRD §27.3
CREATE TABLE IF NOT EXISTS ai_usage (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id),
    department      TEXT,
    provider        TEXT,
    model           TEXT,
    action          TEXT,
    input_tokens    INTEGER,
    output_tokens   INTEGER,
    cost_usd_x1000  INTEGER,
    cache_hit       INTEGER NOT NULL DEFAULT 0,
    timestamp       TEXT NOT NULL,
    pending_id      INTEGER REFERENCES pending_documents(id)
);

-- PRD §30.2
CREATE TABLE IF NOT EXISTS sessions (
    id              TEXT PRIMARY KEY,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    user_agent      TEXT,
    ip_address      TEXT,
    created_at      TEXT NOT NULL,
    last_used_at    TEXT NOT NULL,
    expires_at      TEXT NOT NULL
);

-- PRD §31.1
CREATE TABLE IF NOT EXISTS feedback (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER REFERENCES users(id),
    category    TEXT NOT NULL CHECK(category IN ('bug','feature','improvement','question','document_error')),
    title       TEXT NOT NULL,
    description TEXT NOT NULL,
    page_url    TEXT,
    doc_id      INTEGER REFERENCES documents(id),  -- §50.3.2 報告錯誤關聯文件
    user_agent  TEXT,
    status      TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','reviewing','resolved','wontfix')),
    admin_reply TEXT,
    created_at  TEXT NOT NULL,
    updated_at  TEXT
);

-- PRD §32.2
CREATE TABLE IF NOT EXISTS user_preferences (
    user_id               INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
    notify_pending        INTEGER NOT NULL DEFAULT 1,
    notify_account_apply  INTEGER NOT NULL DEFAULT 1,
    notify_visibility     INTEGER NOT NULL DEFAULT 1,
    notify_abnormal       INTEGER NOT NULL DEFAULT 1,
    notify_announcement   INTEGER NOT NULL DEFAULT 1,
    default_view          TEXT NOT NULL DEFAULT 'grid' CHECK(default_view IN ('grid','list')),
    font_size             TEXT NOT NULL DEFAULT 'medium' CHECK(font_size IN ('small','medium','large')),
    page_size             INTEGER NOT NULL DEFAULT 50,
    seen_whatsnew_version TEXT,
    onboarding_completed  INTEGER NOT NULL DEFAULT 0,
    onboarding_checklist_progress TEXT NOT NULL DEFAULT '{}'  -- §50.3.1 {doc_id: viewed_at}
);

-- =====================================================================
-- 其他(PRD §27.5 / §34 / §47 / §48 / §49 / 標籤 / 待辦 / 分享)
-- =====================================================================

-- PRD §27.5.1
CREATE TABLE IF NOT EXISTS departments (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL UNIQUE,
    is_active       INTEGER NOT NULL DEFAULT 1,
    sort_order      INTEGER NOT NULL DEFAULT 0,
    created_by      INTEGER REFERENCES users(id),
    created_at      TEXT NOT NULL,
    deactivated_at  TEXT,
    deactivated_by  INTEGER REFERENCES users(id),
    onboarding_doc_ids TEXT NOT NULL DEFAULT '[]'  -- §50.3.1 部門新人必讀文件 id JSON
);

-- PRD §34.1
CREATE TABLE IF NOT EXISTS ai_cache (
    content_hash    TEXT PRIMARY KEY,
    ai_result_json  TEXT NOT NULL,
    provider        TEXT NOT NULL,
    model           TEXT NOT NULL,
    cached_at       TEXT NOT NULL,
    hit_count       INTEGER NOT NULL DEFAULT 0,
    last_hit_at     TEXT
);

-- PRD-FILL: bookmarks(PRD §47 收藏)
CREATE TABLE IF NOT EXISTS bookmarks (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    document_id     INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    folder          TEXT NOT NULL DEFAULT '預設',          -- 收藏分類
    sort_order      INTEGER NOT NULL DEFAULT 0,
    note            TEXT,
    created_at      TEXT NOT NULL,
    UNIQUE(user_id, document_id)
);

-- PRD-FILL: bookmark_folders(PRD §12.25 收藏資料夾;支援空資料夾 + 改名/刪除)
CREATE TABLE IF NOT EXISTS bookmark_folders (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name            TEXT NOT NULL,
    icon            TEXT,
    color           TEXT,
    sort_order      INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT NOT NULL,
    UNIQUE(user_id, name)
);

-- PRD §48
-- PRD §48.4 文檔模板系統:三件套(structure_md 骨架 / ai_prompt 指令 / example_content 範例)
CREATE TABLE IF NOT EXISTS document_templates (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL,
    description     TEXT,
    scope           TEXT NOT NULL CHECK(scope IN ('global','department')),
    department_id   INTEGER REFERENCES departments(id),    -- scope='department' 時必填
    structure_md    TEXT NOT NULL DEFAULT '',              -- 區塊骨架,AI 必須產出對應段落
    ai_prompt       TEXT NOT NULL DEFAULT '',              -- 給 AI 的具體指令
    example_content TEXT,                                  -- 完整範例(選填)
    is_active       INTEGER NOT NULL DEFAULT 1,
    is_default      INTEGER NOT NULL DEFAULT 0,            -- 該範圍的預設模板
    created_by      INTEGER REFERENCES users(id),
    created_at      TEXT NOT NULL,
    updated_at      TEXT,
    updated_by      INTEGER REFERENCES users(id)
);

-- PRD §41 防護D:跨部門同主題追蹤(admin 跨部門主題管理頁 §41.5)
CREATE TABLE IF NOT EXISTS cross_dept_topics (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    topic           TEXT NOT NULL,                          -- 主題名(取自文件標題)
    doc_ids         TEXT NOT NULL DEFAULT '[]',             -- JSON 涉及文件 ids
    departments     TEXT NOT NULL DEFAULT '[]',             -- JSON 涉及部門
    status          TEXT NOT NULL DEFAULT 'pending'
                    CHECK(status IN ('pending','moc_created','ignored')),
    moc_id          INTEGER REFERENCES documents(id),       -- 建立的統一 MOC 文件
    ignored_until   TEXT,                                   -- ignored:30 天內不再通知
    created_at      TEXT NOT NULL,
    updated_at      TEXT
);

-- PRD §49.6
CREATE TABLE IF NOT EXISTS sensitive_topics (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL UNIQUE,
    description     TEXT,
    is_builtin      INTEGER NOT NULL DEFAULT 0,
    is_active       INTEGER NOT NULL DEFAULT 1,
    threshold       REAL NOT NULL DEFAULT 0.65,
    centroid        BLOB,
    created_by      INTEGER REFERENCES users(id),
    created_at      TEXT NOT NULL,
    updated_at      TEXT
);

CREATE TABLE IF NOT EXISTS sensitive_topic_samples (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    topic_id        INTEGER NOT NULL REFERENCES sensitive_topics(id) ON DELETE CASCADE,
    sample_text     TEXT NOT NULL,
    embedding       BLOB NOT NULL,
    added_by        INTEGER REFERENCES users(id),
    added_at        TEXT NOT NULL
);

-- PRD §49.7
CREATE TABLE IF NOT EXISTS confidential_samples (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    label           TEXT NOT NULL,
    description     TEXT,
    embedding       BLOB NOT NULL,
    text_length     INTEGER,
    text_hash       TEXT,
    added_by        INTEGER REFERENCES users(id),
    added_at        TEXT NOT NULL,
    is_active       INTEGER NOT NULL DEFAULT 1
);

-- PRD §49.9
CREATE TABLE IF NOT EXISTS false_positive_appeals (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id),
    text_snippet    TEXT NOT NULL,
    layer           INTEGER NOT NULL,
    matched_rule    TEXT,
    similarity      REAL,
    user_reason     TEXT,
    status          TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected')),
    reviewed_by     INTEGER REFERENCES users(id),
    reviewed_at     TEXT,
    admin_note      TEXT,
    created_at      TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS whitelist_samples (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    embedding       BLOB NOT NULL,
    label           TEXT,
    added_by        INTEGER REFERENCES users(id),
    added_at        TEXT NOT NULL
);

-- PRD-FILL: tags + document_tags(PRD §12.21 標籤頁所需;§6 frontmatter tags 為 JSON,這裡是正規化版本)
CREATE TABLE IF NOT EXISTS tags (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL UNIQUE,
    color           TEXT,
    created_at      TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS document_tags (
    document_id     INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    tag_id          INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY(document_id, tag_id)
);

-- PRD-FILL: tasks(PRD §12.24 個人待辦)
CREATE TABLE IF NOT EXISTS tasks (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title           TEXT NOT NULL,
    body            TEXT,
    status          TEXT NOT NULL DEFAULT 'open'
                    CHECK(status IN ('open','in_progress','done','archived')),
    due_at          TEXT,
    sort_order      INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT NOT NULL,
    updated_at      TEXT
);

-- PRD-FILL: share_links(PRD §11 分享連結)
CREATE TABLE IF NOT EXISTS share_links (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    document_id     INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    token           TEXT NOT NULL UNIQUE,
    created_by      INTEGER NOT NULL REFERENCES users(id),
    password_hash   TEXT,
    require_login   INTEGER NOT NULL DEFAULT 0,
    expires_at      TEXT,
    revoked_at      TEXT,
    created_at      TEXT NOT NULL,
    last_used_at    TEXT
);

-- =====================================================================
-- 索引(PRD §5.3)
-- =====================================================================
CREATE INDEX IF NOT EXISTS idx_documents_category    ON documents(category);
CREATE INDEX IF NOT EXISTS idx_documents_visibility  ON documents(visibility, is_deleted);
CREATE INDEX IF NOT EXISTS idx_pending_status        ON pending_documents(status);
CREATE INDEX IF NOT EXISTS idx_history_document      ON document_history(document_id);
CREATE INDEX IF NOT EXISTS idx_audit_user            ON audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_timestamp       ON audit_log(timestamp);
CREATE INDEX IF NOT EXISTS idx_notifications_user    ON notifications(user_id, is_read);
CREATE INDEX IF NOT EXISTS idx_users_department      ON users(department, is_deleted);
CREATE INDEX IF NOT EXISTS idx_ai_usage_user         ON ai_usage(user_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_ai_usage_dept         ON ai_usage(department, timestamp);
CREATE INDEX IF NOT EXISTS idx_departments_active    ON departments(is_active, sort_order);
CREATE INDEX IF NOT EXISTS idx_ai_cache_cached_at    ON ai_cache(cached_at);
CREATE INDEX IF NOT EXISTS idx_conf_samples_active   ON confidential_samples(is_active);
CREATE INDEX IF NOT EXISTS idx_sessions_user         ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_bookmarks_user        ON bookmarks(user_id, sort_order);
CREATE INDEX IF NOT EXISTS idx_tasks_user            ON tasks(user_id, status, sort_order);
CREATE INDEX IF NOT EXISTS idx_share_links_doc       ON share_links(document_id);
CREATE INDEX IF NOT EXISTS idx_templates_scope       ON document_templates(scope, department_id, is_active);
CREATE INDEX IF NOT EXISTS idx_templates_default     ON document_templates(is_default, is_active);

-- =====================================================================
-- FTS5 全文索引(PRD §5.3)
-- 採 contentless 對應 documents 主表,由觸發器同步
-- =====================================================================
CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts
USING fts5(title, summary, body, content='documents', content_rowid='id');

CREATE TRIGGER IF NOT EXISTS documents_ai AFTER INSERT ON documents BEGIN
    INSERT INTO documents_fts(rowid, title, summary, body)
    VALUES (new.id, new.title, new.summary, new.body);
END;
CREATE TRIGGER IF NOT EXISTS documents_ad AFTER DELETE ON documents BEGIN
    INSERT INTO documents_fts(documents_fts, rowid, title, summary, body)
    VALUES ('delete', old.id, old.title, old.summary, old.body);
END;
CREATE TRIGGER IF NOT EXISTS documents_au AFTER UPDATE ON documents BEGIN
    INSERT INTO documents_fts(documents_fts, rowid, title, summary, body)
    VALUES ('delete', old.id, old.title, old.summary, old.body);
    INSERT INTO documents_fts(rowid, title, summary, body)
    VALUES (new.id, new.title, new.summary, new.body);
END;

-- =====================================================================
-- L3+ trusted_devices(記住此裝置以跳過 2FA),對應 HTML「記住我 14 天」
-- PRD §10.1 / §10.2 衍生:經過 2FA 驗證的裝置在 TTL 內登入可跳過 2FA。
-- 撤銷方式:使用者於 /account/sessions 移除、改密、jwt_version+1、過期 cron。
-- 只存 token 的 SHA-256,避免 DB 外洩。
-- =====================================================================
-- =====================================================================
-- L3+ users profile 擴充(migration v3):display_name / extension /
-- secondary_departments(兼任部門 JSON)。PRD §5 未列,依使用者需求 + §7.3 gap。
-- 註:欄位若已存在,db.py 的 migration 會 try/except 略過(SQLite 無 IF NOT EXISTS for ADD COLUMN)。
-- =====================================================================

CREATE TABLE IF NOT EXISTS trusted_devices (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash      TEXT NOT NULL,
    label           TEXT,
    user_agent      TEXT,
    ip_address      TEXT,
    created_at      TEXT NOT NULL,
    last_used_at    TEXT NOT NULL,
    expires_at      TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_trusted_devices_user ON trusted_devices(user_id, expires_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_trusted_devices_token ON trusted_devices(token_hash);

-- =====================================================================
-- L10:AI Provider 設定 / 模型對照 / 切換旗標 / 匯率快取(PRD §6.x / §27.3 / §33.4)
-- key-value JSON 設定表(PRD §33.4 system_config.exchange_rates 等)。
-- =====================================================================
CREATE TABLE IF NOT EXISTS system_config (
    key         TEXT PRIMARY KEY,
    value       TEXT NOT NULL,                  -- JSON
    updated_at  TEXT NOT NULL,
    updated_by  INTEGER REFERENCES users(id)
);

-- §50.2.1 多部門兼任(users_departments 多對多,D234)。
-- 註:PRD 範例用 department_id,但本系統全鏈路為「部門名稱制」(owner_department / allowed_departments
-- 皆存名稱),為一致性與降低重構風險,此處改存部門名稱 department(§7.1 取較一致一方)。
CREATE TABLE IF NOT EXISTS users_departments (
    user_id     INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    department  TEXT NOT NULL,
    is_primary  INTEGER NOT NULL DEFAULT 0,
    assigned_at TEXT NOT NULL,
    assigned_by INTEGER REFERENCES users(id),
    PRIMARY KEY (user_id, department)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_primary ON users_departments(user_id) WHERE is_primary=1;

CREATE INDEX IF NOT EXISTS idx_ai_usage_user ON ai_usage(user_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_ai_usage_dept ON ai_usage(department, timestamp);
CREATE INDEX IF NOT EXISTS idx_ai_cache_cached_at ON ai_cache(cached_at);