//! Thin `sync_state` key-value helpers over the sqlx pool. //! //! `sync_state` is a plain KV table (device_id, auto_sync_enabled, //! sync_interval_minutes, last_sync_at, ...) that the engine and the app share. //! Reading and writing settings is app-side and needs only the pool, so these //! stay as sqlx helpers here rather than routing through the store. The engine //! owns the *sync* semantics (cursor, changelog, apply); these touch only user //! settings and the pending-count readout for the status UI. use goingson_core::CoreError; use sqlx::SqlitePool; use std::collections::HashMap; /// Read several `sync_state` values in one query. pub(crate) async fn get_sync_states_batch( pool: &SqlitePool, keys: &[&str], ) -> Result, CoreError> { if keys.is_empty() { return Ok(HashMap::new()); } let placeholders = keys.iter().map(|_| "?").collect::>().join(","); let query = format!("SELECT key, value FROM sync_state WHERE key IN ({placeholders})"); let mut q = sqlx::query_as::<_, (String, String)>(sqlx::AssertSqlSafe(query.as_str())); for key in keys { q = q.bind(*key); } let rows = q.fetch_all(pool).await.map_err(CoreError::database)?; Ok(rows.into_iter().collect()) } /// Upsert one `sync_state` value. pub(crate) async fn set_sync_state( pool: &SqlitePool, key: &str, value: &str, ) -> Result<(), CoreError> { sqlx::query("INSERT OR REPLACE INTO sync_state (key, value) VALUES (?, ?)") .bind(key) .bind(value) .execute(pool) .await .map_err(CoreError::database)?; Ok(()) } /// Count local changes not yet pushed (drives the status UI's pending badge). pub(crate) async fn count_pending_changes(pool: &SqlitePool) -> Result { let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM sync_changelog WHERE pushed = 0") .fetch_one(pool) .await .map_err(CoreError::database)?; Ok(row.0) }