diff --git a/k-tv-backend/infra/src/app_settings_repository.rs b/k-tv-backend/infra/src/app_settings_repository.rs new file mode 100644 index 0000000..20c2827 --- /dev/null +++ b/k-tv-backend/infra/src/app_settings_repository.rs @@ -0,0 +1,83 @@ +//! SQLite implementation of IAppSettingsRepository. + +use async_trait::async_trait; +use sqlx::SqlitePool; +use domain::{DomainError, DomainResult, IAppSettingsRepository}; + +pub struct SqliteAppSettingsRepository { + pool: SqlitePool, +} + +impl SqliteAppSettingsRepository { + pub fn new(pool: SqlitePool) -> Self { + Self { pool } + } +} + +#[async_trait] +impl IAppSettingsRepository for SqliteAppSettingsRepository { + async fn get(&self, key: &str) -> DomainResult> { + sqlx::query_scalar::<_, String>("SELECT value FROM app_settings WHERE key = ?") + .bind(key) + .fetch_optional(&self.pool) + .await + .map_err(|e| DomainError::InfrastructureError(e.to_string())) + } + + async fn set(&self, key: &str, value: &str) -> DomainResult<()> { + sqlx::query("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)") + .bind(key) + .bind(value) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(|e| DomainError::InfrastructureError(e.to_string())) + } + + async fn get_all(&self) -> DomainResult> { + sqlx::query_as::<_, (String, String)>("SELECT key, value FROM app_settings ORDER BY key") + .fetch_all(&self.pool) + .await + .map_err(|e| DomainError::InfrastructureError(e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::SqlitePool; + use domain::IAppSettingsRepository; + + async fn setup() -> SqlitePool { + let pool = SqlitePool::connect(":memory:").await.unwrap(); + sqlx::query( + "CREATE TABLE app_settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)" + ).execute(&pool).await.unwrap(); + sqlx::query("INSERT INTO app_settings VALUES ('library_sync_interval_hours', '6')") + .execute(&pool).await.unwrap(); + pool + } + + #[tokio::test] + async fn get_returns_seeded_value() { + let repo = SqliteAppSettingsRepository::new(setup().await); + let val = repo.get("library_sync_interval_hours").await.unwrap(); + assert_eq!(val, Some("6".to_string())); + } + + #[tokio::test] + async fn set_then_get() { + let repo = SqliteAppSettingsRepository::new(setup().await); + repo.set("library_sync_interval_hours", "12").await.unwrap(); + let val = repo.get("library_sync_interval_hours").await.unwrap(); + assert_eq!(val, Some("12".to_string())); + } + + #[tokio::test] + async fn get_all_returns_all_keys() { + let repo = SqliteAppSettingsRepository::new(setup().await); + let all = repo.get_all().await.unwrap(); + assert!(!all.is_empty()); + assert!(all.iter().any(|(k, _)| k == "library_sync_interval_hours")); + } +} diff --git a/k-tv-backend/infra/src/lib.rs b/k-tv-backend/infra/src/lib.rs index b915328..bb3202f 100644 --- a/k-tv-backend/infra/src/lib.rs +++ b/k-tv-backend/infra/src/lib.rs @@ -18,6 +18,7 @@ pub mod db; pub mod factory; pub mod jellyfin; pub mod provider_registry; +mod app_settings_repository; mod activity_log_repository; mod channel_repository; mod library_repository; @@ -33,6 +34,8 @@ pub mod local_files; pub use db::run_migrations; pub use provider_registry::ProviderRegistry; +#[cfg(feature = "sqlite")] +pub use app_settings_repository::SqliteAppSettingsRepository; #[cfg(feature = "sqlite")] pub use activity_log_repository::SqliteActivityLogRepository; #[cfg(feature = "sqlite")]