2025-03-16 16:21:45 +01:00

117 lines
3.2 KiB
Rust

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<Arc<AppState>> {
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<UserTarget>,
) -> Result<Json<User>, 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<Json<UserBalance>, 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<Json<Pagination<User>>, 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<TransactionQuery>,
) -> Result<Json<Pagination<FullTransaction>>, 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<Json<UserAccounts>, 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""#);
}
}