diff --git a/src/main.rs b/src/main.rs index 3aeb291..34ea4f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,6 +107,10 @@ async fn main() { log::error!(target: "database", "Error while creating user table: {err:?}"); return; } + if let Err(err) = db.execute(builder.build(schema.create_table_from_entity(crate::entities::prelude::Bal).if_not_exists())).await { + log::error!(target: "database", "Error while creating bal table: {err:?}"); + return; + } match cli.command { Commands::Run => run_server(db).await, @@ -171,6 +175,11 @@ async fn run_server(db: Arc) { .routes(routes!(routes::owner::create_owner)) .routes(routes!(routes::owner::update_owner)) .routes(routes!(routes::owner::get_owners)) + // Bal API + .routes(routes!(routes::bal::get_bal_by_id)) + .routes(routes!(routes::bal::create_bal)) + .routes(routes!(routes::bal::update_bal)) + .routes(routes!(routes::bal::get_bals)) // Misc .routes(routes!(routes::websocket::ws_handler)) // Authentication diff --git a/src/routes/bal.rs b/src/routes/bal.rs new file mode 100644 index 0000000..ec3a43b --- /dev/null +++ b/src/routes/bal.rs @@ -0,0 +1,161 @@ +use std::sync::Arc; + +use axum::{extract::{Path, State}, Json}; +use reqwest::{StatusCode}; +use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait, QueryFilter, TryIntoModel}; +use serde::{Deserialize, Serialize}; +use utoipa::IntoParams; + +use crate::{entities::{bal, prelude::*}, routes::auth::Claims, AppState}; + + +#[derive(IntoParams)] +#[into_params(names("id"), parameter_in = Path)] +#[allow(dead_code)] +struct BalByIdParams(u32); + +#[axum::debug_handler] +#[utoipa::path( + get, + path = "/bal/{id}", + params(BalByIdParams), + security(("jwt" = [])), + responses( + (status = OK, body = bal::Model, description = "Found bal with corresponding ID in the database"), + (status = NOT_FOUND, description = "No bal with this id exists in the database"), + (status = FORBIDDEN, description = "You don't own the specified bal"), + ), + summary = "Get a bal by its ID", + description = "Get a bal from its ID", + tag = "bal-api", +)] +pub async fn get_bal_by_id( + State(state): State>, + claims: Claims, + Path(id): Path, +) -> (StatusCode, Json>) { + if let Ok(Some(res)) = Bal::find_by_id(id).one(state.db_conn.as_ref()).await { + if !res.user_id == claims.user_id { + (StatusCode::FORBIDDEN, Json(None)) + } else { + (StatusCode::OK, Json(Some(res))) + } + } else { + (StatusCode::NOT_FOUND, Json(None)) + } +} + +#[derive(Deserialize, Serialize, utoipa::ToSchema)] +pub struct BalCreateParams { + name: String, +} + +#[axum::debug_handler] +#[utoipa::path( + post, + path = "/bal", + request_body = BalCreateParams, + security(("jwt" = [])), + responses( + (status = OK, body = bal::Model, description = "Successfully created BAL"), + ), + summary = "Create a new bal", + description = "Create a new bal", + tag = "bal-api", +)] +pub async fn create_bal( + State(state): State>, + claims: Claims, + Json(instance_payload): Json, +) -> (StatusCode, Json>) { + let bal = bal::ActiveModel { + id: NotSet, + user_id: Set(claims.user_id), + name: Set(instance_payload.name), + }; + + let b = bal.save(state.db_conn.as_ref()).await; + match b { + Err(e) => { + log::error!(target: "api", "Error while inserting new bal: {:#?}", e); + (StatusCode::BAD_REQUEST, Json(None)) + }, + Ok(res) => (StatusCode::OK, Json(Some(res.try_into_model().expect("All fields should be set once the bal is saved")))) + } +} + +#[derive(Deserialize, Serialize, utoipa::ToSchema)] +pub struct BalUpdateParams { + name: Option, +} + +#[axum::debug_handler] +#[utoipa::path( + patch, + path = "/bal/{id}", + params(BalByIdParams), + request_body = BalUpdateParams, + security(("jwt" = [])), + responses( + (status = OK, body = bal::Model, description = "Successfully updated bal"), + (status = NOT_FOUND, description = "No bal with specified id was found"), + (status = FORBIDDEN, description = "You don't own the specified bal"), + ), + summary = "Update a bal", + description = "Update a bal", + tag = "bal-api", +)] +pub async fn update_bal( + State(state): State>, + claims: Claims, + Path(id): Path, + Json(instance_payload): Json, +) -> (StatusCode, Json>) { + if let Ok(Some(bal)) = Bal::find_by_id(id).one(state.db_conn.as_ref()).await { + if bal.user_id != claims.user_id { + return (StatusCode::FORBIDDEN, Json(None)); + } + let mut bal: bal::ActiveModel = bal.into(); + bal.name = match instance_payload.name { + None => bal.name, + Some(v) => Set(v) + }; + + match bal.update(state.db_conn.as_ref()).await { + Err(e) => { + log::error!(target: "api", "Error while updating bal from api: {:#?}", e); + (StatusCode::INTERNAL_SERVER_ERROR, Json(None)) + }, + Ok(res) => { + let model = res.try_into_model().expect("All fields should be set once the bal is saved"); + (StatusCode::OK, Json(Some(model))) + } + } + } else { + (StatusCode::NOT_FOUND, Json(None)) + } +} + +#[axum::debug_handler] +#[utoipa::path( + get, + path = "/bals", + params(BalByIdParams), + security(("jwt" = [])), + responses( + (status = OK, body = Vec, description = "All Bals you own"), + ), + summary = "Get all Bals you own", + description = "Get all Bals you own", + tag = "bal-api", +)] +pub async fn get_bals( + State(state): State>, + claims: Claims, +) -> (StatusCode, Json>) { + if let Ok(res) = Bal::find().filter(bal::Column::UserId.eq(claims.user_id)).all(state.db_conn.as_ref()).await { + (StatusCode::OK, Json(res)) + } else { + (StatusCode::NOT_FOUND, Json(vec![])) + } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 34b9290..5d4b63e 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod bal; pub mod book; pub mod book_instance; pub mod owner;