This commit is contained in:
JMARyA 2024-04-29 13:11:27 +02:00
parent 9da4ef072c
commit ba61580adb
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
7 changed files with 177 additions and 179 deletions

9
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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<ItemInventoryEntry> =
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<ItemInventoryEntry> =
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<Variant> {
// 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<ItemVariantEntry> =
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<String> 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<mongodb::bson::Document> =
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<Item> {
Some(Item::new(
self.mongodb.clone(),

View file

@ -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()

View file

@ -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<AddinventoryForm>,
) -> impl Responder {
let id = r.match_info().query("item_id");
let itemdb: &actix_web::web::Data<item::ItemDB> = 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<item::ItemDB> = 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)
}

View file

@ -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<item::ItemDB> = 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<item::ItemDB> = $req.app_data().unwrap();
itemdb
}};
}
#[derive(Deserialize, Debug)]
pub struct SupplyForm {
item: String,
variant: String,
amount: Option<usize>,
price: String,
origin: String,
}
#[post("/supply")]
pub async fn supply_route(
req: HttpRequest,
form: actix_web::web::Form<SupplyForm>,
) -> 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
}))
}

View file

@ -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<AddVariantForm>,
) -> impl Responder {
let id = r.match_info().query("item_id");
let itemdb: &actix_web::web::Data<item::ItemDB> = 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<item::ItemDB> = 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)
}