diff --git a/api/src/db/budget.rs b/api/src/db/budget.rs index a846690..b7bfed2 100644 --- a/api/src/db/budget.rs +++ b/api/src/db/budget.rs @@ -24,6 +24,16 @@ pub struct BudgetEntity { pub status: StatusType, } +#[derive(Debug, Default, FromRow)] +pub struct BudgetWithPermissionValueEntity { + pub id: Uuid, + pub name: String, + pub description: Option, + pub icon: Option, + pub permission_name: String, + pub permission_value: i32, +} + pub async fn insert_new_budget_for_user( pool: &DbPool, new_budget: NewBudgetEntity, @@ -48,3 +58,61 @@ pub async fn insert_new_budget_for_user( Ok((new_budget, budget_permissions)) } + +pub async fn get_all_budgets_for_user( + pool: &DbPool, + user_id: i32, +) -> Result, AppError> { + sqlx::query_as::<_, BudgetWithPermissionValueEntity>( + "SELECT + b.id as id, + b.name as name, + b.description as description, + b.icon as icon, + p.name as permission_name, + p.value as permission_value + FROM + public.budget b + INNER JOIN public.user_budget_permission ubp ON b.id = ubp.budget_id + INNER JOIN public.permission p on ubp.permission_id = p.id + WHERE + ubp.user_id = $1 + ORDER BY + b.id, + p.value;", + ) + .bind(user_id) + .fetch_all(pool) + .await + .inspect_err(|err| { + error!( + ?err, + user_id, "Cannot fetch permission values for the user's budgets" + ) + }) + .map_err(AppError::from) + .map(|all_budgets| { + all_budgets + .chunk_by(|a, b| a.id == b.id) + .map(|budgets| { + budgets.into_iter().fold( + BudgetWithPermissionValueEntity::default(), + |acc, entity| BudgetWithPermissionValueEntity { + id: entity.id, + name: entity.name.clone(), + description: entity.description.clone(), + icon: entity.icon.clone(), + permission_name: acc + .permission_name + .split('|') + .filter(|name| !name.is_empty()) + .chain([entity.permission_name.as_str()]) + .collect::>() + .join("|"), + permission_value: acc.permission_value + entity.permission_value, + }, + ) + }) + .collect::>() + }) +} diff --git a/api/src/db/user_budget_permission.rs b/api/src/db/user_budget_permission.rs index 8872e05..48bdb6d 100644 --- a/api/src/db/user_budget_permission.rs +++ b/api/src/db/user_budget_permission.rs @@ -35,7 +35,7 @@ pub async fn associate_budget_with_user_as_owner( let values = get_all_permissions_by_category(pool, PermissionCategoryType::Budget) .await? .into_iter() - .map(|permission| format!("({user_id}, {budget_id}, {})", permission.id)) + .map(|permission| format!("({user_id}, '{budget_id}', {})", permission.id)) .collect::>() .join(","); diff --git a/api/src/requests/account/create/handler.rs b/api/src/requests/account/create/handler.rs index c59be0c..8b081a0 100644 --- a/api/src/requests/account/create/handler.rs +++ b/api/src/requests/account/create/handler.rs @@ -7,7 +7,7 @@ use http::StatusCode; use crate::{ db::{DbPool, NewAccountEntity, insert_new_account_for_user}, - models::{AppError, Session}, + models::{ApiResponse, AppError, Session}, requests::AppState, }; @@ -22,14 +22,18 @@ pub async fn account_creation_post_handler( let pool = state.db_pool(); let user_id = session.user_id; - account_creation_request(pool, user_id, request).await + account_creation_request(pool, user_id, request) + .await + .map(|(status_code, response)| { + (status_code, ApiResponse::new(response).into_json_response()).into_response() + }) } async fn account_creation_request( pool: &DbPool, user_id: i32, request: AccountCreationRequest, -) -> Result { +) -> Result<(StatusCode, AccountCreationResponse), AppError> { let AccountCreationRequest { r#type: account_type, name, @@ -48,5 +52,5 @@ async fn account_creation_request( .await .map(|response| AccountCreationResponse::from(response))?; - Ok((StatusCode::CREATED, Json(response)).into_response()) + Ok((StatusCode::CREATED, response)) } diff --git a/api/src/requests/account/read/handler.rs b/api/src/requests/account/read/handler.rs index dc47957..ef804fb 100644 --- a/api/src/requests/account/read/handler.rs +++ b/api/src/requests/account/read/handler.rs @@ -23,23 +23,19 @@ pub async fn account_read_all_get_request( account_read_all_request(pool, user_id) .await - .map(|(status_code, response)| { - (status_code, Json(ApiResponse::new(response))).into_response() - }) + .map(|response| (StatusCode::OK, Json(ApiResponse::new(response))).into_response()) } async fn account_read_all_request( pool: &DbPool, user_id: i32, -) -> Result<(StatusCode, Vec), AppError> { - let accounts = get_all_accounts_for_user(pool, user_id) +) -> Result, AppError> { + get_all_accounts_for_user(pool, user_id) .await .map(|accounts| { accounts .into_iter() .map(AccountsReadAllResponse::from) .collect::>() - })?; - - Ok((StatusCode::OK, accounts)) + }) } diff --git a/api/src/requests/account/read/models/response.rs b/api/src/requests/account/read/models/response.rs index 03b9f2e..5c1cea3 100644 --- a/api/src/requests/account/read/models/response.rs +++ b/api/src/requests/account/read/models/response.rs @@ -4,6 +4,7 @@ use uuid::Uuid; use crate::db::{AccountType, AccountWithPermissionValueEntity}; #[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct AccountsReadAllResponse { pub id: Uuid, pub account_type: AccountType, diff --git a/api/src/requests/budget/create/handler.rs b/api/src/requests/budget/create/handler.rs index 66390f7..677cfdb 100644 --- a/api/src/requests/budget/create/handler.rs +++ b/api/src/requests/budget/create/handler.rs @@ -7,7 +7,7 @@ use http::StatusCode; use crate::{ db::{DbPool, NewBudgetEntity, insert_new_budget_for_user}, - models::{AppError, Session}, + models::{ApiResponse, AppError, Session}, requests::AppState, }; @@ -24,7 +24,9 @@ pub async fn budget_create_post_request( budget_creation_request(pool, user_id, request) .await - .map(|(status_code, response)| (status_code, Json(response)).into_response()) + .map(|(status_code, response)| { + (status_code, ApiResponse::new(response).into_json_response()).into_response() + }) } async fn budget_creation_request( diff --git a/api/src/requests/budget/mod.rs b/api/src/requests/budget/mod.rs index ee6bc49..9161221 100644 --- a/api/src/requests/budget/mod.rs +++ b/api/src/requests/budget/mod.rs @@ -1,15 +1,21 @@ -use axum::{Router, routing::post}; +mod create; +mod read; + +use axum::{ + Router, + routing::{get, post}, +}; use create::budget_create_post_request; +use read::budget_read_all_get_request; use super::AppState; -mod create; - pub fn requests(state: AppState) -> Router { Router::new().nest( "/budget", Router::new() .route("/", post(budget_create_post_request)) + .route("/", get(budget_read_all_get_request)) .with_state(state), ) } diff --git a/api/src/requests/budget/read/handler.rs b/api/src/requests/budget/read/handler.rs new file mode 100644 index 0000000..b2dc907 --- /dev/null +++ b/api/src/requests/budget/read/handler.rs @@ -0,0 +1,47 @@ +use axum::{ + debug_handler, + extract::State, + response::{IntoResponse, Response}, +}; +use http::StatusCode; + +use crate::{ + db::{DbPool, get_all_budgets_for_user}, + models::{ApiResponse, AppError, Session}, + requests::AppState, +}; + +use super::models::BudgetReadAllResponse; + +#[debug_handler] +pub async fn budget_read_all_get_request( + State(state): State, + session: Session, +) -> Result { + let pool = state.db_pool(); + let user_id = session.user_id; + + budget_read_all_request(pool, user_id) + .await + .map(|response| { + ( + StatusCode::OK, + ApiResponse::new(response).into_json_response(), + ) + .into_response() + }) +} + +async fn budget_read_all_request( + pool: &DbPool, + user_id: i32, +) -> Result, AppError> { + get_all_budgets_for_user(pool, user_id) + .await + .map(|budgets| { + budgets + .into_iter() + .map(BudgetReadAllResponse::from) + .collect::>() + }) +} diff --git a/api/src/requests/budget/read/mod.rs b/api/src/requests/budget/read/mod.rs new file mode 100644 index 0000000..18b8ea1 --- /dev/null +++ b/api/src/requests/budget/read/mod.rs @@ -0,0 +1,4 @@ +mod handler; +pub mod models; + +pub use handler::*; diff --git a/api/src/requests/budget/read/models/mod.rs b/api/src/requests/budget/read/models/mod.rs new file mode 100644 index 0000000..1d28ef9 --- /dev/null +++ b/api/src/requests/budget/read/models/mod.rs @@ -0,0 +1,3 @@ +mod response; + +pub use response::*; diff --git a/api/src/requests/budget/read/models/response.rs b/api/src/requests/budget/read/models/response.rs new file mode 100644 index 0000000..ebfa65a --- /dev/null +++ b/api/src/requests/budget/read/models/response.rs @@ -0,0 +1,36 @@ +use serde::Serialize; +use uuid::Uuid; + +use crate::db::BudgetWithPermissionValueEntity; + +#[derive(Debug, Serialize)] +pub struct BudgetReadAllResponse { + pub id: Uuid, + pub name: String, + pub description: Option, + pub icon: Option, + pub permission_name: String, + pub permission_value: i32, +} + +impl From for BudgetReadAllResponse { + fn from(other: BudgetWithPermissionValueEntity) -> Self { + let BudgetWithPermissionValueEntity { + id, + name, + description, + icon, + permission_name, + permission_value, + } = other; + + Self { + id, + name, + description, + icon, + permission_name, + permission_value, + } + } +}