use crate::analytics::queue::AnalyticsQueue; use crate::config::Config; use crate::db::Db; use rusqlite::Connection; use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::time::Instant; #[derive(Clone)] pub struct UserDbs { pub content: Arc>, pub analytics: Arc>, pub profile: Arc>, } #[derive(Clone)] pub struct AppState { pub admin_db: Arc>, pub content_db: Arc>, pub analytics_db: Arc>, pub system_db: Arc>, pub users_db: Arc>, pub user_dbs: Arc>>, pub db: Db, pub config: Config, pub analytics_queue: AnalyticsQueue, pub start_time: Instant, } impl AppState { pub fn get_user_dbs(&self, user_id: i64) -> Result { let mut pool = crate::utils::lock_db(&self.user_dbs, "user_dbs")?; if let Some(dbs) = pool.get(&user_id) { return Ok(dbs.clone()); } // Open connection and run migrations let user_dir = self.config.data_dir.join("users").join(user_id.to_string()); std::fs::create_dir_all(&user_dir)?; let content_path = user_dir.join("content.db"); let analytics_path = user_dir.join("analytics.db"); let profile_path = user_dir.join("profile.db"); let mut content_conn = Connection::open(content_path)?; let mut analytics_conn = Connection::open(analytics_path)?; let profile_conn = Connection::open(profile_path)?; crate::db::sqlite::enable_wal(&content_conn, "content")?; crate::db::sqlite::enable_wal(&analytics_conn, "analytics")?; crate::db::sqlite::enable_wal(&profile_conn, "profile")?; crate::db::sqlite::enable_foreign_keys(&content_conn, "content")?; crate::db::sqlite::enable_foreign_keys(&analytics_conn, "analytics")?; crate::db::sqlite::enable_foreign_keys(&profile_conn, "profile")?; // Run migrations crate::db::migrations::run_migrations( &mut content_conn, "content", crate::db::migrations::CONTENT_MIGRATIONS, Some(&self.system_db), ) .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; crate::db::migrations::run_migrations( &mut analytics_conn, "analytics", crate::db::migrations::ANALYTICS_MIGRATIONS, Some(&self.system_db), ) .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; profile_conn.execute_batch( "CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL );", )?; let dbs = UserDbs { content: Arc::new(Mutex::new(content_conn)), analytics: Arc::new(Mutex::new(analytics_conn)), profile: Arc::new(Mutex::new(profile_conn)), }; pool.insert(user_id, dbs.clone()); Ok(dbs) } pub fn db_compact(&self) -> Result<(), crate::error::AppError> { crate::utils::lock_db(&self.admin_db, "admin_db")?.execute("VACUUM;", [])?; crate::utils::lock_db(&self.content_db, "content_db")?.execute("VACUUM;", [])?; crate::utils::lock_db(&self.analytics_db, "analytics_db")?.execute("VACUUM;", [])?; crate::utils::lock_db(&self.system_db, "system_db")?.execute("VACUUM;", [])?; crate::utils::lock_db(&self.users_db, "users_db")?.execute("VACUUM;", [])?; Ok(()) } }