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::{book, prelude::{Book}}, utils::open_library}; use crate::AppState; #[derive(IntoParams)] #[into_params(names("id"), parameter_in = Path)] #[allow(dead_code)] struct BookByIdParams(u32); #[axum::debug_handler] #[utoipa::path( get, path = "/book/id/{id}", params(BookByIdParams), security(("jwt" = [])), responses( (status = OK, body = book::Model, description = "Found book with corresponding ID in the database", examples( ("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"}))), ("Book doesn't have an EAN" = (value = json!({"author": "Author B. Ook", "ean": "", "id": 1465312, "title": "Itsabook"}))) )), (status = NOT_FOUND, description = "No book with this id exists in the database") ), summary = "Get a book by its internal ID", description = "Get a book from its ID in Alexandria's internal database", tag = "book-api", )] pub async fn get_book_by_id( State(state): State>, Path(id): Path, ) -> (StatusCode, Json>) { if let Ok(Some(res)) = Book::find_by_id(id).one(state.db_conn.as_ref()).await { (StatusCode::OK, Json(Some(res))) } else { (StatusCode::NOT_FOUND, Json(None)) } } #[derive(IntoParams)] #[into_params(names("ean"), parameter_in = Path)] #[allow(dead_code)] struct BookByEanParams(String); #[axum::debug_handler] #[utoipa::path( get, path = "/book/ean/{ean}", params(BookByEanParams), security(("jwt" = [])), responses( (status = OK, body = book::Model, description = "Found book with corresponding EAN", examples( ("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"}))) )), (status = NOT_FOUND, description = "No book with this EAN found in the database or on openlibrary") ), summary = "Get a book by its EAN", description = "Get a book from its EAN. If it doesn't exist in its database, Alexandria will try to find it using openlibrary.org's API", tag = "book-api" )] pub async fn get_book_by_ean( State(state): State>, Path(ean): Path, ) -> (StatusCode, Json>) { if let Ok(Some(res)) = Book::find().filter(book::Column::Ean.eq(&ean)).one(state.db_conn.as_ref()).await { (StatusCode::OK, Json(Some(res))) } else { let fetched_book = open_library::fetch_book_by_ean(&state.web_client, &ean).await; if let Some(book) = fetched_book { let res = Book::insert(book.to_active_model()).exec(state.db_conn.as_ref()).await.unwrap(); (StatusCode::OK, Json(Some(book::Model { id: res.last_insert_id, ean: ean, title: book.title, author: book.author }))) } else { (StatusCode::NOT_FOUND, Json(None)) } } } #[derive(Deserialize, Serialize, utoipa::ToSchema)] pub struct BookCreateParams { ean: String, title: String, author: String } #[axum::debug_handler] #[utoipa::path( post, path = "/book", request_body = BookCreateParams, security(("jwt" = [])), responses( (status = OK, body = book::Model, description = "Successfully saved book data"), (status = CONFLICT, body = book::Model, description = "A book with the same EAN already exists. Replies with the data of the already saved book."), ), summary = "Manually add book data to the database", description = "Should be used when needing to insert data for an EAN that doesn't already exist in the database and couldn't be fetched automatically, ie the /book/ean/{ean} endpoint returned a 404 NOT FOUND error", tag = "book-api", )] pub async fn create_book( State(state): State>, Json(instance_payload): Json, ) -> (StatusCode, Json>) { if let Some(book) = Book::find().filter(book::Column::Ean.eq(&instance_payload.ean)).one(state.db_conn.as_ref()).await.unwrap() { return (StatusCode::CONFLICT, Json(Some(book))); } let book = book::ActiveModel { ean: Set(instance_payload.ean), title: Set(instance_payload.title), author: Set(instance_payload.author), id: NotSet }; let b = book.save(state.db_conn.as_ref()).await; match b { Err(e) => { log::error!(target: "api", "Error while inserting new book: {:#?}", 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 is saved")))) } }