Compare commits
6 commits
b51aa94d5f
...
d8c29e1ec8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8c29e1ec8 | ||
|
|
62a1d9e2a7 | ||
|
|
1c4abab453 | ||
|
|
8a7cfac885 | ||
|
|
00b38c4989 | ||
|
|
53c95f90fd |
5 changed files with 234 additions and 5 deletions
|
|
@ -69,12 +69,21 @@ async fn main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let (router, mut api) = OpenApiRouter::new()
|
let (router, mut api) = OpenApiRouter::new()
|
||||||
|
// 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))
|
||||||
|
// 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))
|
||||||
|
.routes(routes!(routes::book_instance::update_book_instance))
|
||||||
|
.routes(routes!(routes::book_instance::sell_book_instance))
|
||||||
|
.routes(routes!(routes::book_instance::bulk_create_book_instance))
|
||||||
|
// Owner API
|
||||||
.routes(routes!(routes::owner::get_owner_by_id))
|
.routes(routes!(routes::owner::get_owner_by_id))
|
||||||
.routes(routes!(routes::owner::create_owner))
|
.routes(routes!(routes::owner::create_owner))
|
||||||
|
.routes(routes!(routes::owner::update_owner))
|
||||||
|
.routes(routes!(routes::owner::get_owners))
|
||||||
|
// Misc
|
||||||
.routes(routes!(routes::websocket::ws_handler))
|
.routes(routes!(routes::websocket::ws_handler))
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.with_state(shared_state)
|
.with_state(shared_state)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ struct BookInstanceByIdParams(u32);
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/book_instance/id/{id}",
|
path = "/book_instance/{id}",
|
||||||
params(BookInstanceByIdParams),
|
params(BookInstanceByIdParams),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book_instance::Model, description = "Found book instance with corresponding ID in the database"),
|
(status = OK, body = book_instance::Model, description = "Found book instance with corresponding ID in the database"),
|
||||||
|
|
@ -79,3 +79,140 @@ pub async fn create_book_instance(
|
||||||
Ok(res) => (StatusCode::OK, Json(Some(res.try_into_model().expect("All fields should be set once the book instance is saved"))))
|
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,
|
||||||
|
responses(
|
||||||
|
(status = OK, body = book_instance::Model, description = "Successfully updated book instance"),
|
||||||
|
(status = NOT_FOUND, description = "No book instance with specified id was found"),
|
||||||
|
),
|
||||||
|
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>>,
|
||||||
|
Path(id): Path<u32>,
|
||||||
|
Json(instance_payload): Json<BookInstanceUpdateParams>,
|
||||||
|
) -> (StatusCode, Json<Option<book_instance::Model>>) {
|
||||||
|
|
||||||
|
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,
|
||||||
|
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>>,
|
||||||
|
Path(id): Path<u32>,
|
||||||
|
Json(instance_payload): Json<BookInstanceSaleParams>,
|
||||||
|
) -> (StatusCode, Json<Option<book_instance::Model>>) {
|
||||||
|
|
||||||
|
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>,
|
||||||
|
responses(
|
||||||
|
(status = OK, description = "Successfully created book instances"),
|
||||||
|
),
|
||||||
|
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>>,
|
||||||
|
Json(instance_payload): Json<Vec<BookInstanceCreateParams>>,
|
||||||
|
) -> StatusCode {
|
||||||
|
|
||||||
|
let instances = instance_payload
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| book_instance::ActiveModel {
|
||||||
|
book_id: Set(p.book_id),
|
||||||
|
owner_id: Set(p.owner_id),
|
||||||
|
price: Set(p.price),
|
||||||
|
status: Set(book_instance::BookStatus::Available),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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::{ActiveModelTrait, ActiveValue::{NotSet, Set}, EntityTrait, TryIntoModel};
|
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set, Unchanged}, EntityTrait, TryIntoModel};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utoipa::IntoParams;
|
use utoipa::IntoParams;
|
||||||
|
|
||||||
|
|
@ -82,3 +82,86 @@ pub async fn create_owner(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||||
|
pub struct OwnerUpdateParams {
|
||||||
|
first_name: Option<String>,
|
||||||
|
last_name: Option<String>,
|
||||||
|
contact: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
#[utoipa::path(
|
||||||
|
patch,
|
||||||
|
path = "/owner/{id}",
|
||||||
|
params(OwnerByIdParams),
|
||||||
|
request_body = OwnerUpdateParams,
|
||||||
|
responses(
|
||||||
|
(status = OK, body = owner::Model, description = "Successfully updated owner"),
|
||||||
|
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
||||||
|
),
|
||||||
|
summary = "Update an owner",
|
||||||
|
description = "Update an owner",
|
||||||
|
tag = "owner-api",
|
||||||
|
)]
|
||||||
|
pub async fn update_owner(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Path(id): Path<u32>,
|
||||||
|
Json(instance_payload): Json<OwnerUpdateParams>,
|
||||||
|
) -> (StatusCode, Json<Option<owner::Model>>) {
|
||||||
|
|
||||||
|
if let Ok(Some(owner)) = Owner::find_by_id(id).one(state.db_conn.as_ref()).await {
|
||||||
|
let mut owner: owner::ActiveModel = owner.into();
|
||||||
|
owner.first_name = match instance_payload.first_name {
|
||||||
|
None => owner.first_name,
|
||||||
|
Some(v) => Set(v)
|
||||||
|
};
|
||||||
|
owner.last_name = match instance_payload.last_name {
|
||||||
|
None => owner.last_name,
|
||||||
|
Some(v) => Set(v)
|
||||||
|
};
|
||||||
|
owner.contact = match instance_payload.contact {
|
||||||
|
None => owner.contact,
|
||||||
|
Some(v) => Set(v)
|
||||||
|
};
|
||||||
|
|
||||||
|
match owner.update(state.db_conn.as_ref()).await {
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: "api", "Error while updating owner 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 owner is saved");
|
||||||
|
let _ = state.event_bus.send(Event::WebsocketBroadcast(WebsocketMessage::NewOwner(Arc::new(model.clone()))));
|
||||||
|
(StatusCode::OK, Json(Some(model)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(StatusCode::NOT_FOUND, Json(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/owners",
|
||||||
|
responses(
|
||||||
|
(status = OK, body = Vec<owner::Model>, description = "List of owners"),
|
||||||
|
),
|
||||||
|
summary = "List book owners",
|
||||||
|
description = "List book owners",
|
||||||
|
tag = "owner-api",
|
||||||
|
)]
|
||||||
|
pub async fn get_owners(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
) -> (StatusCode, Json<Option<Vec<owner::Model>>>) {
|
||||||
|
match Owner::find().all(state.db_conn.as_ref()).await {
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: "api", "Error while getting owner list: {:#?}", e);
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(None))
|
||||||
|
}
|
||||||
|
Ok(owners) => {
|
||||||
|
(StatusCode::OK, Json(Some(owners)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@ use axum::{
|
||||||
ws::{Message, Utf8Bytes, WebSocket, WebSocketUpgrade},
|
ws::{Message, Utf8Bytes, WebSocket, WebSocketUpgrade},
|
||||||
ConnectInfo,
|
ConnectInfo,
|
||||||
State
|
State
|
||||||
}, http, response::IntoResponse
|
}, response::IntoResponse
|
||||||
};
|
};
|
||||||
use reqwest::StatusCode;
|
|
||||||
|
|
||||||
use crate::{utils::events, AppState};
|
use crate::{utils::events, AppState};
|
||||||
|
|
||||||
|
|
@ -73,6 +72,7 @@ async fn handle_socket(mut socket: WebSocket, who: SocketAddr, state: Arc<AppSta
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
match event {
|
match event {
|
||||||
events::Event::WebsocketBroadcast(message) => {
|
events::Event::WebsocketBroadcast(message) => {
|
||||||
|
log::debug!(target: "websocket", "Sent {message:?} to {who}");
|
||||||
let _ = sender.send(Message::Text(Utf8Bytes::from(message.to_json().to_string()))).await;
|
let _ = sender.send(Message::Text(Utf8Bytes::from(message.to_json().to_string()))).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum Event {
|
||||||
WebsocketBroadcast(WebsocketMessage)
|
WebsocketBroadcast(WebsocketMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum WebsocketMessage {
|
pub enum WebsocketMessage {
|
||||||
NewOwner(Arc<owner::Model>)
|
NewOwner(Arc<owner::Model>)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue