use crate::config::Config; use crate::db::migrations::{ run_migrations, ADMIN_MIGRATIONS, ANALYTICS_MIGRATIONS, CONTENT_MIGRATIONS, SYSTEM_MIGRATIONS, }; use crate::db::sqlite::{enable_foreign_keys, enable_wal}; use rusqlite::Connection; use std::fs; use std::sync::{Arc, Mutex}; pub mod admin; pub mod analytics; pub mod audit_events; pub mod content; pub mod migrations; pub mod preview; pub mod qr; pub mod sqlite; #[derive(Clone)] pub struct Db { pub admin: Arc>, pub content: Arc>, pub analytics: Arc>, pub system: Arc>, } impl Db { pub fn init(config: &Config) -> Result> { // Ensure data directory exists if !config.data_dir.exists() { fs::create_dir_all(&config.data_dir)?; } let admin_path = config.data_dir.join("admin.db"); let content_path = config.data_dir.join("content.db"); let analytics_path = config.data_dir.join("analytics.db"); let system_path = config.data_dir.join("system.db"); use tracing::info; info!("Opening admin.db"); let mut admin_conn = Connection::open(admin_path)?; info!("Opening content.db"); let mut content_conn = Connection::open(content_path)?; info!("Opening analytics.db"); let mut analytics_conn = Connection::open(analytics_path)?; info!("Opening system.db"); let mut system_conn = Connection::open(system_path)?; // Enable WAL mode for better concurrency and write performance info!(database = "admin", "Enabling WAL mode on admin.db"); enable_wal(&admin_conn, "admin")?; info!(database = "content", "Enabling WAL mode on content.db"); enable_wal(&content_conn, "content")?; info!(database = "analytics", "Enabling WAL mode on analytics.db"); enable_wal(&analytics_conn, "analytics")?; info!(database = "system", "Enabling WAL mode on system.db"); enable_wal(&system_conn, "system")?; // Enable foreign key support info!( database = "admin", "Enabling foreign key enforcement on admin.db" ); enable_foreign_keys(&admin_conn, "admin")?; info!( database = "content", "Enabling foreign key enforcement on content.db" ); enable_foreign_keys(&content_conn, "content")?; info!( database = "analytics", "Enabling foreign key enforcement on analytics.db" ); enable_foreign_keys(&analytics_conn, "analytics")?; info!( database = "system", "Enabling foreign key enforcement on system.db" ); enable_foreign_keys(&system_conn, "system")?; // 1. Run migrations for system.db first, as it receives secondary audit records info!("Running system migrations"); run_migrations(&mut system_conn, "system", SYSTEM_MIGRATIONS, None)?; let system_arc = Arc::new(Mutex::new(system_conn)); // 2. Run migrations for other databases with system.db logging info!("Running admin migrations"); run_migrations( &mut admin_conn, "admin", ADMIN_MIGRATIONS, Some(&system_arc), )?; info!("Running content migrations"); run_migrations( &mut content_conn, "content", CONTENT_MIGRATIONS, Some(&system_arc), )?; info!("Running analytics migrations"); run_migrations( &mut analytics_conn, "analytics", ANALYTICS_MIGRATIONS, Some(&system_arc), )?; Ok(Self { admin: Arc::new(Mutex::new(admin_conn)), content: Arc::new(Mutex::new(content_conn)), analytics: Arc::new(Mutex::new(analytics_conn)), system: system_arc, }) } pub fn compact(&self) -> Result<(), rusqlite::Error> { let admin = self.admin.lock().unwrap(); admin.execute("VACUUM;", [])?; let content = self.content.lock().unwrap(); content.execute("VACUUM;", [])?; let analytics = self.analytics.lock().unwrap(); analytics.execute("VACUUM;", [])?; let system = self.system.lock().unwrap(); system.execute("VACUUM;", [])?; Ok(()) } } #[cfg(test)] mod db_init_tests { use super::*; use std::path::PathBuf; #[test] fn test_db_init() { let temp_dir = PathBuf::from("./temp_test_db_dir"); if temp_dir.exists() { let _ = std::fs::remove_dir_all(&temp_dir); } let mut config = Config::load(); config.data_dir = temp_dir.clone(); let db = Db::init(&config); // Cleanup if temp_dir.exists() { let _ = std::fs::remove_dir_all(&temp_dir); } assert!(db.is_ok(), "Failed to init DB: {:?}", db.err()); } }