work
This commit is contained in:
parent
e8e8d9d960
commit
c012683af1
10 changed files with 384 additions and 30 deletions
175
src/item.rs
175
src/item.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
src/main.rs
36
src/main.rs
|
@ -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()
|
||||
|
|
70
src/pages/item/inventory_page.rs
Normal file
70
src/pages/item/inventory_page.rs
Normal 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
50
src/pages/item/mod.rs
Normal 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)
|
||||
}
|
43
src/pages/item/variant_pages.rs
Normal file
43
src/pages/item/variant_pages.rs
Normal 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
1
src/pages/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod item;
|
9
src/ui/mod.rs
Normal file
9
src/ui/mod.rs
Normal 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)
|
||||
};
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue