From 5d4ece3c345cfa2c28ce0955bdd2d363e15ff8c1 Mon Sep 17 00:00:00 2001 From: Ninjdai Date: Fri, 8 Aug 2025 18:58:04 +0200 Subject: [PATCH] feat: book instance search API endpoint --- src/main.rs | 2 ++ src/routes/bal.rs | 2 +- src/routes/book_instance.rs | 61 +++++++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8f26abf..dd7b8c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,6 +184,7 @@ async fn run_server(db: Arc, port: u16) { .routes(routes!(routes::book_instance::bulk_create_book_instance)) .routes(routes!(routes::book_instance::get_bal_owner_book_instances)) .routes(routes!(routes::book_instance::get_bal_book_instances_by_ean)) + .routes(routes!(routes::book_instance::search_bal_book_instances)) // Owner API .routes(routes!(routes::owner::get_owner_by_id)) .routes(routes!(routes::owner::create_owner)) @@ -230,6 +231,7 @@ async fn run_server(db: Arc, port: u16) { .try_it_out_enabled(true) .filter(true) .display_request_duration(true) + .persist_authorization(true) ); let router = router.merge(swagger); diff --git a/src/routes/bal.rs b/src/routes/bal.rs index f7691f7..ceb9b69 100644 --- a/src/routes/bal.rs +++ b/src/routes/bal.rs @@ -12,7 +12,7 @@ use crate::{entities::{bal, prelude::*}, routes::auth::Claims, AppState}; #[derive(IntoParams)] #[into_params(names("id"), parameter_in = Path)] #[allow(dead_code)] -struct BalByIdParams(u32); +pub struct BalByIdParams(u32); #[axum::debug_handler] #[utoipa::path( diff --git a/src/routes/book_instance.rs b/src/routes/book_instance.rs index 6d1aa46..f7cec31 100644 --- a/src/routes/book_instance.rs +++ b/src/routes/book_instance.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use axum::{extract::{Path, State}, Json}; use reqwest::{StatusCode}; @@ -6,7 +6,7 @@ use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, EntityT use serde::{Deserialize, Serialize}; use utoipa::IntoParams; -use crate::{entities::{book, book_instance, prelude::*}, routes::auth::Claims, utils::auth::{user_is_bal_owner, user_is_book_instance_owner, user_is_owner_owner}, AppState}; +use crate::{entities::{book, book_instance, prelude::*}, routes::{auth::Claims, bal}, utils::auth::{user_is_bal_owner, user_is_book_instance_owner, user_is_owner_owner}, AppState}; #[derive(IntoParams)] @@ -329,3 +329,60 @@ pub async fn get_bal_book_instances_by_ean( (StatusCode::INTERNAL_SERVER_ERROR, Json(vec![])) } } + +#[derive(Deserialize, Serialize, utoipa::ToSchema)] +pub struct BookInstanceSearchParams { + title: String, +} + +#[derive(Serialize, utoipa::ToSchema)] +pub struct BookInstanceSearchResults { + book_instances: Vec, + books: HashMap, +} + +#[axum::debug_handler] +#[utoipa::path( + post, + path = "/bal/{id}/search", + params(bal::BalByIdParams), + request_body = BookInstanceSearchParams, + security(("jwt" = [])), + responses( + (status = OK, body = BookInstanceSearchResults, description = "Found book instances in the database"), + (status = FORBIDDEN, description = "You do not own the specified bal"), + ), + summary = "Search a BAL for books instances", + description = "Lists all book instances that match the requested parameters in a bal", + tag = "book-instance-api", +)] +pub async fn search_bal_book_instances( + State(state): State>, + claims: Claims, + Path(bal_id): Path, + Json(instance_payload): Json, +) -> (StatusCode, Json>) { + if !user_is_bal_owner(claims.user_id, bal_id, state.db_conn.as_ref()).await { + return (StatusCode::FORBIDDEN, Json(None)); + } + if let Ok(res) = BookInstance::find() + .filter(book_instance::Column::BalId.eq(bal_id)) + .filter(book_instance::Column::Available.eq(true)) + .join(JoinType::InnerJoin, book_instance::Relation::Book.def()) + .filter(book::Column::Title.like(format!("%{}%", instance_payload.title))) + .all(state.db_conn.as_ref()).await + { + let mut book_id_map = HashMap::new(); + for instance in &res { + if book_id_map.get(&instance.book_id).is_none() { + book_id_map.insert(instance.book_id, Book::find_by_id(instance.book_id).one(state.db_conn.as_ref()).await.unwrap().unwrap()); + } + } + (StatusCode::OK, Json(Some(BookInstanceSearchResults { + book_instances: res, + books: book_id_map + }))) + } else { + (StatusCode::INTERNAL_SERVER_ERROR, Json(None)) + } +}