| |
| |
| |
| |
| |
|
|
| |
| CREATE TABLE IF NOT EXISTS schema_migrations ( |
| version INTEGER PRIMARY KEY, |
| name TEXT NOT NULL, |
| applied_at TEXT NOT NULL |
| ); |
|
|
| |
| |
| |
|
|
| |
| 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, |
| guest_allowed_depts TEXT NOT NULL DEFAULT '[]', |
| 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 |
| ); |
|
|
| |
| CREATE TABLE IF NOT EXISTS documents ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| title TEXT NOT NULL, |
| category TEXT NOT NULL, |
| tags TEXT NOT NULL DEFAULT '[]', |
| summary TEXT, |
| body TEXT NOT NULL, |
| vault_path TEXT UNIQUE, |
| 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 '[]', |
| access_list TEXT NOT NULL DEFAULT '[]', |
| related TEXT NOT NULL DEFAULT '[]', |
| up_moc_id INTEGER REFERENCES documents(id), |
| keywords TEXT NOT NULL DEFAULT '[]', |
| last_restored_from INTEGER, |
| 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, |
| 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) |
| ); |
|
|
| |
| 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_meta_json TEXT, |
| 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 |
| ); |
|
|
| |
| 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) |
| ); |
|
|
| |
| CREATE TABLE IF NOT EXISTS detection_rules ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| rule_name TEXT NOT NULL UNIQUE, |
| pattern TEXT NOT NULL, |
| layer INTEGER NOT NULL DEFAULT 1, |
| 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) |
| ); |
|
|
| |
| 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, |
| ip_address TEXT, |
| user_agent TEXT, |
| result TEXT CHECK(result IN ('success','fail','blocked')) DEFAULT 'success', |
| timestamp TEXT NOT NULL |
| ); |
|
|
| |
| 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, |
| title TEXT NOT NULL, |
| body TEXT, |
| link TEXT, |
| is_read INTEGER NOT NULL DEFAULT 0, |
| created_at TEXT NOT NULL, |
| read_at TEXT |
| ); |
|
|
| |
| |
| |
|
|
| 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 |
| ); |
|
|
| |
| 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) |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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), |
| 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 |
| ); |
|
|
| |
| 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 '{}' |
| ); |
|
|
| |
| |
| |
|
|
| |
| 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 '[]' |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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) |
| ); |
|
|
| |
| 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) |
| ); |
|
|
| |
| |
| 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), |
| structure_md TEXT NOT NULL DEFAULT '', |
| ai_prompt TEXT NOT NULL DEFAULT '', |
| 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) |
| ); |
|
|
| |
| CREATE TABLE IF NOT EXISTS cross_dept_topics ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| topic TEXT NOT NULL, |
| doc_ids TEXT NOT NULL DEFAULT '[]', |
| departments TEXT NOT NULL DEFAULT '[]', |
| status TEXT NOT NULL DEFAULT 'pending' |
| CHECK(status IN ('pending','moc_created','ignored')), |
| moc_id INTEGER REFERENCES documents(id), |
| ignored_until TEXT, |
| created_at TEXT NOT NULL, |
| updated_at TEXT |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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) |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| 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 |
| ); |
|
|
| |
| |
| |
| 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); |
|
|
| |
| |
| |
| |
| 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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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); |
|
|
| |
| |
| |
| |
| CREATE TABLE IF NOT EXISTS system_config ( |
| key TEXT PRIMARY KEY, |
| value TEXT NOT NULL, |
| updated_at TEXT NOT NULL, |
| updated_by INTEGER REFERENCES users(id) |
| ); |
|
|
| |
| |
| |
| 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); |
|
|