This repository has been archived on 2025-08-25. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
Alexandria/src/routes/book_instance.rs
2025-08-03 20:10:00 +02:00

251 lines
8.8 KiB
Rust

use std::sync::Arc;
use axum::{extract::{Path, State}, Json};
use reqwest::{StatusCode};
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, EntityTrait, TryIntoModel};
use serde::{Deserialize, Serialize};
use utoipa::IntoParams;
use crate::{entities::{book_instance, prelude::*}, routes::auth::Claims, utils::auth::{user_is_bal_owner, user_is_book_instance_owner}, AppState};
#[derive(IntoParams)]
#[into_params(names("id"), parameter_in = Path)]
#[allow(dead_code)]
struct BookInstanceByIdParams(u32);
#[axum::debug_handler]
#[utoipa::path(
get,
path = "/book_instance/{id}",
params(BookInstanceByIdParams),
security(("jwt" = [])),
responses(
(status = OK, body = book_instance::Model, description = "Found book instance with corresponding ID in the database"),
(status = NOT_FOUND, description = "No book instance with this id exists in the database")
),
summary = "Get a book instance by its ID",
description = "Get a book instance from its ID",
tag = "book-instance-api",
)]
pub async fn get_book_instance_by_id(
State(state): State<Arc<AppState>>,
Path(id): Path<u32>,
) -> (StatusCode, Json<Option<book_instance::Model>>) {
if let Ok(Some(res)) = BookInstance::find_by_id(id).one(state.db_conn.as_ref()).await {
(StatusCode::OK, Json(Some(res)))
} else {
(StatusCode::NOT_FOUND, Json(None))
}
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct BookInstanceCreateParams {
book_id: u32,
owner_id: u32,
bal_id: u32,
price: i32,
}
#[axum::debug_handler]
#[utoipa::path(
post,
path = "/book_instance",
request_body = BookInstanceCreateParams,
security(("jwt" = [])),
responses(
(status = OK, body = book_instance::Model, description = "Successfully created book instance"),
(status = FORBIDDEN, description = "You don't own the specified BAL"),
),
summary = "Create a new book instance",
description = "Create a new book instance",
tag = "book-instance-api",
)]
pub async fn create_book_instance(
State(state): State<Arc<AppState>>,
claims: Claims,
Json(instance_payload): Json<BookInstanceCreateParams>,
) -> (StatusCode, Json<Option<book_instance::Model>>) {
if !user_is_bal_owner(claims.user_id, instance_payload.bal_id, state.db_conn.as_ref()).await {
return (StatusCode::FORBIDDEN, Json(None));
}
let book_instance = book_instance::ActiveModel {
book_id: Set(instance_payload.book_id),
owner_id: Set(instance_payload.owner_id),
bal_id: Set(instance_payload.bal_id),
price: Set(instance_payload.price),
status: Set(book_instance::BookStatus::Available),
..Default::default()
};
let b = book_instance.save(state.db_conn.as_ref()).await;
match b {
Err(e) => {
log::error!(target: "api", "Error while inserting new book instance: {:#?}", e);
(StatusCode::BAD_REQUEST, Json(None))
},
Ok(res) => (StatusCode::OK, Json(Some(res.try_into_model().expect("All fields should be set once the book instance is saved"))))
}
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct BookInstanceUpdateParams {
status: Option<book_instance::BookStatus>,
owner_id: Option<u32>,
price: Option<i32>,
}
#[axum::debug_handler]
#[utoipa::path(
patch,
path = "/book_instance/{id}",
params(BookInstanceByIdParams),
request_body = BookInstanceUpdateParams,
security(("jwt" = [])),
responses(
(status = OK, body = book_instance::Model, description = "Successfully updated book instance"),
(status = NOT_FOUND, description = "No book instance with specified id was found"),
(status = FORBIDDEN, description = "You don't own the specified book instance"),
),
summary = "Update a book instance",
description = "Update a book instance",
tag = "book-instance-api",
)]
pub async fn update_book_instance(
State(state): State<Arc<AppState>>,
claims: Claims,
Path(id): Path<u32>,
Json(instance_payload): Json<BookInstanceUpdateParams>,
) -> (StatusCode, Json<Option<book_instance::Model>>) {
if !user_is_book_instance_owner(claims.user_id, id, state.db_conn.as_ref()).await {
return (StatusCode::FORBIDDEN, Json(None));
}
if let Ok(Some(book_instance)) = BookInstance::find_by_id(id).one(state.db_conn.as_ref()).await {
let mut book_instance: book_instance::ActiveModel = book_instance.into();
book_instance.price = match instance_payload.price {
None => book_instance.price,
Some(v) => Set(v)
};
book_instance.owner_id = match instance_payload.owner_id {
None => book_instance.owner_id,
Some(v) => Set(v)
};
book_instance.status = match instance_payload.status {
None => book_instance.status,
Some(v) => Set(v)
};
match book_instance.update(state.db_conn.as_ref()).await {
Err(e) => {
log::error!(target: "api", "Error while updating book instance 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 book instance is saved");
(StatusCode::OK, Json(Some(model)))
}
}
} else {
(StatusCode::NOT_FOUND, Json(None))
}
}
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct BookInstanceSaleParams {
price: i32,
}
#[axum::debug_handler]
#[utoipa::path(
post,
path = "/book_instance/{id}/sell",
params(BookInstanceByIdParams),
request_body = BookInstanceSaleParams,
security(("jwt" = [])),
responses(
(status = OK, body = book_instance::Model, description = "Successfully sold book instance"),
(status = NOT_FOUND, description = "No book instance with specified id was found"),
),
summary = "Sell a book instance",
description = "Sell a book instance",
tag = "book-instance-api",
)]
pub async fn sell_book_instance(
State(state): State<Arc<AppState>>,
claims: Claims,
Path(id): Path<u32>,
Json(instance_payload): Json<BookInstanceSaleParams>,
) -> (StatusCode, Json<Option<book_instance::Model>>) {
if !user_is_book_instance_owner(claims.user_id, id, state.db_conn.as_ref()).await {
return (StatusCode::FORBIDDEN, Json(None));
}
if let Ok(Some(book_instance)) = BookInstance::find_by_id(id).one(state.db_conn.as_ref()).await {
let mut book_instance: book_instance::ActiveModel = book_instance.into();
book_instance.sold_price = Set(Some(instance_payload.price));
book_instance.status = Set(book_instance::BookStatus::Sold);
match book_instance.update(state.db_conn.as_ref()).await {
Err(e) => {
log::error!(target: "api", "Error while selling book instance 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 book instance is saved");
(StatusCode::OK, Json(Some(model)))
}
}
} else {
(StatusCode::NOT_FOUND, Json(None))
}
}
#[axum::debug_handler]
#[utoipa::path(
post,
path = "/book_instance/bulk",
request_body = Vec<BookInstanceCreateParams>,
security(("jwt" = [])),
responses(
(status = OK, description = "Successfully created book instances"),
(status = FORBIDDEN, description = "You don't own at least one specified bal of the book instances sent"),
),
summary = "Create new book instances in bulk",
description = "Create new book instances in bulk",
tag = "book-instance-api",
)]
pub async fn bulk_create_book_instance(
State(state): State<Arc<AppState>>,
claims: Claims,
Json(instance_payload): Json<Vec<BookInstanceCreateParams>>,
) -> StatusCode {
for i in &instance_payload {
if !user_is_bal_owner(claims.user_id, i.bal_id, state.db_conn.as_ref()).await {
return StatusCode::FORBIDDEN;
}
}
let instances = instance_payload
.into_iter()
.map(|p| {
book_instance::ActiveModel {
book_id: Set(p.book_id),
owner_id: Set(p.owner_id),
bal_id: Set(p.bal_id),
price: Set(p.price),
status: Set(book_instance::BookStatus::Available),
id: NotSet,
sold_price: NotSet
}
});
match BookInstance::insert_many(instances).exec(state.db_conn.as_ref()).await {
Err(e) => {
log::error!(target: "api", "Error while bulk inserting new book instances: {:#?}", e);
StatusCode::INTERNAL_SERVER_ERROR
},
Ok(_) => StatusCode::OK
}
}