move to rocket + refactor

This commit is contained in:
JMARyA 2024-06-21 21:14:45 +02:00
parent b0e80dd3e8
commit b8caa43972
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
11 changed files with 686 additions and 886 deletions

1258
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,18 +4,16 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
actix-web = "4.4.1"
chrono = "0.4.38" chrono = "0.4.38"
env_logger = "0.10.1"
futures = "0.3.30" futures = "0.3.30"
log = "0.4.20" log = "0.4.20"
maud = "0.25.0" mdq = { git = "https://git.hydrar.de/mdtools/mdq" }
mdq = { git = "https://git.hydrar.de/mdtools/mdq", version = "0.3.0" }
mongodb = "2.8.0" mongodb = "2.8.0"
rocket = { version = "0.5.1", features = ["json"] }
rocket_cors = "0.6.0"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111" serde_json = "1.0.111"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
tokio = { version = "1.35.1", features = ["full"] } tokio = { version = "1.35.1", features = ["full"] }
toml = "0.8.8" toml = "0.8.8"
uuid = { version = "1.8.0", features = ["v4"] } uuid = { version = "1.8.0", features = ["v4"] }
web-base = "0.2.2"

3
rocket.toml Normal file
View file

@ -0,0 +1,3 @@
[default]
address = "0.0.0.0"
port = 8080

View file

