diff --git a/Cargo.lock b/Cargo.lock index 7530500..9009605 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -484,6 +484,7 @@ dependencies = [ "serde_json", "tokio", "toml", + "uuid", "web-base", ] @@ -2649,9 +2650,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -2661,9 +2662,9 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e" +checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 58fc1d0..2042a9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,5 @@ serde = { version = "1.0.195", features = ["derive"] } serde_json = "1.0.111" tokio = { version = "1.35.1", features = ["full"] } toml = "0.8.8" +uuid = { version = "1.8.0", features = ["v4"] } web-base = "0.2.2" diff --git a/src/item.rs b/src/item.rs index 6b9ce93..9142697 100644 --- a/src/item.rs +++ b/src/item.rs @@ -3,6 +3,33 @@ use std::collections::HashSet; use futures::TryStreamExt; use mongodb::{bson::doc, Collection}; +// todo : api key auth + +// case: itemset +// items describe generic top level structure + +// case: item variants +// specific instance of an item (real world) +// possibly deviating attrs + +// case: import +// made / bought an item -> supply +// check in with attrs -> uuid (transaction) +// commited to db + +// case: export +// used an item -> demand +// mark as used +// update db + +// ITEM +// VARIANTS +// QUANTIZATION +// IMPORT / EXPORT +// DEPENDENCIES +// DEMAND STATS +// SEASONAL REVIEWS + macro_rules! collect_results { ($res:expr) => {{ let mut ret = vec![]; @@ -15,6 +42,14 @@ macro_rules! collect_results { }}; } +macro_rules! get_mongo { + () => { + mongodb::Client::with_uri_str(std::env::var("DB_URI").unwrap()) + .await + .unwrap() + }; +} + #[derive(serde::Deserialize, serde::Serialize)] pub struct ItemVariantEntry { pub item_name: String, @@ -70,11 +105,10 @@ impl Item { let quantities: Collection = self.db.database("cdb").collection("item_quantities"); - let mut found = - quantities - .find(doc! { "item_name": self.item.name.clone()}, None) - .await - .unwrap(); + let mut found = quantities + .find(doc! { "item_name": self.item.name.clone()}, None) + .await + .unwrap(); collect_results!(found) } @@ -83,11 +117,10 @@ impl Item { let quantities: Collection = self.db.database("cdb").collection("item_quantities"); - let mut found = - quantities - .find(doc! { "item_name": self.item.name.clone()}, None) - .await - .unwrap(); + let mut found = quantities + .find(doc! { "item_name": self.item.name.clone()}, None) + .await + .unwrap(); let mut hashset = HashSet::new(); @@ -139,6 +172,14 @@ impl Item { ret } + pub fn variant(&self, variant: &str) -> Option { + // todo : check if avail + Some(Variant { + item: self.item.name.clone(), + variant: variant.to_string(), + }) + } + pub async fn add_variant(&self, name: &str) { let variants: Collection = self.db.database("cdb").collection("item_variants"); @@ -155,6 +196,66 @@ impl Item { } } +#[derive(Debug, Clone)] +pub struct Price(String); + +impl Price { + fn new(price: &str) -> Self { + // todo : implement + + Self(price.to_string()) + } +} + +impl From for Price { + fn from(val: std::string::String) -> Self { + Self::new(&val) + } +} + +pub struct Variant { + pub item: String, + pub variant: String, +} + +pub struct Transaction {} + +impl Transaction { + pub fn new(item: &str, variant: &str, price: Price, origin: &str) -> Self { + // todo : implement + Self {} + } + + fn as_doc(self) -> mongodb::bson::Document { + mongodb::bson::doc! {} + } +} + +impl Variant { + pub async fn supply(&self, amount: usize, price: Price, origin: &str) -> String { + // todo : implement db transactions + let db = get_mongo!(); + let col: mongodb::Collection = + db.database("cdb").collection("transactions"); + + let mut transactions = vec![]; + for _ in 0..amount { + transactions.push(Transaction::new( + &self.item, + &self.variant, + price.clone(), + origin, + )); + } + + for transaction in transactions { + let r = col.insert_one(transaction.as_doc(), None).await; + } + + todo!() + } +} + pub struct ItemDB { index: mdq::Index, mongodb: mongodb::Client, @@ -165,7 +266,7 @@ impl ItemDB { // scan for markdown item entries let index = mdq::Index::new(dir, true); let mongodb = mongodb::Client::with_uri_str(mongodb).await.unwrap(); - + /* for item in &index.documents { let item = ItemEntry::new(item.clone()); log::info!("Adding item {} to DB", item.name); @@ -180,11 +281,12 @@ impl ItemDB { } else { // todo : update values } - } + }*/ Self { index, mongodb } } + /// Retrieves an item by name pub fn get_item(&self, item: &str) -> Option { Some(Item::new( self.mongodb.clone(), diff --git a/src/main.rs b/src/main.rs index 4c3cc88..94b72b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,11 +36,8 @@ async fn main() -> std::io::Result<()> { web_base::map!(web_base::Site::new(), |app: actix_web::App<_>| { app.app_data(itemdb.clone()) .service(index) - .service(routes::item::item_page) - .service(routes::item::variant_pages::add_item_variant_page) - .service(routes::item::variant_pages::add_item_variant_page_post) - .service(routes::item::inventory_page::add_item_inventory_page) - .service(routes::item::inventory_page::add_item_inventory_page_post) + .service(routes::item::supply_route) + .service(routes::item::item_variants_page) }) .bind(("0.0.0.0".to_string(), 8080))? .run() diff --git a/src/routes/item/inventory_page.rs b/src/routes/item/inventory_page.rs deleted file mode 100644 index 7e85072..0000000 --- a/src/routes/item/inventory_page.rs +++ /dev/null @@ -1,70 +0,0 @@ -use actix_web::{get, post, HttpRequest, Responder}; -use maud::html; - -use crate::item; - -#[derive(serde::Serialize, serde::Deserialize)] -struct AddinventoryForm { - variant: String, - origin: String, - price: f64, -} - -#[post("/item/{item_id}/inventory/add")] -pub async fn add_item_inventory_page_post( - r: HttpRequest, - f: actix_web::web::Form, -) -> impl Responder { - let id = r.match_info().query("item_id"); - let itemdb: &actix_web::web::Data = r.app_data().unwrap(); - let item = itemdb.get_item(id).unwrap(); - let rec_id = item.add_inventory(&f.variant, &f.origin, f.price).await; - - // todo : generate receipts - - web_base::redirect(&format!("/receipt/{}", rec_id)) -} - -#[get("/item/{item_id}/inventory/add")] -pub async fn add_item_inventory_page(r: HttpRequest) -> impl Responder { - let id = r.match_info().query("item_id"); - - let itemdb: &actix_web::web::Data = r.app_data().unwrap(); - - let item = itemdb.get_item(id).unwrap(); - let item_variants = item.get_variants().await; - - // todo : get previous origins - let item_origins = item.get_origins().await; - - let content = html!( - div class="bg-white p-8 rounded shadow-md max-w-md grid items-center justify-center m-auto mt-5" { - h1 class="text-2xl font-bold mb-4" { "Add Item inventory" }; - form action=(format!("/item/{}/inventory/add", id)) method="post" { - div { - label class="text-sm font-medium text-gray-600 mr-3" for="variant" { "Variant:" }; - select class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" id="variant" name="variant" { - @for variant in item_variants { - option value=(variant) { (variant) }; - }; - }; - }; - br; - label class="text-sm font-medium text-gray-600" for="origin" { "Origin:" }; - input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="text" name="origin" list="origin_options" placeholder="Origin"; - datalist id="origin_options" { - @for origin in item_origins { - option value=(origin); - } - }; - br; - label class="text-sm font-medium text-gray-600" for="price" { "Price: "}; - input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="number" step="0.01" name="price" placeholder="0.00"; - br; - input class="hover:cursor-pointer bg-blue-500 text-white rounded px-4 py-2 mt-5" type="submit" name="submit" value="Add"; - }; - }; - ).into_string(); - - web_base::build_site(&r, "Add Item inventory", &content) -} diff --git a/src/routes/item/mod.rs b/src/routes/item/mod.rs index a6e1973..33c7af3 100644 --- a/src/routes/item/mod.rs +++ b/src/routes/item/mod.rs @@ -1,50 +1,60 @@ -use actix_web::{get, HttpRequest, Responder}; +use actix_web::post; +use actix_web::{get, HttpRequest, HttpResponse, Responder}; use maud::html; -pub mod inventory_page; -pub mod variant_pages; +use serde::Deserialize; use crate::item; -#[get("/item/{item_id}")] -pub async fn item_page(r: HttpRequest) -> impl Responder { - let id = r.match_info().query("item_id"); - println!("{}", id); - - let itemdb: &actix_web::web::Data = r.app_data().unwrap(); - - let item = itemdb.get_item(id).unwrap(); - - let content = - html!( - div class="bg-gray-100 min-h-screen min-w-screen items-center justify-center py-5" { - div class="w-screen px-5" { - div { - p class="mb-2 font-bold text-2xl" { (format!("Item : {}", item.item.name)) }; - p class="font-bold" { (format!("Category: {}", item.item.category))}; - }; - div class="mt-5" { - // (button("Add Variant", &format!("/item/{}/variant/add", item.item.name))) - // (button("Add inventory", &format!("/item/{}/inventory/add", item.item.name))); - }; - }; - - - div class="mt-5 px-5" { - h1 class="text-2xl font-bold mb-4" { "Variants" }; - @for variant in item.get_variants().await { - p class="mb-2" { (variant) }; - } - @for q in item.get_inventory_entries().await { - div class="p-2" { - p { (format!("Variant {}", q.variant_name))}; - p { (format!("Origin {}", q.origin))}; - p { (format!("Price {}", q.price))}; - }; - } - }; - }; - ) - .into_string(); - - web_base::build_site(&r, "Item", &content) +macro_rules! get_itemdb { + ($req:expr) => {{ + let itemdb: &actix_web::web::Data = $req.app_data().unwrap(); + itemdb + }}; +} + +#[derive(Deserialize, Debug)] +pub struct SupplyForm { + item: String, + variant: String, + amount: Option, + price: String, + origin: String, +} + +#[post("/supply")] +pub async fn supply_route( + req: HttpRequest, + form: actix_web::web::Form, +) -> impl Responder { + let itemdb = get_itemdb!(req); + println!("{form:?}"); + let variant = itemdb + .get_item(&form.item) + .unwrap() + .variant(&form.variant) + .unwrap(); + let transaction_id = variant + .supply( + form.amount.unwrap_or(1), + form.price.clone().into(), + &form.origin, + ) + .await; + actix_web::HttpResponse::Ok().json(serde_json::json!({"uuid": transaction_id})) +} + +#[get("/item/{item_id}/variants")] +pub async fn item_variants_page(r: HttpRequest) -> impl Responder { + let id = r.match_info().query("item_id"); + + let itemdb = get_itemdb!(r); + + let item = itemdb.get_item(id); + + let variants = item.unwrap().get_variants().await; + + HttpResponse::Ok().json(serde_json::json!({ + "item": id, + "variants": variants + })) } diff --git a/src/routes/item/variant_pages.rs b/src/routes/item/variant_pages.rs deleted file mode 100644 index 7f2e996..0000000 --- a/src/routes/item/variant_pages.rs +++ /dev/null @@ -1,43 +0,0 @@ -use actix_web::{get, post, HttpRequest, Responder}; -use maud::html; - -use crate::item; - -#[derive(serde::Serialize, serde::Deserialize)] -struct AddVariantForm { - variant_name: String, -} - -#[post("/item/{item_id}/variant/add")] -pub async fn add_item_variant_page_post( - r: HttpRequest, - f: actix_web::web::Form, -) -> impl Responder { - let id = r.match_info().query("item_id"); - let itemdb: &actix_web::web::Data = r.app_data().unwrap(); - let item = itemdb.get_item(id).unwrap(); - item.add_variant(&f.variant_name).await; - - web_base::redirect(&format!("/item/{}", id)) -} - -#[get("/item/{item_id}/variant/add")] -pub async fn add_item_variant_page(r: HttpRequest) -> impl Responder { - let id = r.match_info().query("item_id"); - - let itemdb: &actix_web::web::Data = r.app_data().unwrap(); - - let item = itemdb.get_item(id).unwrap(); - - let content = html!( - div class="bg-white p-8 rounded shadow-md max-w-md grid items-center justify-center m-auto mt-5" { - h1 class="text-2xl font-bold mb-4" { "Add Variant for " (item.item.name) }; - form action=(format!("/item/{}/variant/add", id)) method="post" { - input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="text" name="variant_name" placeholder="Name" { }; - input class="ml-5 hover:cursor-pointer bg-blue-500 text-white rounded px-4 py-2 mt-5" type="submit" name="submit" value="Add"; - }; - }; - ).into_string(); - - web_base::build_site(&r, "Add Item Variant", &content) -}