use crate::connections::db_connection::db; use crate::controllers::server_controller::ServerConstraints; use crate::models::accounts::AccountID; use crate::models::files::FileID; use crate::models::movements::{Movement, MovementID, NewMovement}; use crate::models::users::UserID; use crate::schema::movements; use crate::services::{accounts_service, files_service}; use crate::utils::time_utils::time; use diesel::prelude::*; use diesel::{RunQueryDsl, sql_query}; use std::collections::HashMap; #[derive(serde::Deserialize)] pub struct UpdateMovementQuery { pub account_id: AccountID, pub time: u64, pub label: String, pub file_id: Option, pub amount: f32, pub checked: bool, } impl UpdateMovementQuery { pub async fn check_error( &self, user_id: UserID, ref_movement: Option, ) -> anyhow::Result> { let constraints = ServerConstraints::default(); // Check movement label if !constraints.movement_label.check_str(&self.label) { return Ok(Some("Invalid movement label length!")); } // Check the account let Ok(account) = accounts_service::get_by_id(self.account_id).await else { return Ok(Some("The specified account does not exists!")); }; if account.user_id() != user_id { return Ok(Some("The user does not own the account!")); } // Check the file if let Some(file_id) = self.file_id { let file = files_service::get_file_with_id(file_id)?; if file.user_id() != user_id { return Ok(Some("The user does not own the referenced file!")); } } // Check for conflict with other movements if let Ok(movement) = get_by_account_label_amount_time(self.account_id, &self.label, self.amount, self.time) .await { if Some(movement.id()) != ref_movement { return Ok(Some( "A movement taken at the same time with the same label and the same amount already exists!", )); } } Ok(None) } } /// Create a new movement pub async fn create(query: &UpdateMovementQuery) -> anyhow::Result { let new_account = NewMovement { account_id: query.account_id.0, time: query.time as i64, label: &query.label, file_id: query.file_id.map(|f| f.0), amount: query.amount, checked: query.checked, time_create: time() as i64, time_update: time() as i64, }; let res: Movement = diesel::insert_into(movements::table) .values(&new_account) .get_result(&mut db()?)?; update(res.id(), query).await?; Ok(res) } /// Update a movement pub async fn update(id: MovementID, q: &UpdateMovementQuery) -> anyhow::Result<()> { diesel::update(movements::dsl::movements.filter(movements::dsl::id.eq(id.0))) .set(( movements::dsl::time_update.eq(time() as i64), movements::dsl::account_id.eq(q.account_id.0), movements::dsl::time.eq(q.time as i64), movements::dsl::label.eq(&q.label), movements::dsl::file_id.eq(&q.file_id.map(|f| f.0)), movements::dsl::amount.eq(q.amount), )) .execute(&mut db()?)?; Ok(()) } /// Get a single movement by its id pub async fn get_by_id(movement_id: MovementID) -> anyhow::Result { Ok(movements::table .filter(movements::dsl::id.eq(movement_id.0)) .get_result(&mut db()?)?) } /// Get a single movement by account_id, label, amount and time pub async fn get_by_account_label_amount_time( account_id: AccountID, label: &str, amount: f32, time: u64, ) -> anyhow::Result { Ok(movements::table .filter( movements::dsl::account_id .eq(account_id.0) .and(movements::dsl::label.eq(label)) .and(movements::dsl::amount.eq(amount)) .and(movements::dsl::time.eq(time as i64)), ) .get_result(&mut db()?)?) } /// Get the list of movements of an account pub async fn get_list_account(account_id: AccountID) -> anyhow::Result> { Ok(movements::table .filter(movements::dsl::account_id.eq(account_id.0)) .get_results(&mut db()?)?) } table! { accounts_balances (account_id) { account_id -> Int4, balance -> Float4, } } #[derive(QueryableByName)] #[diesel(table_name = accounts_balances)] struct AccountBalance { account_id: i32, balance: f32, } /// Get the balances of all the accounts of the user pub async fn get_balances(user_id: UserID) -> anyhow::Result> { let result = sql_query(format!("select a.id as account_id, sum(m.amount) as balance from accounts a right join movements m on a.id = m.account_id where a.user_id = {} group by a.id", user_id.0)) .get_results::(&mut db()?)?; Ok(result .into_iter() .map(|r| (AccountID(r.account_id), r.balance)) .collect()) } /// Delete a movement pub async fn delete(id: MovementID) -> anyhow::Result<()> { diesel::delete(movements::dsl::movements.filter(movements::dsl::id.eq(id.0))) .execute(&mut db()?)?; Ok(()) }