@ -1,4 +1,4 @@
use mongodb::{bson::doc, ClientSession}; use mongodb::bson::doc;
use crate::get_mongo; use crate::get_mongo;
@ -16,8 +16,7 @@ impl InventoryCache {
.return_document(mongodb::options::ReturnDocument::After) .return_document(mongodb::options::ReturnDocument::After)
.build(); .build();
let result = db db.database("cdb")
.database("cdb")
.collection::<mongodb::bson::Document>("cache") .collection::<mongodb::bson::Document>("cache")
.find_one_and_update(doc! { "_id": "inventory"}, update, options) .find_one_and_update(doc! { "_id": "inventory"}, update, options)
.await .await
@ -51,8 +50,7 @@ impl InventoryCache {
.return_document(mongodb::options::ReturnDocument::After) .return_document(mongodb::options::ReturnDocument::After)
.build(); .build();
let result = db db.database("cdb")
.database("cdb")
.collection::<mongodb::bson::Document>("cache") .collection::<mongodb::bson::Document>("cache")
.find_one_and_update(doc! { "_id": "inventory"}, update, options) .find_one_and_update(doc! { "_id": "inventory"}, update, options)
.await .await

View file

@ -49,7 +49,7 @@ impl ItemDB {
let mongodb = get_mongo!(); let mongodb = get_mongo!();
for item in &index.documents { for item in &index.documents {
let item = ItemEntry::new(item.clone()); let item = ItemEntry::new(item);
item.init_db(&mongodb).await; item.init_db(&mongodb).await;
} }
@ -62,7 +62,7 @@ impl ItemDB {
self.index self.index
.documents .documents
.iter() .iter()
.map(|x| ItemEntry::new(x.clone())) .map(ItemEntry::new)
.find(|x| x.name == item)?, .find(|x| x.name == item)?,
)) ))
} }
@ -71,7 +71,7 @@ impl ItemDB {
pub fn items(&self) -> Vec<String> { pub fn items(&self) -> Vec<String> {
let mut ret = vec![]; let mut ret = vec![];
for item in &self.index.documents { for item in &self.index.documents {
let item = ItemEntry::new(item.clone()); let item = ItemEntry::new(item);
ret.push(item.name); ret.push(item.name);
} }
ret ret

View file

@ -32,7 +32,7 @@ pub struct ItemEntry {
} }
impl ItemEntry { impl ItemEntry {
pub fn new(doc: mdq::Document) -> Self { pub fn new(doc: &mdq::Document) -> Self {
let name = std::path::Path::new(&doc.path) let name = std::path::Path::new(&doc.path)
.file_stem() .file_stem()
.unwrap() .unwrap()
@ -56,13 +56,12 @@ impl ItemEntry {
.as_mapping() .as_mapping()
.unwrap() .unwrap()
.get("variants") .get("variants")
.unwrap() .map(|x| x.as_mapping().unwrap().clone())
.as_mapping() .unwrap_or_default()
.unwrap()
{ {
variants.insert( variants.insert(
variant_name.as_str().unwrap().to_string(), variant_name.as_str().unwrap().to_string(),
Variant::from_yml(variant, variant_name.as_str().unwrap(), &name), Variant::from_yml(&variant, variant_name.as_str().unwrap(), &name),
); );
} }
@ -111,15 +110,15 @@ pub struct Item {
} }
impl Item { impl Item {
pub fn new(item: ItemEntry) -> Self { pub const fn new(item: ItemEntry) -> Self {
Self { item } Self { item }
} }
pub async fn get_variants(&self) -> Vec<String> { pub fn get_variants(&self) -> Vec<String> {
self.item.variants.keys().cloned().collect() self.item.variants.keys().cloned().collect()
} }
pub async fn variant(&self, variant: &str) -> Option<Variant> { pub fn variant(&self, variant: &str) -> Option<Variant> {
self.item.variants.get(variant).map(|x| x.clone()) self.item.variants.get(variant).cloned()
} }
} }

View file

@ -1,4 +1,5 @@
use actix_web::{get, HttpRequest, Responder}; use rocket::routes as route;
use rocket::{http::Method, launch};
mod cache; mod cache;
mod db; mod db;
@ -22,31 +23,35 @@ mod variant;
// ░████▀░░░▀▀█████▄▄▄▄█████████▄░░ // ░████▀░░░▀▀█████▄▄▄▄█████████▄░░
// ░░▀▀░░░░░░░░░▀▀██████▀▀░░░▀▀██░░ // ░░▀▀░░░░░░░░░▀▀██████▀▀░░░▀▀██░░
#[get("/")] #[launch]
pub(crate) async fn index(r: HttpRequest) -> impl Responder { async fn rocket() -> _ {
let itemdb: &actix_web::web::Data<db::ItemDB> = r.app_data().unwrap(); let cors = rocket_cors::CorsOptions {
let content = ""; allowed_origins: rocket_cors::AllowedOrigins::all(),
web_base::func::build_site(&r, "Index", &content) allowed_methods: vec![Method::Get, Method::Post, Method::Options]
.into_iter()
.map(From::from)
.collect(),
allowed_headers: rocket_cors::AllowedHeaders::all(),
allow_credentials: true,
..Default::default()
} }
.to_cors()
#[actix_web::main] .expect("error creating CORS options");
async fn main() -> std::io::Result<()> {
env_logger::init();
let itemdb = db::ItemDB::new("./itemdb").await; let itemdb = db::ItemDB::new("./itemdb").await;
let itemdb = actix_web::web::Data::new(itemdb);
web_base::map!(web_base::Site::new(), |app: actix_web::App<_>| { rocket::build()
app.app_data(itemdb.clone()) .mount(
.service(index) "/",
.service(routes::item::supply_route) // /supply route![
.service(routes::item::item_variants_page) // /item/{item_id}/variants routes::item::get_items_route,
.service(routes::item::get_items_route) // /items routes::item::item_variants_page,
.service(routes::item::demand_route) // /demand routes::item::supply_log_route,
.service(routes::item::supply_log_route) // /item/{item_id}/{variant_id}/supply routes::item::demand_log_route,
.service(routes::item::demand_log_route) // /item/{item_id}/{variant_id}/demand routes::item::supply_route,
}) routes::item::demand_route,
.bind(("0.0.0.0".to_string(), 8080))? ],
.run() )
.await .manage(itemdb)
.attach(cors)
} }

View file

@ -1,24 +1,20 @@
use actix_web::post; use rocket::serde::json::Json;
use actix_web::{get, HttpRequest, HttpResponse, Responder}; use rocket::State;
use rocket::{get, post};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json;
use crate::item; use crate::db::ItemDB;
use crate::routes::bad_req;
use crate::variant::Variant; use crate::variant::Variant;
macro_rules! get_itemdb { use super::{api_error, ApiError, FallibleApiResponse};
($req:expr) => {{
let itemdb: &actix_web::web::Data<crate::db::ItemDB> = $req.app_data().unwrap(); pub fn item_does_not_exist_error() -> ApiError {
itemdb api_error("The item does not exist")
}};
} }
pub fn item_does_not_exist_error() -> actix_web::Error { pub fn variant_does_not_exist_error() -> ApiError {
bad_req("The item does not exist") api_error("The item does not exist")
}
pub fn variant_does_not_exist_error() -> actix_web::Error {
bad_req("The item does not exist")
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -28,24 +24,20 @@ pub struct DemandForm {
price: String, price: String,
} }
#[post("/demand")] #[post("/demand", data = "<f>")]
pub async fn demand_route( pub async fn demand_route(f: Json<DemandForm>) -> FallibleApiResponse {
req: HttpRequest,
f: actix_web::web::Form<DemandForm>,
) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(req);
let uuid = Variant::demand( let uuid = Variant::demand(
&f.uuid, &f.uuid,
f.price f.price
.clone() .clone()
.try_into() .try_into()
.map_err(|_| bad_req("Price malformed"))?, .map_err(|()| api_error("Price malformed"))?,
&f.destination, &f.destination,
) )
.await .await
.ok_or_else(|| bad_req("Demand failed"))?; .ok_or_else(|| api_error("Demand failed"))?;
Ok(actix_web::HttpResponse::Ok().json(serde_json::json!({"uuid": uuid}))) Ok(json!({"uuid": uuid}))
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct SupplyForm { pub struct SupplyForm {
@ -56,18 +48,13 @@ pub struct SupplyForm {
origin: String, origin: String,
} }
#[post("/supply")] #[post("/supply", data = "<form>")]
pub async fn supply_route( pub async fn supply_route(form: Json<SupplyForm>, itemdb: &State<ItemDB>) -> FallibleApiResponse {
req: HttpRequest,
form: actix_web::web::Form<SupplyForm>,
) -> actix_web::Result<impl Responder> {
let itemdb = get_itemdb!(req);
println!("{form:?}"); println!("{form:?}");
let variant = itemdb let variant = itemdb
.get_item(&form.item) .get_item(&form.item)
.ok_or_else(item_does_not_exist_error)? .ok_or_else(item_does_not_exist_error)?
.variant(&form.variant) .variant(&form.variant)
.await
.ok_or_else(variant_does_not_exist_error)?; .ok_or_else(variant_does_not_exist_error)?;
let transaction_id = variant let transaction_id = variant
@ -76,72 +63,63 @@ pub async fn supply_route(
form.price form.price
.clone() .clone()
.try_into() .try_into()
.map_err(|_| bad_req("Price malformed"))?, .map_err(|()| api_error("Price malformed"))?,
&form.origin, &form.origin,
) )
.await; .await;
Ok(actix_web::HttpResponse::Ok().json(serde_json::json!({"uuid": transaction_id}))) Ok(json!({"uuid": transaction_id}))
} }
#[get("/items")] #[get("/items")]
pub async fn get_items_route(r: HttpRequest) -> impl Responder { pub fn get_items_route(itemdb: &State<ItemDB>) -> serde_json::Value {
let itemdb = get_itemdb!(r);
let items = itemdb.items(); let items = itemdb.items();
actix_web::HttpResponse::Ok().json(serde_json::json!({"items": items})) json!({"items": items})
} }
#[derive(Deserialize, Debug)] #[get("/item/<item_id>/variants")]
pub struct AddVariantForm { pub fn item_variants_page(item_id: &str, itemdb: &State<ItemDB>) -> FallibleApiResponse {
pub variant: String, let item = itemdb
pub amount: usize, .get_item(item_id)
} .ok_or_else(item_does_not_exist_error)?;
let variants = item.get_variants();
#[get("/item/{item_id}/variants")] Ok(json!({
pub async fn item_variants_page(r: HttpRequest) -> actix_web::Result<impl Responder> { "item": item_id,
let id = r.match_info().query("item_id");
let itemdb = get_itemdb!(r);
let item = itemdb.get_item(id).ok_or_else(item_does_not_exist_error)?;
let variants = item.get_variants().await;
Ok(HttpResponse::Ok().json(serde_json::json!({
"item": id,
"variants": variants "variants": variants
}))) }))
} }
#[get("/item/{item_id}/{variant_id}/supply")] #[get("/item/<item_id>/<variant_id>/supply")]
pub async fn supply_log_route(r: HttpRequest) -> actix_web::Result<impl Responder> { pub async fn supply_log_route(
let itemdb = get_itemdb!(r); item_id: &str,
let item_id = r.match_info().query("item_id"); variant_id: &str,
let variant_id = r.match_info().query("variant_id"); itemdb: &State<ItemDB>,
) -> FallibleApiResponse {
let variant = itemdb let variant = itemdb
.get_item(item_id) .get_item(item_id)
.ok_or_else(item_does_not_exist_error)? .ok_or_else(item_does_not_exist_error)?
.variant(variant_id) .variant(variant_id)
.await
.ok_or_else(variant_does_not_exist_error)?; .ok_or_else(variant_does_not_exist_error)?;
let transactions = variant.supply_log().await; let transactions = variant.supply_log().await;
Ok(HttpResponse::Ok().json(serde_json::json!(transactions))) Ok(json!(transactions))
} }
#[get("/item/{item_id}/{variant_id}/demand")] #[get("/item/<item_id>/<variant_id>/demand")]
pub async fn demand_log_route(r: HttpRequest) -> actix_web::Result<impl Responder> { pub async fn demand_log_route(
let itemdb = get_itemdb!(r); item_id: &str,
let item_id = r.match_info().query("item_id"); variant_id: &str,
let variant_id = r.match_info().query("variant_id"); itemdb: &State<ItemDB>,
) -> FallibleApiResponse {
let variant = itemdb let variant = itemdb
.get_item(item_id) .get_item(item_id)
.ok_or_else(item_does_not_exist_error)? .ok_or_else(item_does_not_exist_error)?
.variant(variant_id) .variant(variant_id)
.await
.ok_or_else(variant_does_not_exist_error)?; .ok_or_else(variant_does_not_exist_error)?;
let transactions = variant.demand_log().await; let transactions = variant.demand_log().await;
Ok(HttpResponse::Ok().json(serde_json::json!(transactions))) Ok(json!(transactions))
} }

View file

@ -1,5 +1,13 @@
use rocket::response::status::BadRequest;
use serde_json::json;
pub mod item; pub mod item;
pub fn bad_req(msg: &'static str) -> actix_web::Error { type ApiError = BadRequest<serde_json::Value>;
actix_web::error::ErrorBadRequest(msg) type FallibleApiResponse = Result<serde_json::Value, ApiError>;
fn api_error(msg: &str) -> ApiError {
BadRequest(json!({
"error": msg
}))
} }

View file

@ -3,14 +3,14 @@ pub enum TransactionType {
Normal(Transaction), Normal(Transaction),
} }
impl TransactionType { impl From<mongodb::bson::Document> for TransactionType {
pub fn from(b: mongodb::bson::Document) -> Self { fn from(value: mongodb::bson::Document) -> Self {
if let Ok(kind) = b.get_str("kind") { if let Ok(kind) = value.get_str("kind") {
if kind == "batch" { if kind == "batch" {
return Self::Batch(BatchTransaction::from(b)); return Self::Batch(value.into());
} }
} }
Self::Normal(Transaction::from(b)) Self::Normal(value.into())
} }
} }
@ -40,12 +40,14 @@ impl Transaction {
"_id": &self.uuid, "_id": &self.uuid,
"item": &self.item, "item": &self.item,
"variant": &self.variant, "variant": &self.variant,
"price": self.price.as_bson(), "price": Into::<mongodb::bson::Bson>::into(self.price.clone()),
"origin": &self.origin, "origin": &self.origin,
"timestamp": self.timestamp "timestamp": self.timestamp
} }
} }
}
impl From<mongodb::bson::Document> for Transaction {
fn from(b: mongodb::bson::Document) -> Self { fn from(b: mongodb::bson::Document) -> Self {
let uuid = b.get_str("oid").unwrap().to_string(); let uuid = b.get_str("oid").unwrap().to_string();
let item = b.get_str("item").unwrap().to_string(); let item = b.get_str("item").unwrap().to_string();
@ -64,6 +66,19 @@ impl Transaction {
} }
} }
impl From<Transaction> for mongodb::bson::Document {
fn from(value: Transaction) -> Self {
mongodb::bson::doc! {
"_id": &value.uuid,
"item": &value.item,
"variant": &value.variant,
"price": Into::<mongodb::bson::Bson>::into(value.price),
"origin": &value.origin,
"timestamp": value.timestamp
}
}
}
pub struct BatchTransaction { pub struct BatchTransaction {
pub uuid: String, pub uuid: String,
pub transactions: Vec<String>, pub transactions: Vec<String>,
@ -76,27 +91,32 @@ impl BatchTransaction {
transactions, transactions,
} }
} }
pub fn as_doc(&self) -> mongodb::bson::Document {
mongodb::bson::doc! {
"_id": &self.uuid,
"kind": "batch",
"transactions": &self.transactions
}
} }
fn from(b: mongodb::bson::Document) -> Self { impl From<mongodb::bson::Document> for BatchTransaction {
let uuid = b.get_str("_id").unwrap().to_string(); fn from(value: mongodb::bson::Document) -> Self {
let transactions = b let uuid = value.get_str("_id").unwrap().to_string();
let transactions = value
.get_array("transactions") .get_array("transactions")
.unwrap() .unwrap()
.into_iter() .iter()
.map(|x| x.as_str().unwrap().to_string()) .map(|x| x.as_str().unwrap().to_string())
.collect(); .collect();
Self { uuid, transactions } Self { uuid, transactions }
} }
} }
impl From<BatchTransaction> for mongodb::bson::Document {
fn from(value: BatchTransaction) -> Self {
mongodb::bson::doc! {
"_id": value.uuid,
"kind": "batch",
"transactions": value.transactions
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Price { pub struct Price {
pub value: f64, pub value: f64,
@ -119,11 +139,13 @@ impl Price {
currency: currency.to_string(), currency: currency.to_string(),
}) })
} }
}
pub fn as_bson(&self) -> mongodb::bson::Bson { impl From<Price> for mongodb::bson::Bson {
fn from(value: Price) -> Self {
mongodb::bson::bson!({ mongodb::bson::bson!({
"value": self.value, "value": value.value,
"currency": self.currency.clone() "currency": value.currency
}) })
} }
} }

