This commit is contained in:
parent
910b10de35
commit
b817d36816
6 changed files with 193 additions and 10 deletions
|
|
@ -34,6 +34,8 @@ where C: ConnectionTrait {
|
|||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::book_instance::Entity")]
|
||||
BookInstance,
|
||||
#[sea_orm(has_one = "super::bal_stats::Entity")]
|
||||
BalStats,
|
||||
}
|
||||
|
||||
impl Related<super::book_instance::Entity> for Entity {
|
||||
|
|
|
|||
38
src/entities/bal_stats.rs
Normal file
38
src/entities/bal_stats.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
#[sea_orm(table_name = "BALStats")]
|
||||
#[schema(title="BalStats", as=entities::BALStats)]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = true)]
|
||||
#[serde(skip_serializing)]
|
||||
id: u32,
|
||||
pub bal_id: u32,
|
||||
|
||||
pub total_owned_sold_books: u32,
|
||||
pub total_sold_books: u32,
|
||||
|
||||
pub total_owned_collected_money: f32,
|
||||
pub total_collected_money: f32,
|
||||
|
||||
pub total_different_owners: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::bal::Entity",
|
||||
from = "Column::BalId",
|
||||
to = "super::bal::Column::Id"
|
||||
)]
|
||||
Bal,
|
||||
}
|
||||
|
||||
impl Related<super::bal::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Bal.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
pub mod prelude;
|
||||
|
||||
pub mod bal;
|
||||
pub mod bal_stats;
|
||||
pub mod book;
|
||||
pub mod book_instance;
|
||||
pub mod owner;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
pub use super::bal::Entity as Bal;
|
||||
pub use super::bal_stats::Entity as BalStats;
|
||||
pub use super::book::Entity as Book;
|
||||
pub use super::book_instance::Entity as BookInstance;
|
||||
pub use super::owner::Entity as Owner;
|
||||
|
|
|
|||
|
|
@ -175,6 +175,8 @@ pub async fn run_server(db: Arc<DatabaseConnection>, port: u16, serve_docs: bool
|
|||
.routes(routes!(routes::bal::stop_bal))
|
||||
.routes(routes!(routes::bal::get_bals))
|
||||
.routes(routes!(routes::bal::get_bal_accounting))
|
||||
.routes(routes!(routes::bal::return_to_owner))
|
||||
.routes(routes!(routes::bal::get_stats_by_bal_id))
|
||||
// Authentication
|
||||
.route_layer(middleware::from_fn_with_state(shared_state.clone(), routes::auth::auth_middleware))
|
||||
.routes(routes!(routes::auth::auth))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{collections::{HashMap, HashSet}, 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::{bal::{self, BalState}, book_instance, owner, prelude::*}, routes::auth::Claims, AppState};
|
||||
use crate::{entities::{bal::{self, BalState}, bal_stats, book_instance, owner, prelude::*}, routes::auth::Claims, AppState};
|
||||
|
||||
|
||||
#[derive(IntoParams)]
|
||||
|
|
@ -242,16 +242,49 @@ pub async fn stop_bal(
|
|||
let mut bal: bal::ActiveModel = bal.into();
|
||||
bal.state = Set(BalState::Ended);
|
||||
|
||||
match bal.update(state.db_conn.as_ref()).await {
|
||||
Err(e) => {
|
||||
log::error!(target: "api", "Error while updating bal 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 bal is saved");
|
||||
(StatusCode::OK, Json(Some(model)))
|
||||
let model = bal.update(state.db_conn.as_ref()).await.unwrap().try_into_model().expect("All fields should be set once the bal is saved");
|
||||
|
||||
let self_owner = Owner::find()
|
||||
.filter(owner::Column::User.eq(true))
|
||||
.filter(owner::Column::UserId.eq(claims.user_id))
|
||||
.one(state.db_conn.as_ref()).await.unwrap().unwrap();
|
||||
|
||||
let mut total_sold_books = 0;
|
||||
let mut total_owned_sold_books = 0;
|
||||
let mut total_collected_money = 0.;
|
||||
let mut total_owned_collected_money = 0.;
|
||||
|
||||
let mut owner_set = HashSet::new();
|
||||
for book in BookInstance::find()
|
||||
.filter(book_instance::Column::BalId.eq(id))
|
||||
.all(state.db_conn.as_ref()).await.unwrap() {
|
||||
if book.sold_price.is_some() {
|
||||
owner_set.insert(book.owner_id);
|
||||
|
||||
total_sold_books += 1;
|
||||
total_collected_money += book.sold_price.unwrap();
|
||||
if book.owner_id == self_owner.id {
|
||||
total_owned_sold_books += 1;
|
||||
total_owned_collected_money += book.sold_price.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let bal_stats = bal_stats::ActiveModel {
|
||||
id: NotSet,
|
||||
bal_id: Set(id),
|
||||
|
||||
total_sold_books: Set(total_sold_books),
|
||||
total_owned_sold_books: Set(total_owned_sold_books),
|
||||
|
||||
total_collected_money: Set(total_collected_money),
|
||||
total_owned_collected_money: Set(total_owned_collected_money),
|
||||
|
||||
total_different_owners: Set(owner_set.len().try_into().unwrap())
|
||||
};
|
||||
|
||||
bal_stats.save(state.db_conn.as_ref()).await.unwrap();
|
||||
(StatusCode::OK, Json(Some(model)))
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, Json(None))
|
||||
}
|
||||
|
|
@ -349,3 +382,109 @@ pub async fn get_bal_accounting(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub struct BalAccountingReturnParams {
|
||||
return_type: ReturnType,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub enum ReturnType {
|
||||
Books,
|
||||
Money,
|
||||
All
|
||||
}
|
||||
|
||||
#[derive(IntoParams)]
|
||||
#[into_params(names("id", "owner_id"), parameter_in = Path)]
|
||||
#[allow(dead_code)]
|
||||
pub struct BalAndOwnerByIdParams(u32, u32);
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/bal/{id}/accounting/return/{owner_id}",
|
||||
params(BalAndOwnerByIdParams),
|
||||
request_body = BalAccountingReturnParams,
|
||||
security(("jwt" = [])),
|
||||
responses(
|
||||
(status = OK, description = "Successfully returned requested type to owner"),
|
||||
(status = CONFLICT, description = "Books and money have already been returned to the owner, or no money nor books were owed"),
|
||||
(status = NOT_FOUND, description = "No bal or owner with this id exists in the database"),
|
||||
(status = FORBIDDEN, description = "You don't own the specified bal or owner"),
|
||||
),
|
||||
summary = "Return books and/or money to an owner",
|
||||
description = "Return books and/or money to an owner",
|
||||
tag = "bal-api",
|
||||
)]
|
||||
pub async fn return_to_owner(
|
||||
State(state): State<Arc<AppState>>,
|
||||
claims: Claims,
|
||||
Path((id, owner_id)): Path<(u32, u32)>,
|
||||
Json(payload): Json<BalAccountingReturnParams>
|
||||
) -> StatusCode {
|
||||
if let Ok(Some(bal)) = Bal::find_by_id(id).one(state.db_conn.as_ref()).await && let Ok(Some(owner)) = Owner::find_by_id(owner_id).one(state.db_conn.as_ref()).await {
|
||||
if bal.user_id != claims.user_id || owner.user_id != claims.user_id {
|
||||
StatusCode::FORBIDDEN
|
||||
} else {
|
||||
let owner_books = BookInstance::find()
|
||||
.filter(book_instance::Column::OwnerId.eq(owner.id))
|
||||
.filter(book_instance::Column::BalId.eq(bal.id))
|
||||
.all(state.db_conn.as_ref()).await.unwrap();
|
||||
if owner_books.is_empty() {
|
||||
return StatusCode::CONFLICT;
|
||||
}
|
||||
|
||||
let mut book_delete_query = BookInstance::delete_many()
|
||||
.filter(book_instance::Column::OwnerId.eq(owner.id))
|
||||
.filter(book_instance::Column::BalId.eq(bal.id));
|
||||
book_delete_query = match payload.return_type {
|
||||
ReturnType::Books => book_delete_query.filter(book_instance::Column::SoldPrice.is_null()),
|
||||
ReturnType::Money => book_delete_query.filter(book_instance::Column::SoldPrice.is_not_null()),
|
||||
ReturnType::All => book_delete_query
|
||||
};
|
||||
|
||||
let _ = book_delete_query.exec(state.db_conn.as_ref()).await;
|
||||
StatusCode::OK
|
||||
}
|
||||
} else {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/bal/{id}/stats",
|
||||
params(BalByIdParams),
|
||||
security(("jwt" = [])),
|
||||
responses(
|
||||
(status = OK, body = bal_stats::Model, description = "Found bal stats with corresponding ID in the database"),
|
||||
(status = CONFLICT, description = "The specified BAL is not ended yet, can't get stats"),
|
||||
(status = NOT_FOUND, description = "No bal with this id exists in the database"),
|
||||
(status = FORBIDDEN, description = "You don't own the specified bal"),
|
||||
),
|
||||
summary = "Get a bal's stats by its ID",
|
||||
description = "Get a bal's stats from its ID",
|
||||
tag = "bal-api",
|
||||
)]
|
||||
pub async fn get_stats_by_bal_id(
|
||||
State(state): State<Arc<AppState>>,
|
||||
claims: Claims,
|
||||
Path(id): Path<u32>,
|
||||
) -> (StatusCode, Json<Option<bal_stats::Model>>) {
|
||||
if let Ok(Some(bal)) = Bal::find_by_id(id).one(state.db_conn.as_ref()).await {
|
||||
if !bal.user_id == claims.user_id {
|
||||
(StatusCode::FORBIDDEN, Json(None))
|
||||
} else if bal.state != BalState::Ended {
|
||||
(StatusCode::CONFLICT, Json(None))
|
||||
} else {
|
||||
if let Ok(Some(stats)) = BalStats::find().filter(bal_stats::Column::BalId.eq(bal.id)).one(state.db_conn.as_ref()).await {
|
||||
(StatusCode::OK, Json(Some(stats)))
|
||||
} else {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(None))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, Json(None))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue