diff --git a/src/cache.rs b/src/cache.rs index 2ade0fb..7a3a6a4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,6 +1,6 @@ use mongodb::bson::doc; -use crate::get_mongo; +use crate::{get_mongo, id_of}; pub struct InventoryCache {} @@ -18,7 +18,7 @@ impl InventoryCache { db.database("cdb") .collection::("cache") - .find_one_and_update(doc! { "_id": "inventory"}, update, options) + .find_one_and_update(id_of!("inventory"), update, options) .await .unwrap(); } @@ -28,7 +28,7 @@ impl InventoryCache { let inventorycache = db .database("cdb") .collection::("cache") - .find_one(doc! {"_id": "inventory"}, None) + .find_one(id_of!("inventory"), None) .await .unwrap() .unwrap(); @@ -52,7 +52,7 @@ impl InventoryCache { db.database("cdb") .collection::("cache") - .find_one_and_update(doc! { "_id": "inventory"}, update, options) + .find_one_and_update(id_of!("inventory"), update, options) .await .unwrap(); } diff --git a/src/db.rs b/src/db.rs index 40c7a32..5c08c99 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,6 @@ -use crate::item::{Item, ItemEntry}; +use crate::item::Item; +/// Collect database results into a `Vec<_>` #[macro_export] macro_rules! collect_results { ($res:expr) => {{ @@ -14,6 +15,7 @@ macro_rules! collect_results { }}; } +/// Get a database collection #[macro_export] macro_rules! cdb_col { ($db:expr, $col:expr) => { @@ -22,6 +24,7 @@ macro_rules! cdb_col { }; } +/// Get a MongoDB Client from the environment #[macro_export] macro_rules! get_mongo { () => { @@ -31,6 +34,7 @@ macro_rules! get_mongo { }; } +/// MongoDB filter for the `_id` field. #[macro_export] macro_rules! id_of { ($id:expr) => { @@ -38,18 +42,22 @@ macro_rules! id_of { }; } +/// Item database pub struct ItemDB { index: mdq::Index, } impl ItemDB { + /// Create a new item database using `dir` as the base. + /// + /// The directory should contain markdown documents with valid frontmatter to be parsed into `Item`s pub async fn new(dir: &str) -> Self { // scan for markdown item entries let index = mdq::Index::new(dir, true); let mongodb = get_mongo!(); for item in &index.documents { - let item = ItemEntry::new(item); + let item = Item::new(item); item.init_db(&mongodb).await; } @@ -58,20 +66,20 @@ impl ItemDB { /// Retrieves an item by name pub fn get_item(&self, item: &str) -> Option { - Some(Item::new( + Some( self.index .documents .iter() - .map(ItemEntry::new) + .map(Item::new) // <-- todo : performance? .find(|x| x.name == item)?, - )) + ) } /// Get all items pub fn items(&self) -> Vec { let mut ret = vec![]; for item in &self.index.documents { - let item = ItemEntry::new(item); + let item = Item::new(item); ret.push(item.name); } ret diff --git a/src/item.rs b/src/item.rs index 5ff53c3..bdd81bc 100644 --- a/src/item.rs +++ b/src/item.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use crate::id_of; use mongodb::{bson::doc, Collection}; +use serde_json::json; use crate::variant::Variant; @@ -25,13 +26,23 @@ fn hashmap_to_bson_document( document } -pub struct ItemEntry { +/// Represents a single item in a collection of physical goods. +/// +/// This struct serves as a blueprint for describing individual items within +/// a larger inventory or database of physical goods. It includes fields for +/// the name of the item and its category. +pub struct Item { + /// The name of the Item pub name: String, + /// Category of the Item pub category: String, + /// The variants of an Item. + /// Each key of the `HashMap<_>` is the name of a variant and contains a `Variant` pub variants: HashMap, } -impl ItemEntry { +impl Item { + /// Creates a new `Item` from a parsed markdown document pub fn new(doc: &mdq::Document) -> Self { let name = std::path::Path::new(&doc.path) .file_stem() @@ -72,6 +83,7 @@ impl ItemEntry { } } + /// Adds the `Item` to the database pub async fn init_db(&self, mongodb: &mongodb::Client) { log::info!("Adding item {} to DB", self.name); let items: Collection = @@ -100,25 +112,28 @@ impl ItemEntry { } } -/// Represents a single item in a collection of physical goods. -/// -/// This struct serves as a blueprint for describing individual items within -/// a larger inventory or database of physical goods. It includes fields for -/// the name of the item and its category. -pub struct Item { - pub item: ItemEntry, -} - impl Item { - pub const fn new(item: ItemEntry) -> Self { - Self { item } - } - + /// Get this items variant names pub fn get_variants(&self) -> Vec { - self.item.variants.keys().cloned().collect() + self.variants.keys().cloned().collect() } + /// Get a `Variant` of an `Item` pub fn variant(&self, variant: &str) -> Option { - self.item.variants.get(variant).cloned() + self.variants.get(variant).cloned() + } + + pub fn api_json(&self) -> serde_json::Value { + let variants: HashMap = self + .variants + .iter() + .map(|(key, value)| (key.clone(), value.api_json())) + .collect(); + + json!({ + "item": self.name, + "category": self.category, + "variants": variants + }) } } diff --git a/src/main.rs b/src/main.rs index 3d863f4..9ec9a4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,11 +45,13 @@ async fn rocket() -> _ { "/", route![ routes::item::get_items_route, + routes::item::item_route, routes::item::item_variants_page, routes::item::supply_log_route, routes::item::demand_log_route, routes::item::supply_route, routes::item::demand_route, + routes::item::transaction_route ], ) .manage(itemdb) diff --git a/src/routes/item/mod.rs b/src/routes/item/mod.rs index dbb9545..8e76cdb 100644 --- a/src/routes/item/mod.rs +++ b/src/routes/item/mod.rs @@ -5,6 +5,7 @@ use serde::Deserialize; use serde_json::json; use crate::db::ItemDB; +use crate::transaction::Transaction; use crate::variant::Variant; use super::{api_error, ApiError, FallibleApiResponse}; @@ -77,6 +78,14 @@ pub fn get_items_route(itemdb: &State) -> serde_json::Value { json!({"items": items}) } +#[get("/item/")] +pub fn item_route(item_id: &str, itemdb: &State) -> FallibleApiResponse { + let item = itemdb + .get_item(item_id) + .ok_or_else(item_does_not_exist_error)?; + Ok(item.api_json()) +} + #[get("/item//variants")] pub fn item_variants_page(item_id: &str, itemdb: &State) -> FallibleApiResponse { let item = itemdb @@ -123,3 +132,11 @@ pub async fn demand_log_route( Ok(json!(transactions)) } + +#[get("/transaction/")] +pub async fn transaction_route(transaction: &str) -> FallibleApiResponse { + let t = Transaction::get(transaction) + .await + .ok_or_else(|| api_error("No transaction with this UUID"))?; + Ok(t.api_json()) +} diff --git a/src/transaction.rs b/src/transaction.rs index de80cf9..452a85c 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,3 +1,9 @@ +use mongodb::bson::doc; +use serde::Serialize; +use serde_json::json; + +use crate::{cdb_col, get_mongo, id_of}; + pub enum TransactionType { Batch(BatchTransaction), Normal(Transaction), @@ -35,6 +41,24 @@ impl Transaction { } } + pub async fn get(uuid: &str) -> Option { + let db = get_mongo!(); + let transactions = cdb_col!(db, "transactions"); + let doc = transactions.find_one(id_of!(uuid), None).await.ok()??; + Some(doc.into()) + } + + pub fn api_json(&self) -> serde_json::Value { + json!({ + "uuid": self.uuid, + "item": self.item, + "variant": self.variant, + "price": self.price, + "origin": self.origin, + "timestamp": self.timestamp + }) + } + pub fn as_doc(&self) -> mongodb::bson::Document { mongodb::bson::doc! { "_id": &self.uuid, @@ -49,12 +73,12 @@ impl Transaction { impl From for Transaction { fn from(b: mongodb::bson::Document) -> Self { - let uuid = b.get_str("oid").unwrap().to_string(); + let uuid = b.get_str("_id").unwrap().to_string(); let item = b.get_str("item").unwrap().to_string(); let variant = b.get_str("variant").unwrap().to_string(); let origin = b.get_str("origin").unwrap().to_string(); let timestamp = b.get_i64("timestamp").unwrap(); - let price = Price::from(b); + let price = b.get_document("price").unwrap().clone().into(); Self { uuid, item, @@ -117,7 +141,7 @@ impl From for mongodb::bson::Document { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct Price { pub value: f64, pub currency: String, @@ -153,7 +177,7 @@ impl From for mongodb::bson::Bson { impl From for Price { fn from(value: mongodb::bson::Document) -> Self { Self { - value: value.get_f64("value").unwrap(), + value: value.get_f64("value").unwrap_or(0.0), currency: value.get_str("currency").unwrap().to_string(), } } diff --git a/src/variant.rs b/src/variant.rs index 7cb6bfe..2aae1e1 100644 --- a/src/variant.rs +++ b/src/variant.rs @@ -1,8 +1,9 @@ use mongodb::bson::doc; +use serde_json::json; use crate::{ cache::InventoryCache, - cdb_col, collect_results, get_mongo, + cdb_col, collect_results, get_mongo, id_of, transaction::{BatchTransaction, Price, Transaction}, }; @@ -103,10 +104,7 @@ impl Variant { // check if supply transaction exists let supply_t = cdb_col!(db, "supply"); - supply_t - .find_one(doc! { "_id": uuid}, None) - .await - .unwrap()?; + supply_t.find_one(id_of!(uuid), None).await.unwrap()?; // todo : demand batch @@ -183,6 +181,15 @@ impl Variant { ret_uuid } + pub fn api_json(&self) -> serde_json::Value { + json!({ + "item": self.item, + "variant": self.variant, + "amount": self.amount as u32, + "depends": self.depends + }) + } + pub fn as_bson(&self) -> mongodb::bson::Document { mongodb::bson::doc! { "item": &self.item,