171 lines
5.3 KiB
Rust
171 lines
5.3 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
|
|
{
|
|
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<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())
|
|
}
|
|
|
|
/// 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(())
|
|
}
|