mmex_lib 0.1.1-beta.1

Library for interacting with Money Manager EX data and logic
Documentation
use rusqlite::Row;
use sea_query::{Alias, Expr, Query, SimpleExpr, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;

use crate::domain::payees::{Payee, PayeeError, PayeeId, PayeeRepository};
use crate::infrastructure::db_executor::DbExecutor;
use crate::MmexError;

pub struct PayeeMapper;

impl PayeeMapper {
    pub fn map_row(row: &Row) -> rusqlite::Result<Payee> {
        Ok(Payee {
            id: PayeeId::new(row.get("PAYEEID")?),
            name: row.get("PAYEENAME")?,
            category_id: row.get("CATEGID")?,
            number: row.get("NUMBER")?,
            website: row.get("WEBSITE")?,
            notes: row.get("NOTES")?,
            active: row.get::<_, i32>("ACTIVE")? != 0,
            pattern: row.get("PATTERN")?,
        })
    }
}

pub struct SqlPayeeRepository<'a, E: DbExecutor> {
    executor: &'a E,
}

impl<'a, E: DbExecutor> SqlPayeeRepository<'a, E> {
    pub fn new(executor: &'a E) -> Self {
        Self { executor }
    }
}

impl<'a, E: DbExecutor> PayeeRepository for SqlPayeeRepository<'a, E> {
    fn find_all(&self) -> Result<Vec<Payee>, PayeeError> {
        let (sql, _) = Query::select()
            .columns([
                "PAYEEID",
                "PAYEENAME",
                "CATEGID",
                "NUMBER",
                "WEBSITE",
                "NOTES",
                "ACTIVE",
                "PATTERN",
            ])
            .from(Alias::new("PAYEE_V1"))
            .build(SqliteQueryBuilder);

        Ok(self
            .executor
            .query_map_ext(&sql, [], |row| PayeeMapper::map_row(row))?)
    }

    fn find_by_id(&self, id: PayeeId) -> Result<Option<Payee>, PayeeError> {
        let (sql, values) = Query::select()
            .columns([
                "PAYEEID",
                "PAYEENAME",
                "CATEGID",
                "NUMBER",
                "WEBSITE",
                "NOTES",
                "ACTIVE",
                "PATTERN",
            ])
            .from(Alias::new("PAYEE_V1"))
            .and_where(Expr::col(Alias::new("PAYEEID")).eq(id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        match self
            .executor
            .query_row_ext(&sql, &values.as_params()[..], |row| {
                PayeeMapper::map_row(row)
            }) {
            Ok(payee) => Ok(Some(payee)),
            Err(MmexError::NotFound) => Ok(None),
            Err(e) => Err(PayeeError::Common(e)),
        }
    }

    fn insert(&self, p: &Payee) -> Result<Payee, PayeeError> {
        let (sql, values) = Query::insert()
            .into_table(Alias::new("PAYEE_V1"))
            .columns([
                Alias::new("PAYEENAME"),
                Alias::new("CATEGID"),
                Alias::new("NUMBER"),
                Alias::new("WEBSITE"),
                Alias::new("NOTES"),
                Alias::new("ACTIVE"),
                Alias::new("PATTERN"),
            ])
            .values_panic([
                p.name.clone().into(),
                p.category_id.into(),
                p.number.clone().into(),
                p.website.clone().into(),
                p.notes.clone().into(),
                (if p.active { 1 } else { 0 }).into(),
                p.pattern.clone().into(),
            ])
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;

        let last_id: i64 = self
            .executor
            .query_row_ext("SELECT last_insert_rowid()", [], |r| r.get(0))?;

        let mut new_payee = p.clone();
        new_payee.id = PayeeId::new(last_id);
        Ok(new_payee)
    }

    fn update(&self, p: &Payee) -> Result<(), PayeeError> {
        let (sql, values) = Query::update()
            .table(Alias::new("PAYEE_V1"))
            .values([
                (Alias::new("PAYEENAME"), p.name.clone().into()),
                (Alias::new("CATEGID"), p.category_id.into()),
                (Alias::new("NUMBER"), p.number.clone().into()),
                (Alias::new("WEBSITE"), p.website.clone().into()),
                (Alias::new("NOTES"), p.notes.clone().into()),
                (Alias::new("ACTIVE"), (if p.active { 1 } else { 0 }).into()),
                (Alias::new("PATTERN"), p.pattern.clone().into()),
            ])
            .and_where(Expr::col(Alias::new("PAYEEID")).eq(p.id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }

    fn update_partial(
        &self,
        id: PayeeId,
        update: crate::domain::payees::PayeeUpdate,
    ) -> Result<(), PayeeError> {
        let mut query = Query::update();
        query.table(Alias::new("PAYEE_V1"));

        let mut has_values = false;

        if let Some(name) = update.name {
            query.value(Alias::new("PAYEENAME"), SimpleExpr::from(name));
            has_values = true;
        }
        if let Some(cat_id) = update.category_id {
            query.value(Alias::new("CATEGID"), SimpleExpr::from(cat_id));
            has_values = true;
        }
        if let Some(number) = update.number {
            query.value(Alias::new("NUMBER"), SimpleExpr::from(number));
            has_values = true;
        }
        if let Some(website) = update.website {
            query.value(Alias::new("WEBSITE"), SimpleExpr::from(website));
            has_values = true;
        }
        if let Some(notes) = update.notes {
            query.value(Alias::new("NOTES"), SimpleExpr::from(notes));
            has_values = true;
        }
        if let Some(active) = update.active {
            query.value(
                Alias::new("ACTIVE"),
                SimpleExpr::from(if active { 1 } else { 0 }),
            );
            has_values = true;
        }
        if let Some(pattern) = update.pattern {
            query.value(Alias::new("PATTERN"), SimpleExpr::from(pattern));
            has_values = true;
        }

        if !has_values {
            return Ok(());
        }

        query.and_where(Expr::col(Alias::new("PAYEEID")).eq(id.v1));

        let (sql, values) = query.build_rusqlite(SqliteQueryBuilder);
        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }

    fn delete(&self, id: PayeeId) -> Result<(), PayeeError> {
        let (sql, values) = Query::delete()
            .from_table(Alias::new("PAYEE_V1"))
            .and_where(Expr::col(Alias::new("PAYEEID")).eq(id.v1))
            .build_rusqlite(SqliteQueryBuilder);

        self.executor.execute_ext(&sql, &values.as_params()[..])?;
        Ok(())
    }
}