This commit is contained in:
JMARyA 2024-01-16 09:02:23 +01:00
parent e8e8d9d960
commit c012683af1
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
10 changed files with 384 additions and 30 deletions

View file

@ -1,22 +1,163 @@
use mongodb::{Collection, bson::doc};
use std::collections::HashSet;
use futures::TryStreamExt;
use mongodb::{bson::doc, Collection};
macro_rules! collect_results {
($res:expr) => {{
let mut ret = vec![];
while let Some(doc) = $res.try_next().await.unwrap() {
ret.push(doc);
}
ret
}};
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ItemVariantEntry {
pub item_name: String,
pub variant_name: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ItemInventoryEntry {
pub item_name: String,
pub variant_name: String,
pub origin: String,
pub price: f64,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ItemEntry {
pub name: String,
pub category: String
pub category: String,
}
impl ItemEntry {
pub fn new(doc: mdq::Document) -> Self {
let name = std::path::Path::new(&doc.path).file_stem().unwrap().to_str().unwrap().to_string();
let category = doc.frontmatter.as_mapping().unwrap().get("category").unwrap().as_str().unwrap().to_string();
let name = std::path::Path::new(&doc.path)
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string();
let category = doc
.frontmatter
.as_mapping()
.unwrap()
.get("category")
.unwrap()
.as_str()
.unwrap()
.to_string();
Self { name, category }
}
}
pub struct Item {
db: mongodb::Client,
pub item: ItemEntry,
}
impl Item {
pub fn new(db: mongodb::Client, item: ItemEntry) -> Self {
Self { db, item }
}
pub async fn get_inventory_entries(&self) -> Vec<ItemInventoryEntry> {
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();
collect_results!(found)
}
pub async fn get_origins(&self) -> Vec<String> {
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 hashset = HashSet::new();
while let Some(res) = found.try_next().await.unwrap() {
hashset.insert(res.origin);
}
hashset.into_iter().collect()
}
pub async fn add_inventory(&self, variant: &str, origin: &str, price: f64) -> String {
// todo : implement
let quantities: Collection<ItemInventoryEntry> =
self.db.database("cdb").collection("item_quantities");
let id = quantities
.insert_one(
ItemInventoryEntry {
item_name: self.item.name.clone(),
variant_name: variant.to_owned(),
origin: origin.to_owned(),
price,
},
None,
)
.await
.unwrap();
return id.inserted_id.as_object_id().unwrap().to_string();
}
pub async fn get_variants(&self) -> Vec<String> {
let variants: Collection<ItemVariantEntry> =
self.db.database("cdb").collection("item_variants");
let mut found_variants = variants
.find(
doc! {
"item_name": self.item.name.clone()
},
None,
)
.await
.unwrap();
let mut ret = vec!["Generic".to_string()];
while let Some(doc) = found_variants.try_next().await.unwrap() {
ret.push(doc.variant_name);
}
ret
}
pub async fn add_variant(&self, name: &str) {
let variants: Collection<ItemVariantEntry> =
self.db.database("cdb").collection("item_variants");
variants
.insert_one(
ItemVariantEntry {
item_name: self.item.name.clone(),
variant_name: name.to_owned(),
},
None,
)
.await
.unwrap();
}
}
pub struct ItemDB {
index: mdq::Index,
mongodb: mongodb::Client
mongodb: mongodb::Client,
}
impl ItemDB {
@ -29,14 +170,30 @@ impl ItemDB {
let item = ItemEntry::new(item.clone());
log::info!("Adding item {} to DB", item.name);
let items: Collection<ItemEntry> = mongodb.database("cdb").collection("items");
items.insert_one(item, None).await.unwrap();
if let None =
items
.find_one(doc! { "name": item.name.clone()}, None)
.await
.unwrap()
{
items.insert_one(item, None).await.unwrap();
} else {
// todo : update values
}
}
Self { index, mongodb }
}
pub fn get_item(&self, item: &str) -> Option<ItemEntry> {
self.index.documents.iter().map(|x| ItemEntry::new(x.clone())).find(|x| x.name == item)
pub fn get_item(&self, item: &str) -> Option<Item> {
Some(Item::new(
self.mongodb.clone(),
self.index
.documents
.iter()
.map(|x| ItemEntry::new(x.clone()))
.find(|x| x.name == item)?,
))
}
pub fn items(&self) -> Vec<String> {
@ -47,4 +204,4 @@ impl ItemDB {
}
ret
}
}
}

View file

@ -1,14 +1,17 @@
use actix_web::{get, HttpRequest, Responder};
use maud::html;
mod item;
mod pages;
mod ui;
// ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░
// ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░
// ░░░░░░░░░░▄███████▀░░░▀███▄░░░░░
// ░░░░░░░░▄███████▀░░░░░░░▀███▄░░░
// ░░░░░░▄████████░░░░░░░░░░░███▄░░
// ░░░░░██████████▄░░░░░░░░░░░███▌░ ▒█▀▀█ █▀▀█ █▀▄▀█ █▀▄▀█ ▒█▀▀▄ ▒█▀▀█
// ░░░░░▀█████▀░▀███▄░░░░░░░░░▐███░ ▒█░░░ █░░█ █░▀░█ █░▀░█ ▒█░▒█ ▒█▀▀▄
// ░░░░░██████████▄░░░░░░░░░░░███▌░ ▒█▀▀█ █▀▀█ █▀▄▀█ █▀▄▀█ ▒█▀▀▄ ▒█▀▀█
// ░░░░░▀█████▀░▀███▄░░░░░░░░░▐███░ ▒█░░░ █░░█ █░▀░█ █░▀░█ ▒█░▒█ ▒█▀▀▄
// ░░░░░░░▀█▀░░░░░▀███▄░░░░░░░▐███░ ▒█▄▄█ ▀▀▀▀ ▀░░░▀ ▀░░░▀ ▒█▄▄▀ ▒█▄▄█
// ░░░░░░░░░░░░░░░░░▀███▄░░░░░███▌░
// ░░░░▄██▄░░░░░░░░░░░▀███▄░░▐███░░
@ -17,23 +20,6 @@ mod 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!(
p { "Item" };
p { (format!("Category: {}", item.category))}
).into_string();
web_base::build_site(&r, "Item", &content)
}
#[get("/")]
pub(crate) async fn index(r: HttpRequest) -> impl Responder {
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
@ -52,13 +38,21 @@ pub(crate) async fn index(r: HttpRequest) -> impl Responder {
async fn main() -> std::io::Result<()> {
env_logger::init();
let itemdb = item::ItemDB::new("./itemdb", "mongodb://user:pass@mongodb:27017").await;
let itemdb = item::ItemDB::new("./itemdb", "mongodb://user:pass@127.0.0.1:27017").await;
let itemdb = actix_web::web::Data::new(itemdb);
web_base::map!(
web_base::Site::new()
.head_content("<script src=\"https://cdn.tailwindcss.com\"></script>".to_string()),
|app: actix_web::App<_>| { app.app_data(itemdb.clone()).service(index).service(item_page) }
|app: actix_web::App<_>| {
app.app_data(itemdb.clone())
.service(index)
.service(pages::item::item_page)
.service(pages::item::variant_pages::add_item_variant_page)
.service(pages::item::variant_pages::add_item_variant_page_post)
.service(pages::item::inventory_page::add_item_inventory_page)
.service(pages::item::inventory_page::add_item_inventory_page_post)
}
)
.bind(("0.0.0.0".to_string(), 8080))?
.run()

View file

@ -0,0 +1,70 @@
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)
}

50
src/pages/item/mod.rs Normal file
View file

@ -0,0 +1,50 @@
use actix_web::{get, HttpRequest, Responder};
use maud::html;
pub mod inventory_page;
pub mod variant_pages;
use crate::{item, ui::button};
#[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)
}

View file

@ -0,0 +1,43 @@
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)
}

1
src/pages/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod item;

9
src/ui/mod.rs Normal file
View file

@ -0,0 +1,9 @@
use maud::html;
pub fn button(title: &str, redir: &str) -> maud::PreEscaped<String> {
html!(
a class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded hover:cursor-pointer mt-1 mr-1 mb-1" href=(redir) {
(title)
};
)
}