View file

@ -34,8 +34,7 @@ impl Variant {
.as_mapping() .as_mapping()
.unwrap() .unwrap()
.get("amount") .get("amount")
.map(|x| x.as_u64().unwrap()) .map_or(1, |x| x.as_u64().unwrap()),
.unwrap_or(1),
depends: json depends: json
.as_mapping() .as_mapping()
.unwrap() .unwrap()
@ -43,11 +42,11 @@ impl Variant {
.map(|x| { .map(|x| {
x.as_sequence() x.as_sequence()
.unwrap() .unwrap()
.into_iter() .iter()
.map(|x| x.as_str().unwrap().to_string()) .map(|x| x.as_str().unwrap().to_string())
.collect() .collect()
}) })
.unwrap_or(Vec::new()), .unwrap_or_default(),
} }
} }
@ -104,14 +103,10 @@ impl Variant {
// check if supply transaction exists // check if supply transaction exists
let supply_t = cdb_col!(db, "supply"); let supply_t = cdb_col!(db, "supply");
if supply_t supply_t
.find_one(doc! { "_id": uuid}, None) .find_one(doc! { "_id": uuid}, None)
.await .await
.unwrap() .unwrap()?;
.is_none()
{
return None;
}
// todo : demand batch // todo : demand batch
@ -122,7 +117,7 @@ impl Variant {
doc! { doc! {
"_id": uuid, "_id": uuid,
"destination": destination, "destination": destination,
"price": price.as_bson() "price": Into::<mongodb::bson::Bson>::into(price)
}, },
None, None,
) )
@ -163,7 +158,7 @@ impl Variant {
} }
for transaction in &transactions { for transaction in &transactions {
let r = col.insert_one(transaction.as_doc(), None).await.unwrap(); col.insert_one(transaction.as_doc(), None).await.unwrap();
// update cache // update cache
InventoryCache::push(&transaction.uuid).await; InventoryCache::push(&transaction.uuid).await;
@ -177,8 +172,12 @@ impl Variant {
BatchTransaction::new(transactions.iter().map(|x| x.uuid.clone()).collect()); BatchTransaction::new(transactions.iter().map(|x| x.uuid.clone()).collect());
let col: mongodb::Collection<mongodb::bson::Document> = let col: mongodb::Collection<mongodb::bson::Document> =
db.database("cdb").collection("transactions_batch"); db.database("cdb").collection("transactions_batch");
col.insert_one(batch.as_doc(), None).await.unwrap(); let batch_uuid = batch.uuid.clone();
batch.uuid
col.insert_one(Into::<mongodb::bson::Document>::into(batch), None)
.await
.unwrap();
batch_uuid
}; };
ret_uuid ret_uuid