feat: current bal field on users and corresponding API routes

This commit is contained in:
Ninjdai 2025-08-09 02:11:07 +02:00
parent 5d4ece3c34
commit 9bbdb9decb
5 changed files with 78 additions and 3 deletions

View file

@ -15,6 +15,12 @@ pub struct Model {
pub enum Relation { pub enum Relation {
#[sea_orm(has_many = "super::book_instance::Entity")] #[sea_orm(has_many = "super::book_instance::Entity")]
BookInstance, BookInstance,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::Id",
to = "super::user::Column::CurrentBalId"
)]
User,
} }
impl Related<super::book_instance::Entity> for Entity { impl Related<super::book_instance::Entity> for Entity {

View file

@ -10,6 +10,7 @@ pub struct Model {
#[sea_orm(unique)] #[sea_orm(unique)]
pub username: String, pub username: String,
pub hashed_password: String, pub hashed_password: String,
pub current_bal_id: Option<u32>,
} }
impl Model { impl Model {

View file

@ -195,6 +195,8 @@ async fn run_server(db: Arc<DatabaseConnection>, port: u16) {
.routes(routes!(routes::bal::create_bal)) .routes(routes!(routes::bal::create_bal))
.routes(routes!(routes::bal::update_bal)) .routes(routes!(routes::bal::update_bal))
.routes(routes!(routes::bal::get_bals)) .routes(routes!(routes::bal::get_bals))
.routes(routes!(routes::bal::get_current_bal))
.routes(routes!(routes::bal::set_current_bal))
// Authentication // Authentication
.route_layer(middleware::from_fn_with_state(shared_state.clone(), routes::auth::auth_middleware)) .route_layer(middleware::from_fn_with_state(shared_state.clone(), routes::auth::auth_middleware))
.routes(routes!(routes::auth::auth)) .routes(routes!(routes::auth::auth))

View file

@ -2,11 +2,11 @@ use std::sync::Arc;
use axum::{extract::{Path, State}, Json}; use axum::{extract::{Path, State}, Json};
use reqwest::{StatusCode}; use reqwest::{StatusCode};
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait, QueryFilter, TryIntoModel}; use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, TryIntoModel};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use utoipa::IntoParams; use utoipa::IntoParams;
use crate::{entities::{bal, prelude::*}, routes::auth::Claims, AppState}; use crate::{entities::{bal, prelude::*, user}, routes::auth::Claims, utils::auth::user_is_bal_owner, AppState};
#[derive(IntoParams)] #[derive(IntoParams)]
@ -159,3 +159,68 @@ pub async fn get_bals(
(StatusCode::NOT_FOUND, Json(vec![])) (StatusCode::NOT_FOUND, Json(vec![]))
} }
} }
#[axum::debug_handler]
#[utoipa::path(
get,
path = "/bal/current",
security(("jwt" = [])),
responses(
(status = OK, body = bal::Model, description = "Your current active BAL"),
(status = NOT_FOUND, description = "You don't have a currently active BAL"),
),
summary = "Get your current active BAL",
description = "Get your current active BAL",
tag = "bal-api",
)]
pub async fn get_current_bal(
State(state): State<Arc<AppState>>,
claims: Claims,
) -> (StatusCode, Json<Option<bal::Model>>) {
if let Ok(Some(user)) = User::find_by_id(claims.user_id).one(state.db_conn.as_ref()).await {
if let Some(bal_id) = user.current_bal_id {
(StatusCode::OK, Json(Some(Bal::find_by_id(bal_id).one(state.db_conn.as_ref()).await.unwrap().unwrap())))
} else {
(StatusCode::NOT_FOUND, Json(None))
}
} else {
(StatusCode::INTERNAL_SERVER_ERROR, Json(None))
}
}
#[derive(Deserialize, utoipa::ToSchema)]
pub struct BalIdParams{
id: u32,
}
#[axum::debug_handler]
#[utoipa::path(
post,
path = "/bal/current",
request_body = BalIdParams,
security(("jwt" = [])),
responses(
(status = OK, description = "Successfully set current active BAL"),
(status = UNAUTHORIZED, description = "Tried to set a BAL you don't own as your active BAL"),
),
summary = "Set your current active BAL",
description = "Set your current active BAL",
tag = "bal-api",
)]
pub async fn set_current_bal(
State(state): State<Arc<AppState>>,
claims: Claims,
Json(payload): Json<BalIdParams>,
) -> StatusCode {
if !user_is_bal_owner(claims.user_id, payload.id, state.db_conn.as_ref()).await {
return StatusCode::UNAUTHORIZED;
}
if let Ok(Some(user)) = User::find_by_id(claims.user_id).one(state.db_conn.as_ref()).await {
let mut user_active_model: user::ActiveModel = user.into_active_model();
user_active_model.current_bal_id = Set(Some(payload.id));
let _ = User::update(user_active_model).exec(state.db_conn.as_ref()).await;
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
}

View file

@ -64,7 +64,8 @@ pub async fn manage_users(db: Arc<DatabaseConnection>) {
let new_user = user::ActiveModel { let new_user = user::ActiveModel {
id: NotSet, id: NotSet,
username: Set(username), username: Set(username),
hashed_password: Set(hash_password(password)) hashed_password: Set(hash_password(password)),
current_bal_id: Set(None)
}; };
let res = new_user.insert(db.as_ref()).await.unwrap(); let res = new_user.insert(db.as_ref()).await.unwrap();