Create endpoint to fetch all accounts for a user

This commit is contained in:
Z. Charles Dziura 2025-03-06 16:05:12 -05:00
parent 8005bb6800
commit 2bdb3ff67a
8 changed files with 177 additions and 9 deletions

View file

@ -4,7 +4,7 @@ use tracing::error;
use crate::models::AppError;
use super::{
associate_account_with_user_as_owner, AccountType, DbPool, PermissionEntity, StatusType,
AccountType, DbPool, PermissionEntity, StatusType, associate_account_with_user_as_owner,
};
#[allow(dead_code)]
@ -27,6 +27,18 @@ pub struct AccountEntity {
pub status: StatusType,
}
#[allow(dead_code)]
#[derive(Debug, Default, FromRow)]
pub struct AccountWithPermissionValueEntity {
pub id: i32,
pub account_type: AccountType,
pub name: String,
pub description: Option<String>,
pub currency_code: String,
pub permission_name: String,
pub permission_value: i32,
}
pub async fn insert_new_account_for_user(
pool: &DbPool,
new_account: NewAccountEntity,
@ -53,3 +65,63 @@ pub async fn insert_new_account_for_user(
Ok((new_account, account_permissions))
}
pub async fn get_all_accounts_for_user(
pool: &DbPool,
user_id: i32,
) -> Result<Vec<AccountWithPermissionValueEntity>, AppError> {
sqlx::query_as::<_, AccountWithPermissionValueEntity>(
"SELECT
a.id AS id,
a.account_type AS account_type,
a.name AS name,
a.description AS description,
a.currency_code AS currency_code,
p.name AS permission_name,
p.value AS permission_value
FROM
public.account a
INNER JOIN public.user_account_permission uap ON a.id = uap.account_id
INNER JOIN public.permission p ON uap.permission_id = p.id
WHERE
uap.user_id = $1
ORDER BY
a.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 accounts"
)
})
.map_err(AppError::from)
.map(|all_accounts| {
all_accounts
.chunk_by(|a, b| a.id == b.id)
.map(|accounts| {
accounts.into_iter().fold(
AccountWithPermissionValueEntity::default(),
|acc, entity| AccountWithPermissionValueEntity {
id: entity.id,
account_type: entity.account_type,
name: entity.name.clone(),
description: entity.description.clone(),
currency_code: entity.currency_code.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

@ -1,11 +1,12 @@
use serde::{Deserialize, Serialize};
#[derive(
Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, sqlx::Type,
Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, sqlx::Type,
)]
#[sqlx(type_name = "account_type", rename_all = "PascalCase")]
#[serde(rename_all = "lowercase")]
pub enum AccountType {
#[default]
Asset,
Equity,
Expense,

View file

@ -1,13 +1,12 @@
use axum::{
debug_handler,
Json, debug_handler,
extract::State,
response::{IntoResponse, Response},
Json,
};
use http::StatusCode;
use crate::{
db::{insert_new_account_for_user, DbPool, NewAccountEntity},
db::{DbPool, NewAccountEntity, insert_new_account_for_user},
models::{AppError, Session},
requests::AppState,
};
@ -23,13 +22,13 @@ pub async fn account_creation_post_handler(
let pool = state.db_pool();
let user_id = session.user_id;
account_creation_request(request, user_id, pool).await
account_creation_request(pool, user_id, request).await
}
async fn account_creation_request(
request: AccountCreationRequest,
user_id: i32,
pool: &DbPool,
user_id: i32,
request: AccountCreationRequest,
) -> Result<Response, AppError> {
let AccountCreationRequest {
r#type: account_type,

View file

@ -1,7 +1,12 @@
mod create;
mod read;
use axum::{routing::post, Router};
use axum::{
Router,
routing::{get, post},
};
use create::account_creation_post_handler;
use read::account_read_all_get_request;
use super::AppState;
@ -10,6 +15,7 @@ pub fn requests(state: AppState) -> Router {
"/account",
Router::new()
.route("/", post(account_creation_post_handler))
.route("/", get(account_read_all_get_request))
.with_state(state),
)
}

View file

@ -0,0 +1,45 @@
use axum::{
Json, debug_handler,
extract::State,
response::{IntoResponse, Response},
};
use http::StatusCode;
use crate::{
db::{DbPool, get_all_accounts_for_user},
models::{ApiResponse, AppError, Session},
requests::AppState,
};
use super::models::AccountsReadAllResponse;
#[debug_handler]
pub async fn account_read_all_get_request(
State(state): State<AppState>,
session: Session,
) -> Result<Response, AppError> {
let pool = state.db_pool();
let user_id = session.user_id;
account_read_all_request(pool, user_id)
.await
.map(|(status_code, response)| {
(status_code, 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)
.await
.map(|accounts| {
accounts
.into_iter()
.map(AccountsReadAllResponse::from)
.collect::<Vec<_>>()
})?;
Ok((StatusCode::OK, accounts))
}

View file

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

View file

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

View file

@ -0,0 +1,38 @@
use serde::Serialize;
use crate::db::{AccountType, AccountWithPermissionValueEntity};
#[derive(Debug, Serialize)]
pub struct AccountsReadAllResponse {
pub id: i32,
pub account_type: AccountType,
pub name: String,
pub description: Option<String>,
pub currency_code: String,
pub permission_name: String,
pub permission_value: i32,
}
impl From<AccountWithPermissionValueEntity> for AccountsReadAllResponse {
fn from(other: AccountWithPermissionValueEntity) -> Self {
let AccountWithPermissionValueEntity {
id,
account_type,
name,
description,
currency_code,
permission_name,
permission_value,
} = other;
Self {
id,
account_type,
name,
description,
currency_code,
permission_name,
permission_value,
}
}
}