Implement read all budgets request

This commit is contained in:
Z. Charles Dziura 2025-03-15 09:24:57 -04:00
parent cd013d0009
commit 5b3323f39c
11 changed files with 185 additions and 18 deletions

View file

@ -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<String>,
pub icon: Option<String>,
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<Vec<BudgetWithPermissionValueEntity>, 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::<Vec<_>>()
.join("|"),
permission_value: acc.permission_value + entity.permission_value,
},
)
})
.collect::<Vec<_>>()
})
}

View file

@ -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::<Vec<_>>()
.join(",");

View file

@ -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<Response, AppError> {
) -> 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))
}

View file

@ -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<AccountsReadAllResponse>), AppError> {
let accounts = get_all_accounts_for_user(pool, user_id)
) -> Result<Vec<AccountsReadAllResponse>, AppError> {
get_all_accounts_for_user(pool, user_id)
.await
.map(|accounts| {
accounts
.into_iter()
.map(AccountsReadAllResponse::from)
.collect::<Vec<_>>()
})?;
Ok((StatusCode::OK, accounts))
})
}

View file

@ -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,

View file

@ -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(

View file

@ -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),
)
}

View file

@ -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<AppState>,
session: Session,
) -> Result<Response, AppError> {
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<Vec<BudgetReadAllResponse>, AppError> {
get_all_budgets_for_user(pool, user_id)
.await
.map(|budgets| {
budgets
.into_iter()
.map(BudgetReadAllResponse::from)
.collect::<Vec<_>>()
})
}

View file

@ -0,0 +1,4 @@
mod handler;
pub mod models;
pub use handler::*;

View file

@ -0,0 +1,3 @@
mod response;
pub use response::*;

View file

@ -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<String>,
pub icon: Option<String>,
pub permission_name: String,
pub permission_value: i32,
}
impl From<BudgetWithPermissionValueEntity> 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,
}
}
}