track-core 0.1.0

Shared backend primitives and repositories for the track issue tracker.
Documentation
use serde::{de::DeserializeOwned, Serialize};
use sqlx::Row;

use crate::database::DatabaseContext;
use crate::errors::{ErrorCode, TrackError};

#[derive(Debug, Clone)]
pub struct SettingsRepository {
    database: DatabaseContext,
}

impl SettingsRepository {
    pub fn new(database: Option<DatabaseContext>) -> Result<Self, TrackError> {
        let database = match database {
            Some(database) => database,
            None => DatabaseContext::new(None)?,
        };
        database.initialize()?;

        Ok(Self { database })
    }

    pub fn load_json<T>(&self, key: &str) -> Result<Option<T>, TrackError>
    where
        T: DeserializeOwned + Send + 'static,
    {
        let key = key.to_owned();
        self.database.run(move |connection| {
            Box::pin(async move {
                let row =
                    sqlx::query("SELECT setting_json FROM backend_settings WHERE setting_key = ?1")
                        .bind(&key)
                        .fetch_optional(&mut *connection)
                        .await
                        .map_err(|error| {
                            TrackError::new(
                                ErrorCode::TaskWriteFailed,
                                format!("Could not load backend setting `{key}`: {error}"),
                            )
                        })?;

                row.map(|row| {
                    serde_json::from_str::<T>(row.get::<String, _>("setting_json").as_str())
                        .map_err(|error| {
                            TrackError::new(
                                ErrorCode::InvalidConfig,
                                format!("Backend setting `{key}` is not valid JSON: {error}"),
                            )
                        })
                })
                .transpose()
            })
        })
    }

    pub fn save_json<T>(&self, key: &str, value: &T) -> Result<(), TrackError>
    where
        T: Serialize,
    {
        let key = key.to_owned();
        let serialized = serde_json::to_string(value).map_err(|error| {
            TrackError::new(
                ErrorCode::InvalidConfig,
                format!("Could not serialize backend setting `{key}`: {error}"),
            )
        })?;

        self.database.run(move |connection| {
            Box::pin(async move {
                sqlx::query(
                    r#"
                    INSERT INTO backend_settings (setting_key, setting_json)
                    VALUES (?1, ?2)
                    ON CONFLICT(setting_key) DO UPDATE SET setting_json = excluded.setting_json
                    "#,
                )
                .bind(&key)
                .bind(&serialized)
                .execute(&mut *connection)
                .await
                .map_err(|error| {
                    TrackError::new(
                        ErrorCode::TaskWriteFailed,
                        format!("Could not save backend setting `{key}`: {error}"),
                    )
                })?;

                Ok(())
            })
        })
    }

    pub fn delete(&self, key: &str) -> Result<(), TrackError> {
        let key = key.to_owned();
        self.database.run(move |connection| {
            Box::pin(async move {
                sqlx::query("DELETE FROM backend_settings WHERE setting_key = ?1")
                    .bind(&key)
                    .execute(&mut *connection)
                    .await
                    .map_err(|error| {
                        TrackError::new(
                            ErrorCode::TaskWriteFailed,
                            format!("Could not delete backend setting `{key}`: {error}"),
                        )
                    })?;

                Ok(())
            })
        })
    }
}