use std::sync::Arc; use axum::{Router, extract::Path, routing::get}; use bank_core::{ pagination::Pagination, transaction::{FullTransaction, TransactionQuery}, user::{User, UserAccounts, UserBalance}, }; use serde::{Deserialize, Serialize}; use tracing::instrument; use uuid::Uuid; use crate::{ api::ApiError, model::{Accounts, Transactions, Users}, }; use super::{AppState, EState, Error, Json, PaginationQuery, Query, State, auth::Auth}; pub(super) fn router() -> Router> { Router::new() .route("/{target}", get(user_info)) .route("/@me/balance", get(user_balance)) .route("/@me/accounts", get(user_accounts)) .route("/@me/transactions", get(me_transaction_history)) .route("/", get(list_users)) } #[derive(Deserialize, Serialize)] pub enum UserTarget { #[serde(rename = "@me")] Me, Id(Uuid), } impl UserTarget { pub fn user_id(&self, auth: &Auth) -> Uuid { match self { UserTarget::Me => auth.user_id(), UserTarget::Id(uuid) => *uuid, } } } pub async fn user_info( EState(state): State, auth: Auth, Path(target): Path, ) -> Result, Error> { let user = target.user_id(&auth); let conn = state.conn().await?; let info = Users::info(&conn, user).await?; if let Some(info) = info { return Ok(Json(info)); } if matches!(target, UserTarget::Me) { return Err(ApiError::INTERNAL_SERVER_ERROR.into()); } Err(ApiError::NOT_FOUND.into()) } #[instrument(skip(state))] pub async fn user_balance(EState(state): State, auth: Auth) -> Result, Error> { let conn = state.conn().await?; let info = Accounts::list_for_user(&conn, auth.user_id()).await?; let balance = info.iter().map(|info| info.balance).sum(); Ok(Json(UserBalance { balance })) } #[instrument(skip(state))] pub async fn list_users( EState(state): State, _: Auth, PaginationQuery(pagination): PaginationQuery, ) -> Result>, Error> { let conn = state.conn().await?; let users = Users::list(&conn, pagination).await?; Ok(Json(users)) } #[instrument(skip(state))] pub async fn me_transaction_history( EState(state): State, auth: Auth, Query(TransactionQuery { direction, pagination, }): Query, ) -> Result>, Error> { let conn = state.conn().await?; let result = Transactions::user_history(&conn, auth.user_id(), direction, pagination).await?; Ok(Json(result)) } #[instrument(skip(state))] pub async fn user_accounts(EState(state): State, auth: Auth) -> Result, Error> { let user = auth.user_id(); let conn = state.conn().await?; let result = Accounts::list_for_user(&conn, user).await?; Ok(Json(UserAccounts { result })) } #[cfg(test)] mod tests { use uuid::Uuid; use super::UserTarget; #[test] fn user_target() { let json = serde_json::to_string(&UserTarget::Me).unwrap(); assert_eq!(json, r#""@me""#); let json = serde_json::to_string(&Uuid::nil()).unwrap(); assert_eq!(json, r#""00000000-0000-0000-0000-000000000000""#); } }