181 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
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<FileID>,
 | 
						|
    pub amount: f32,
 | 
						|
    pub checked: bool,
 | 
						|
}
 | 
						|
 | 
						|
impl UpdateMovementQuery {
 | 
						|
    pub async fn check_error(
 | 
						|
        &self,
 | 
						|
        user_id: UserID,
 | 
						|
        ref_movement: Option<MovementID>,
 | 
						|
    ) -> anyhow::Result<Option<&'static str>> {
 | 
						|
        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
 | 
						|
            && 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<Movement> {
 | 
						|
    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<Movement> {
 | 
						|
    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<Movement> {
 | 
						|
    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<Vec<Movement>> {
 | 
						|
    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<HashMap<AccountID, f32>> {
 | 
						|
    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::<AccountBalance>(&mut db()?)?;
 | 
						|
 | 
						|
    Ok(result
 | 
						|
        .into_iter()
 | 
						|
        .map(|r| (AccountID(r.account_id), r.balance))
 | 
						|
        .collect())
 | 
						|
}
 | 
						|
 | 
						|
/// Get all the movements of the user
 | 
						|
pub async fn get_all_movements_user(user_id: UserID) -> anyhow::Result<Vec<Movement>> {
 | 
						|
    let movements = sql_query(format!(
 | 
						|
        "select m.* from movements m join accounts a on a.id = m.account_id where a.user_id = {}",
 | 
						|
        user_id.0
 | 
						|
    ))
 | 
						|
    .load::<Movement>(&mut db()?)?;
 | 
						|
 | 
						|
    Ok(movements)
 | 
						|
}
 | 
						|
 | 
						|
/// 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(())
 | 
						|
}
 |