pub mod error; mod repository; mod serialization; use domain::SecretStore; use sqlx::SqlitePool; use std::sync::Arc; pub use error::SqliteConfigError; pub struct SqliteConfigStore { pool: SqlitePool, secrets: Option>, } impl SqliteConfigStore { pub async fn new(database_url: &str) -> Result { Self::with_secrets(database_url, None).await } pub async fn with_secrets( database_url: &str, secrets: Option>, ) -> Result { let pool = SqlitePool::connect(database_url).await?; let store = Self { pool, secrets }; store.migrate().await?; Ok(store) } pub(crate) fn secrets(&self) -> Option<&(dyn SecretStore + Send + Sync)> { self.secrets.as_deref() } async fn migrate(&self) -> Result<(), sqlx::Error> { sqlx::query( "CREATE TABLE IF NOT EXISTS widgets ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, display_hint TEXT NOT NULL, data_source_id INTEGER NOT NULL, mappings TEXT NOT NULL, max_data_size INTEGER NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS data_sources ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, source_type TEXT NOT NULL, poll_interval_secs INTEGER NOT NULL, config TEXT NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS layout ( id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS presets ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, layout_data TEXT NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS theme ( id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL )", ) .execute(&self.pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS widget_state_cache ( widget_id INTEGER PRIMARY KEY, state_json TEXT NOT NULL )", ) .execute(&self.pool) .await?; // Add alignment columns to widgets (idempotent) let _ = sqlx::query("ALTER TABLE widgets ADD COLUMN h_align TEXT NOT NULL DEFAULT 'left'") .execute(&self.pool) .await; let _ = sqlx::query("ALTER TABLE widgets ADD COLUMN v_align TEXT NOT NULL DEFAULT 'top'") .execute(&self.pool) .await; Ok(()) } }