use rusqlite::Connection; use super::errors::DatabaseError; const CURRENT_SCHEMA_VERSION: i32 = 1; pub fn create_tables(conn: &Connection) -> Result<(), DatabaseError> { conn.execute_batch( " CREATE TABLE IF NOT EXISTS schema_version ( version INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, settings TEXT, status TEXT NOT NULL DEFAULT 'active' ); CREATE TABLE IF NOT EXISTS media_files ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), file_path TEXT NOT NULL, file_hash TEXT, duration_ms INTEGER, sample_rate INTEGER, channels INTEGER, format TEXT, file_size INTEGER, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS speakers ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), label TEXT NOT NULL, display_name TEXT, color TEXT, metadata TEXT ); CREATE TABLE IF NOT EXISTS segments ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), media_file_id TEXT NOT NULL REFERENCES media_files(id), speaker_id TEXT REFERENCES speakers(id), start_ms INTEGER NOT NULL, end_ms INTEGER NOT NULL, text TEXT NOT NULL, original_text TEXT, confidence REAL, is_edited INTEGER NOT NULL DEFAULT 0, edited_at TEXT, segment_index INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS words ( id TEXT PRIMARY KEY, segment_id TEXT NOT NULL REFERENCES segments(id), word TEXT NOT NULL, start_ms INTEGER NOT NULL, end_ms INTEGER NOT NULL, confidence REAL, word_index INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS ai_outputs ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), output_type TEXT NOT NULL, prompt TEXT, content TEXT NOT NULL, provider TEXT, created_at TEXT NOT NULL, metadata TEXT ); CREATE TABLE IF NOT EXISTS annotations ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id), start_ms INTEGER NOT NULL, end_ms INTEGER, text TEXT NOT NULL, type TEXT NOT NULL DEFAULT 'bookmark' ); CREATE INDEX IF NOT EXISTS idx_segments_project ON segments(project_id, segment_index); CREATE INDEX IF NOT EXISTS idx_segments_time ON segments(media_file_id, start_ms); CREATE INDEX IF NOT EXISTS idx_words_segment ON words(segment_id, word_index); CREATE INDEX IF NOT EXISTS idx_words_time ON words(start_ms, end_ms); CREATE INDEX IF NOT EXISTS idx_ai_outputs_project ON ai_outputs(project_id, output_type); ", )?; // Initialize schema version if empty let count: i32 = conn.query_row( "SELECT COUNT(*) FROM schema_version", [], |row| row.get(0), )?; if count == 0 { conn.execute( "INSERT INTO schema_version (version) VALUES (?1)", [CURRENT_SCHEMA_VERSION], )?; } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_tables_idempotent() { let conn = Connection::open_in_memory().unwrap(); conn.pragma_update(None, "foreign_keys", "ON").unwrap(); create_tables(&conn).unwrap(); // Running again should be fine (IF NOT EXISTS) create_tables(&conn).unwrap(); } #[test] fn test_schema_version() { let conn = Connection::open_in_memory().unwrap(); create_tables(&conn).unwrap(); let version: i32 = conn .query_row("SELECT version FROM schema_version", [], |row| row.get(0)) .unwrap(); assert_eq!(version, CURRENT_SCHEMA_VERSION); } }