feat: initial API and docs
This commit is contained in:
parent
79be4eb543
commit
5d709d658b
16 changed files with 3169 additions and 342 deletions
83
src/routes/book.rs
Normal file
83
src/routes/book.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::{Path, State}, Json};
|
||||
use reqwest::{StatusCode};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
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),
|
||||
responses(
|
||||
(status = OK, body = book::Model, description = "Found book with corresponding ID in the database"),
|
||||
(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<Arc<AppState>>,
|
||||
Path(id): Path<u32>,
|
||||
) -> (StatusCode, Json<Option<book::Model>>) {
|
||||
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),
|
||||
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"}))),
|
||||
("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 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<Arc<AppState>>,
|
||||
Path(ean): Path<String>,
|
||||
) -> (StatusCode, Json<Option<book::Model>>) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/routes/book_instance.rs
Normal file
81
src/routes/book_instance.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{extract::{Path, State}, Json};
|
||||
use reqwest::{StatusCode};
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait, TryIntoModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use crate::{entities::{book_instance, prelude::BookInstance}, 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/{id}",
|
||||
params(BookInstanceByIdParams),
|
||||
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,
|
||||
price: i32,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/book_instance",
|
||||
request_body = BookInstanceCreateParams,
|
||||
responses(
|
||||
(status = OK, body = book_instance::Model, description = "Successfully created book instance"),
|
||||
),
|
||||
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>>,
|
||||
Json(instance_payload): Json<BookInstanceCreateParams>,
|
||||
) -> (StatusCode, Json<Option<book_instance::Model>>) {
|
||||
|
||||
let book_instance = book_instance::ActiveModel {
|
||||
book_id: Set(instance_payload.book_id),
|
||||
owner_id: Set(instance_payload.owner_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) => {
|
||||
println!("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"))))
|
||||
}
|
||||
}
|
||||
4
src/routes/mod.rs
Normal file
4
src/routes/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod book;
|
||||
pub mod book_instance;
|
||||
pub mod owner;
|
||||
|
||||
80
src/routes/owner.rs
Normal file
80
src/routes/owner.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
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::{owner, prelude::Owner}, AppState};
|
||||
|
||||
|
||||
#[derive(IntoParams)]
|
||||
#[into_params(names("id"), parameter_in = Path)]
|
||||
#[allow(dead_code)]
|
||||
struct OwnerByIdParams(u32);
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/owner/{id}",
|
||||
params(OwnerByIdParams),
|
||||
responses(
|
||||
(status = OK, body = owner::Model, description = "Found owner with corresponding ID in the database"),
|
||||
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
||||
),
|
||||
summary = "Get an owner by its ID",
|
||||
description = "Get an owner from its ID",
|
||||
tag = "owner-api",
|
||||
)]
|
||||
pub async fn get_owner_by_id(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<u32>,
|
||||
) -> (StatusCode, Json<Option<owner::Model>>) {
|
||||
if let Ok(Some(res)) = Owner::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 OwnerCreateParams {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
contact: String
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/owner",
|
||||
request_body = OwnerCreateParams,
|
||||
responses(
|
||||
(status = OK, body = owner::Model, description = "Successfully created owner"),
|
||||
),
|
||||
summary = "Create a new owner",
|
||||
description = "Create a new owner",
|
||||
tag = "owner-api",
|
||||
)]
|
||||
pub async fn create_owner(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(instance_payload): Json<OwnerCreateParams>,
|
||||
) -> (StatusCode, Json<Option<owner::Model>>) {
|
||||
|
||||
let book_instance = owner::ActiveModel {
|
||||
first_name: Set(instance_payload.first_name),
|
||||
last_name: Set(instance_payload.last_name),
|
||||
contact: Set(instance_payload.contact),
|
||||
id: NotSet
|
||||
};
|
||||
|
||||
let b = book_instance.save(state.db_conn.as_ref()).await;
|
||||
match b {
|
||||
Err(e) => {
|
||||
println!("Error while inserting new owner: {:#?}", 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"))))
|
||||
}
|
||||
}
|
||||
Reference in a new issue