-- Footprints 数据库 schema(开发桥接与 Rust 后端共用同一份) -- v2:加入多端同步地基(uuid v7 / 软删除墓碑 / HLC / 变更日志) CREATE TABLE IF NOT EXISTS footprint_type ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, name TEXT NOT NULL UNIQUE, icon TEXT NOT NULL DEFAULT 'tag', color TEXT NOT NULL DEFAULT '#64748b', sort INTEGER NOT NULL DEFAULT 100, form_template TEXT NOT NULL DEFAULT '[]', is_builtin INTEGER NOT NULL DEFAULT 0, closed INTEGER NOT NULL DEFAULT 0, deleted_at TEXT, origin TEXT, hlc TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS taxonomy ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, kind TEXT NOT NULL, value TEXT NOT NULL, color TEXT, sort INTEGER NOT NULL DEFAULT 100, is_builtin INTEGER NOT NULL DEFAULT 0, deleted_at TEXT, origin TEXT, hlc TEXT, UNIQUE(kind, value) ); CREATE TABLE IF NOT EXISTS dimension ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, name TEXT NOT NULL UNIQUE, color TEXT, sort INTEGER NOT NULL DEFAULT 100, deleted_at TEXT, origin TEXT, hlc TEXT ); CREATE TABLE IF NOT EXISTS footprint ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, type_id INTEGER NOT NULL REFERENCES footprint_type(id), title TEXT NOT NULL, role TEXT, level TEXT, polarity TEXT NOT NULL DEFAULT 'positive', award_grade TEXT, rank INTEGER, organizer TEXT, start_date TEXT, end_date TEXT, status TEXT NOT NULL DEFAULT 'draft', description TEXT NOT NULL DEFAULT '', result TEXT NOT NULL DEFAULT '', metrics TEXT NOT NULL DEFAULT '{}', extras TEXT NOT NULL DEFAULT '{}', group_id TEXT, parent_id INTEGER, tags TEXT NOT NULL DEFAULT '[]', weight INTEGER NOT NULL DEFAULT 3, search_text TEXT NOT NULL DEFAULT '', deleted_at TEXT, origin TEXT, hlc TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')), updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS footprint_dimension ( footprint_id INTEGER NOT NULL REFERENCES footprint(id) ON DELETE CASCADE, dimension_id INTEGER NOT NULL REFERENCES dimension(id) ON DELETE CASCADE, PRIMARY KEY (footprint_id, dimension_id) ); CREATE TABLE IF NOT EXISTS attachment ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, owner_type TEXT NOT NULL DEFAULT 'footprint', owner_id INTEGER NOT NULL, kind TEXT, file_name TEXT NOT NULL, mime TEXT, size INTEGER NOT NULL DEFAULT 0, rel_path TEXT NOT NULL, sha256 TEXT, thumb_rel_path TEXT, taken_at TEXT, deleted_at TEXT, origin TEXT, hlc TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS semester ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, term TEXT NOT NULL UNIQUE, gpa REAL, rank INTEGER, total_students INTEGER, deleted_at TEXT, origin TEXT, hlc TEXT ); CREATE TABLE IF NOT EXISTS course_grade ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, semester_id INTEGER NOT NULL REFERENCES semester(id) ON DELETE CASCADE, course_name TEXT NOT NULL, credit REAL NOT NULL DEFAULT 0, score REAL, grade_point REAL, deleted_at TEXT, origin TEXT, hlc TEXT ); -- 成绩梯度(等级带):用户可配置;单科成绩按 min <= 分数 < max 定级(满分档闭区间) CREATE TABLE IF NOT EXISTS grade_band ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, label TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#64748b', min REAL NOT NULL DEFAULT 0, max REAL NOT NULL DEFAULT 100, sort INTEGER NOT NULL DEFAULT 100, deleted_at TEXT, origin TEXT, hlc TEXT ); CREATE TABLE IF NOT EXISTS metric_def ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, key TEXT NOT NULL UNIQUE, label TEXT NOT NULL, unit TEXT, agg TEXT NOT NULL DEFAULT 'sum', section TEXT, deleted_at TEXT, origin TEXT, hlc TEXT ); CREATE TABLE IF NOT EXISTS saved_view ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, name TEXT NOT NULL UNIQUE, filter TEXT NOT NULL DEFAULT '{}', sort TEXT NOT NULL DEFAULT '{}', is_default INTEGER NOT NULL DEFAULT 0, deleted_at TEXT, origin TEXT, hlc TEXT, updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime')) ); -- ===== 同步基础设施 ===== CREATE TABLE IF NOT EXISTS sync_state ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS sync_oplog ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, row_uuid TEXT NOT NULL, op TEXT NOT NULL, hlc TEXT NOT NULL, synced_at TEXT ); CREATE VIRTUAL TABLE IF NOT EXISTS footprint_fts USING fts5( search_text, content='footprint', content_rowid='id', tokenize='trigram' ); CREATE TRIGGER IF NOT EXISTS footprint_ai AFTER INSERT ON footprint BEGIN INSERT INTO footprint_fts(rowid, search_text) VALUES (new.id, new.search_text); END; CREATE TRIGGER IF NOT EXISTS footprint_ad AFTER DELETE ON footprint BEGIN INSERT INTO footprint_fts(footprint_fts, rowid, search_text) VALUES ('delete', old.id, old.search_text); END; CREATE TRIGGER IF NOT EXISTS footprint_au AFTER UPDATE OF search_text ON footprint BEGIN INSERT INTO footprint_fts(footprint_fts, rowid, search_text) VALUES ('delete', old.id, old.search_text); INSERT INTO footprint_fts(rowid, search_text) VALUES (new.id, new.search_text); END; CREATE INDEX IF NOT EXISTS idx_footprint_start ON footprint(start_date); CREATE INDEX IF NOT EXISTS idx_footprint_type ON footprint(type_id); CREATE INDEX IF NOT EXISTS idx_footprint_group ON footprint(group_id); CREATE INDEX IF NOT EXISTS idx_footprint_hlc ON footprint(hlc); CREATE INDEX IF NOT EXISTS idx_att_owner ON attachment(owner_type, owner_id); CREATE INDEX IF NOT EXISTS idx_oplog_pending ON sync_oplog(synced_at); CREATE UNIQUE INDEX IF NOT EXISTS idx_footprint_uuid ON footprint(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_footprint_type_uuid ON footprint_type(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_taxonomy_uuid ON taxonomy(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_dimension_uuid ON dimension(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_attachment_uuid ON attachment(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_semester_uuid ON semester(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_course_grade_uuid ON course_grade(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_metric_def_uuid ON metric_def(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_saved_view_uuid ON saved_view(uuid); CREATE UNIQUE INDEX IF NOT EXISTS idx_grade_band_uuid ON grade_band(uuid);