//! SQLite implementation of the SyncAccountRepository. use async_trait::async_trait; use sqlx::SqlitePool; use goingson_core::{ CoreError, Result, SyncAccount, SyncAccountId, SyncAccountRepository, UserId, }; use crate::utils::{format_datetime_now, parse_datetime, parse_uuid}; #[derive(Debug, Clone, sqlx::FromRow)] struct SyncAccountRow { pub id: String, pub user_id: String, pub provider: String, pub account_name: String, pub email: Option, pub sync_calendars: i32, pub sync_contacts: i32, pub calendar_ids: String, pub last_calendar_sync: Option, pub last_contact_sync: Option, pub sync_interval_minutes: i32, pub enabled: i32, pub created_at: String, } fn sync_account_from_row(row: SyncAccountRow) -> Result { let calendar_ids: Vec = serde_json::from_str(&row.calendar_ids) .unwrap_or_default(); let last_calendar_sync = row.last_calendar_sync .as_ref() .map(|s| parse_datetime(s)) .transpose()?; let last_contact_sync = row.last_contact_sync .as_ref() .map(|s| parse_datetime(s)) .transpose()?; Ok(SyncAccount { id: parse_uuid(&row.id)?.into(), user_id: parse_uuid(&row.user_id)?.into(), provider: row.provider, account_name: row.account_name, email: row.email, sync_calendars: row.sync_calendars != 0, sync_contacts: row.sync_contacts != 0, calendar_ids, last_calendar_sync, last_contact_sync, sync_interval_minutes: row.sync_interval_minutes, enabled: row.enabled != 0, created_at: parse_datetime(&row.created_at)?, }) } /// SQLite-backed implementation of [`SyncAccountRepository`]. pub struct SqliteSyncAccountRepository { pool: SqlitePool, } impl SqliteSyncAccountRepository { #[tracing::instrument(skip_all)] pub fn new(pool: SqlitePool) -> Self { Self { pool } } } #[async_trait] impl SyncAccountRepository for SqliteSyncAccountRepository { #[tracing::instrument(skip_all)] async fn list_all(&self, user_id: UserId) -> Result> { let rows = sqlx::query_as::<_, SyncAccountRow>( "SELECT id, user_id, provider, account_name, email, sync_calendars, sync_contacts, calendar_ids, last_calendar_sync, last_contact_sync, sync_interval_minutes, enabled, created_at FROM sync_accounts WHERE user_id = ? ORDER BY created_at ASC" ) .bind(user_id.to_string()) .fetch_all(&self.pool) .await .map_err(CoreError::database)?; rows.into_iter().map(sync_account_from_row).collect() } #[tracing::instrument(skip_all)] async fn get_by_id(&self, id: SyncAccountId, user_id: UserId) -> Result> { let row = sqlx::query_as::<_, SyncAccountRow>( "SELECT id, user_id, provider, account_name, email, sync_calendars, sync_contacts, calendar_ids, last_calendar_sync, last_contact_sync, sync_interval_minutes, enabled, created_at FROM sync_accounts WHERE id = ? AND user_id = ?" ) .bind(id.to_string()) .bind(user_id.to_string()) .fetch_optional(&self.pool) .await .map_err(CoreError::database)?; row.map(sync_account_from_row).transpose() } #[tracing::instrument(skip_all)] async fn create(&self, user_id: UserId, provider: &str, account_name: &str, email: Option<&str>) -> Result { let id = SyncAccountId::new(); let now = format_datetime_now(); sqlx::query( "INSERT INTO sync_accounts (id, user_id, provider, account_name, email, created_at) VALUES (?, ?, ?, ?, ?, ?)" ) .bind(id.to_string()) .bind(user_id.to_string()) .bind(provider) .bind(account_name) .bind(email) .bind(&now) .execute(&self.pool) .await .map_err(CoreError::database)?; self.get_by_id(id, user_id) .await? .ok_or_else(|| CoreError::internal("Failed to retrieve created sync account")) } #[tracing::instrument(skip_all)] async fn update(&self, id: SyncAccountId, user_id: UserId, account_name: &str, sync_calendars: bool, sync_contacts: bool, enabled: bool) -> Result> { let result = sqlx::query( "UPDATE sync_accounts SET account_name = ?, sync_calendars = ?, sync_contacts = ?, enabled = ? WHERE id = ? AND user_id = ?" ) .bind(account_name) .bind(sync_calendars as i32) .bind(sync_contacts as i32) .bind(enabled as i32) .bind(id.to_string()) .bind(user_id.to_string()) .execute(&self.pool) .await .map_err(CoreError::database)?; if result.rows_affected() > 0 { self.get_by_id(id, user_id).await } else { Ok(None) } } #[tracing::instrument(skip_all)] async fn delete(&self, id: SyncAccountId, user_id: UserId) -> Result { let result = sqlx::query("DELETE FROM sync_accounts WHERE id = ? AND user_id = ?") .bind(id.to_string()) .bind(user_id.to_string()) .execute(&self.pool) .await .map_err(CoreError::database)?; Ok(result.rows_affected() > 0) } }