//! SQLite implementation of backup settings repository. //! //! Manages automated backup configuration including frequency, //! retention policy, and last backup tracking. use async_trait::async_trait; use chrono::{DateTime, Utc}; use sqlx::SqlitePool; use uuid::Uuid; use goingson_core::{BackupSettings, BackupSettingsRepository, CoreError, NewBackupSettings, Result, UserId}; use crate::utils::{format_datetime, format_datetime_now, parse_datetime, parse_uuid}; #[derive(Debug, Clone, sqlx::FromRow)] struct BackupSettingsRow { pub id: String, pub user_id: String, pub auto_backup_enabled: i32, pub backup_frequency_minutes: i32, pub max_backups_to_keep: i32, pub last_backup_at: Option, pub created_at: String, pub updated_at: String, } impl TryFrom for BackupSettings { type Error = CoreError; fn try_from(row: BackupSettingsRow) -> std::result::Result { let last_backup_at = row .last_backup_at .as_ref() .map(|s| parse_datetime(s)) .transpose()?; Ok(BackupSettings { id: parse_uuid(&row.id)?, user_id: parse_uuid(&row.user_id)?, auto_backup_enabled: row.auto_backup_enabled != 0, backup_frequency_minutes: row.backup_frequency_minutes, max_backups_to_keep: row.max_backups_to_keep, last_backup_at, created_at: parse_datetime(&row.created_at)?, updated_at: parse_datetime(&row.updated_at)?, }) } } /// SQLite-backed implementation of [`BackupSettingsRepository`]. /// /// Manages per-user backup configuration with automatic scheduling /// and retention policies. pub struct SqliteBackupSettingsRepository { pool: SqlitePool, } impl SqliteBackupSettingsRepository { /// Creates a new repository instance with the given connection pool. #[tracing::instrument(skip_all)] pub fn new(pool: SqlitePool) -> Self { Self { pool } } } #[async_trait] impl BackupSettingsRepository for SqliteBackupSettingsRepository { #[tracing::instrument(skip_all)] async fn get(&self, user_id: UserId) -> Result> { let row = sqlx::query_as::<_, BackupSettingsRow>( r#"SELECT id, user_id, auto_backup_enabled, backup_frequency_minutes, max_backups_to_keep, last_backup_at, created_at, updated_at FROM backup_settings WHERE user_id = ?"#, ) .bind(user_id.to_string()) .fetch_optional(&self.pool) .await .map_err(CoreError::database)?; row.map(BackupSettings::try_from).transpose() } #[tracing::instrument(skip_all)] async fn upsert(&self, user_id: UserId, settings: NewBackupSettings) -> Result { let now = format_datetime_now(); // Check if settings exist let existing = self.get(user_id).await?; if existing.is_some() { // Update sqlx::query( r#"UPDATE backup_settings SET auto_backup_enabled = ?, backup_frequency_minutes = ?, max_backups_to_keep = ?, updated_at = ? WHERE user_id = ?"#, ) .bind(if settings.auto_backup_enabled { 1 } else { 0 }) .bind(settings.backup_frequency_minutes) .bind(settings.max_backups_to_keep) .bind(&now) .bind(user_id.to_string()) .execute(&self.pool) .await .map_err(CoreError::database)?; self.get(user_id) .await? .ok_or_else(|| CoreError::internal("Failed to retrieve updated backup settings")) } else { // Insert let id = Uuid::new_v4(); sqlx::query( r#"INSERT INTO backup_settings (id, user_id, auto_backup_enabled, backup_frequency_minutes, max_backups_to_keep, last_backup_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NULL, ?, ?)"#, ) .bind(id.to_string()) .bind(user_id.to_string()) .bind(if settings.auto_backup_enabled { 1 } else { 0 }) .bind(settings.backup_frequency_minutes) .bind(settings.max_backups_to_keep) .bind(&now) .bind(&now) .execute(&self.pool) .await .map_err(CoreError::database)?; self.get(user_id) .await? .ok_or_else(|| CoreError::internal("Failed to retrieve created backup settings")) } } #[tracing::instrument(skip_all)] async fn update_last_backup_at(&self, user_id: UserId, timestamp: DateTime) -> Result<()> { let now = format_datetime_now(); let backup_time = format_datetime(×tamp); // Ensure settings exist with defaults first let existing = self.get(user_id).await?; if existing.is_none() { // Create default settings self.upsert( user_id, NewBackupSettings { auto_backup_enabled: true, backup_frequency_minutes: 15, max_backups_to_keep: 1, }, ) .await?; } sqlx::query( r#"UPDATE backup_settings SET last_backup_at = ?, updated_at = ? WHERE user_id = ?"#, ) .bind(&backup_time) .bind(&now) .bind(user_id.to_string()) .execute(&self.pool) .await .map_err(CoreError::database)?; Ok(()) } }