Compare commits
2 commits
6ddb24e563
...
e3f954679a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3f954679a | ||
|
|
684eba8c70 |
4 changed files with 53 additions and 20 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -61,7 +61,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"utoipa",
|
"utoipa",
|
||||||
"utoipa-axum",
|
"utoipa-axum",
|
||||||
"utoipa-redoc",
|
|
||||||
"utoipa-swagger-ui",
|
"utoipa-swagger-ui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3590,18 +3589,6 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utoipa-redoc"
|
|
||||||
version = "6.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6427547f6db7ec006cbbef95f7565952a16f362e298b416d2d497d9706fef72d"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"utoipa",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa-swagger-ui"
|
name = "utoipa-swagger-ui"
|
||||||
version = "9.0.2"
|
version = "9.0.2"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ tokio = { version = "1.46.1", features = [ "full" ] }
|
||||||
utoipa = "5.4.0"
|
utoipa = "5.4.0"
|
||||||
utoipa-axum = "0.2.0"
|
utoipa-axum = "0.2.0"
|
||||||
utoipa-swagger-ui = { version = "9", features = ["axum", "reqwest"] }
|
utoipa-swagger-ui = { version = "9", features = ["axum", "reqwest"] }
|
||||||
utoipa-redoc = { version = "6", features = ["axum"] }
|
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
|
|
|
||||||
10
src/main.rs
10
src/main.rs
|
|
@ -7,7 +7,6 @@ use sea_orm::{ConnectionTrait, Database, DatabaseConnection, EntityTrait, Pagina
|
||||||
use tokio::{sync::broadcast::{self, Sender}};
|
use tokio::{sync::broadcast::{self, Sender}};
|
||||||
use utoipa::{openapi::{security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, ContactBuilder, InfoBuilder, LicenseBuilder}, Modify, OpenApi};
|
use utoipa::{openapi::{security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, ContactBuilder, InfoBuilder, LicenseBuilder}, Modify, OpenApi};
|
||||||
use utoipa_axum::router::OpenApiRouter;
|
use utoipa_axum::router::OpenApiRouter;
|
||||||
use utoipa_redoc::{Redoc, Servable};
|
|
||||||
use utoipa_swagger_ui::{Config, SwaggerUi};
|
use utoipa_swagger_ui::{Config, SwaggerUi};
|
||||||
use utoipa_axum::routes;
|
use utoipa_axum::routes;
|
||||||
|
|
||||||
|
|
@ -160,6 +159,7 @@ async fn run_server(db: Arc<DatabaseConnection>) {
|
||||||
// Book API
|
// Book API
|
||||||
.routes(routes!(routes::book::get_book_by_ean))
|
.routes(routes!(routes::book::get_book_by_ean))
|
||||||
.routes(routes!(routes::book::get_book_by_id))
|
.routes(routes!(routes::book::get_book_by_id))
|
||||||
|
.routes(routes!(routes::book::create_book))
|
||||||
// Book Instance API
|
// Book Instance API
|
||||||
.routes(routes!(routes::book_instance::get_book_instance_by_id))
|
.routes(routes!(routes::book_instance::get_book_instance_by_id))
|
||||||
.routes(routes!(routes::book_instance::create_book_instance))
|
.routes(routes!(routes::book_instance::create_book_instance))
|
||||||
|
|
@ -195,14 +195,14 @@ async fn run_server(db: Arc<DatabaseConnection>) {
|
||||||
|
|
||||||
api.merge(ApiDoc::openapi());
|
api.merge(ApiDoc::openapi());
|
||||||
|
|
||||||
let redoc = Redoc::with_url("/docs/", api.clone());
|
let swagger = SwaggerUi::new("/docs/")
|
||||||
let swagger = SwaggerUi::new("/docs2/")
|
.url("/docs/openapi.json", api)
|
||||||
.url("/docs2/openapi.json", api)
|
|
||||||
.config(Config::default()
|
.config(Config::default()
|
||||||
.try_it_out_enabled(true)
|
.try_it_out_enabled(true)
|
||||||
|
.filter(true)
|
||||||
|
.display_request_duration(true)
|
||||||
);
|
);
|
||||||
|
|
||||||
let router = router.merge(redoc);
|
|
||||||
let router = router.merge(swagger);
|
let router = router.merge(swagger);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{extract::{Path, State}, Json};
|
use axum::{extract::{Path, State}, Json};
|
||||||
use reqwest::{StatusCode};
|
use reqwest::{StatusCode};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait, QueryFilter, TryIntoModel};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use utoipa::IntoParams;
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
use crate::{entities::{book, prelude::{Book}}, utils::open_library};
|
use crate::{entities::{book, prelude::{Book}}, utils::open_library};
|
||||||
|
|
@ -85,3 +86,49 @@ pub async fn get_book_by_ean(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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<Arc<AppState>>,
|
||||||
|
Json(instance_payload): Json<BookCreateParams>,
|
||||||
|
) -> (StatusCode, Json<Option<book::Model>>) {
|
||||||
|
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"))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Reference in a new issue