diff --git a/moneymgr_backend/src/controllers/accounts_controller.rs b/moneymgr_backend/src/controllers/accounts_controller.rs index 96c5e14..0059274 100644 --- a/moneymgr_backend/src/controllers/accounts_controller.rs +++ b/moneymgr_backend/src/controllers/accounts_controller.rs @@ -1,8 +1,9 @@ use crate::controllers::HttpResult; use crate::extractors::account_extractor::AccountInPath; use crate::extractors::auth_extractor::AuthExtractor; -use crate::services::accounts_service; +use crate::models::accounts::Account; use crate::services::accounts_service::UpdateAccountQuery; +use crate::services::{accounts_service, movements_service}; use actix_web::{HttpResponse, web}; /// Create a new account @@ -16,9 +17,38 @@ pub async fn create(auth: AuthExtractor, req: web::Json) -> Ok(HttpResponse::Created().finish()) } +#[derive(serde::Deserialize)] +pub struct GetListQuery { + #[serde(default)] + include_balances: bool, +} + +#[derive(serde::Serialize)] +struct AccountAndBalance { + #[serde(flatten)] + account: Account, + balance: f32, +} + /// Get the list of accounts of the user -pub async fn get_list(auth: AuthExtractor) -> HttpResult { - Ok(HttpResponse::Ok().json(accounts_service::get_list_user(auth.user_id()).await?)) +pub async fn get_list(auth: AuthExtractor, query: web::Query) -> HttpResult { + let accounts = accounts_service::get_list_user(auth.user_id()).await?; + + Ok(match query.include_balances { + false => HttpResponse::Ok().json(accounts), + true => { + let balances = movements_service::get_balances(auth.user_id()).await?; + let accounts = accounts + .into_iter() + .map(|account| AccountAndBalance { + balance: *balances.get(&account.id()).unwrap_or(&0.0), + account, + }) + .collect::>(); + + HttpResponse::Ok().json(accounts) + } + }) } /// Get a single account diff --git a/moneymgr_backend/src/controllers/movement_controller.rs b/moneymgr_backend/src/controllers/movement_controller.rs index 324e9aa..e463b90 100644 --- a/moneymgr_backend/src/controllers/movement_controller.rs +++ b/moneymgr_backend/src/controllers/movement_controller.rs @@ -17,6 +17,11 @@ pub async fn create(auth: AuthExtractor, req: web::Json) -> Ok(HttpResponse::Created().finish()) } +/// Get the balances of all the accounts of the user +pub async fn get_accounts_balances(auth: AuthExtractor) -> HttpResult { + Ok(HttpResponse::Ok().json(movements_service::get_balances(auth.user_id()).await?)) +} + /// Get the list of movements of an account pub async fn get_list_of_account(account_id: AccountInPath) -> HttpResult { Ok(HttpResponse::Ok() diff --git a/moneymgr_backend/src/main.rs b/moneymgr_backend/src/main.rs index d2fc456..cee458c 100644 --- a/moneymgr_backend/src/main.rs +++ b/moneymgr_backend/src/main.rs @@ -131,6 +131,10 @@ async fn main() -> std::io::Result<()> { ) // Movement controller .route("/api/movement", web::post().to(movement_controller::create)) + .route( + "/api/accounts/balances", + web::get().to(movement_controller::get_accounts_balances), + ) .route( "/api/account/{account_id}/movements", web::get().to(movement_controller::get_list_of_account), diff --git a/moneymgr_backend/src/models/accounts.rs b/moneymgr_backend/src/models/accounts.rs index 3db1075..a1ffac0 100644 --- a/moneymgr_backend/src/models/accounts.rs +++ b/moneymgr_backend/src/models/accounts.rs @@ -3,7 +3,9 @@ use crate::schema::*; use diesel::prelude::*; use std::fmt::{Display, Formatter}; -#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] +#[derive( + Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord, +)] pub struct AccountID(pub i32); #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/moneymgr_backend/src/services/movements_service.rs b/moneymgr_backend/src/services/movements_service.rs index 44036fc..fba4deb 100644 --- a/moneymgr_backend/src/services/movements_service.rs +++ b/moneymgr_backend/src/services/movements_service.rs @@ -7,8 +7,9 @@ use crate::models::users::UserID; use crate::schema::movements; use crate::services::{accounts_service, files_service}; use crate::utils::time_utils::time; -use diesel::RunQueryDsl; use diesel::prelude::*; +use diesel::{RunQueryDsl, sql_query}; +use std::collections::HashMap; #[derive(serde::Deserialize)] pub struct UpdateMovementQuery { @@ -136,6 +137,31 @@ pub async fn get_list_account(account_id: AccountID) -> anyhow::Result 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)))