From 53d8d184c4f20efa985b113f11bfaa10cf3ea54c Mon Sep 17 00:00:00 2001 From: Lee Smet Date: Fri, 4 Apr 2025 15:59:30 +0200 Subject: [PATCH 01/10] Inject some builders in script Signed-off-by: Lee Smet --- herodb/Cargo.toml | 2 +- herodb/src/cmd/dbexample2/main.rs | 261 ++++++++++++++++++++++++------ herodb/src/db/base.rs | 17 +- herodb/src/db/db.rs | 188 ++++++++++++--------- herodb/src/db/macros.rs | 14 +- herodb/src/models/biz/currency.rs | 16 +- herodb/src/models/biz/product.rs | 62 +++---- herodb/src/models/biz/sale.rs | 34 ++-- 8 files changed, 404 insertions(+), 190 deletions(-) diff --git a/herodb/Cargo.toml b/herodb/Cargo.toml index 2dd1c57..c228f10 100644 --- a/herodb/Cargo.toml +++ b/herodb/Cargo.toml @@ -19,7 +19,7 @@ tempfile = "3.8" poem = "1.3.55" poem-openapi = { version = "2.0.11", features = ["swagger-ui"] } tokio = { version = "1", features = ["full"] } -rhai = "1.15.1" +rhai = "1.21.0" paste = "1.0" [[example]] diff --git a/herodb/src/cmd/dbexample2/main.rs b/herodb/src/cmd/dbexample2/main.rs index d7e7215..e9ce238 100644 --- a/herodb/src/cmd/dbexample2/main.rs +++ b/herodb/src/cmd/dbexample2/main.rs @@ -1,13 +1,12 @@ -use chrono::{DateTime, Utc, Duration}; +use chrono::{DateTime, Duration, Utc}; use herodb::db::{DB, DBBuilder}; use herodb::models::biz::{ - Currency, CurrencyBuilder, - Product, ProductBuilder, ProductComponent, ProductComponentBuilder, - ProductType, ProductStatus, - Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus + Currency, CurrencyBuilder, Product, ProductBuilder, ProductComponent, ProductComponentBuilder, + ProductStatus, ProductType, Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus, }; -use std::path::PathBuf; +use rhai::{Engine, packages::Package}; use std::fs; +use std::path::PathBuf; fn main() -> Result<(), Box> { println!("DB Example 2: Using Builder Pattern and Model-Specific Methods"); @@ -21,25 +20,170 @@ fn main() -> Result<(), Box> { fs::create_dir_all(&db_path)?; println!("Database path: {:?}", db_path); + let mut engine = Engine::new(); + + engine + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::(); + + // Register currency builder methods + engine.register_fn("new_currency_builder", CurrencyBuilder::new); + engine.register_fn("amount", CurrencyBuilder::amount); + engine.register_fn("currency_code", CurrencyBuilder::currency_code::); + engine.register_fn("build", CurrencyBuilder::build); + + // Register method to verify currency + engine.register_fn("amount", Currency::amount); + + // Register product component builder methods + engine.register_fn( + "new_product_component_builder", + ProductComponentBuilder::new, + ); + engine.register_fn("id", ProductComponentBuilder::id); + engine.register_fn("name", ProductComponentBuilder::name::); + engine.register_fn( + "description", + ProductComponentBuilder::description::, + ); + engine.register_fn("quantity", ProductComponentBuilder::quantity); + engine.register_fn("build", ProductComponentBuilder::build); + + // Register product builder methods + engine.register_fn("new_product_builder", ProductBuilder::new); + engine.register_fn("id", ProductBuilder::id); + engine.register_fn("name", ProductBuilder::name::); + engine.register_fn("description", ProductBuilder::description::); + engine.register_fn("price", ProductBuilder::price); + engine.register_fn("type", ProductBuilder::type_); + engine.register_fn("category", ProductBuilder::category::); + engine.register_fn("status", ProductBuilder::status); + engine.register_fn("max_amount", ProductBuilder::max_amount); + engine.register_fn("validity_days", ProductBuilder::validity_days); + engine.register_fn("add_component", ProductBuilder::add_component); + engine.register_fn("build", ProductBuilder::build); + + // Register db builder methods + engine.register_fn("new_db_builder", DBBuilder::new::); + engine.register_fn("register_currency", DBBuilder::register_model::); + engine.register_fn("register_product", DBBuilder::register_model::); + engine.register_fn("register_sale", DBBuilder::register_model::); + engine.register_fn("currency_code", CurrencyBuilder::currency_code::); + engine.register_fn("build", DBBuilder::build); + + // Register db methods + engine.register_fn("insert_currency", DB::insert_currency); + engine.register_fn("insert_product", DB::insert_product); + + let script = r#" + let usd = new_currency_builder() + .amount(0.0) + .currency_code("USD") + .build(); + + // Can we access and print this from the actual Currency? + print(usd.amount()); + + let db = new_db_builder("./tmp/dbexample2") + .register_product() + .register_currency() + .register_sale() + .build(); + + db.insert_currency(usd); + + let component1 = new_product_component_builder() + .id(101) + .name("Basic Support") + .description("24/7 email support") + .quantity(1) + .build(); + + let component2 = new_product_component_builder() + .id(102) + .name("Premium Support") + .description("24/7 phone and email support") + .quantity(1) + .build(); + + // Create products using the builder + // let product1 = new_product_builder() + // .id(1) + // .name("Standard Plan") + // .description("Our standard service offering") + // .price( + // new_currency_builder() + // .amount(29.99) + // .currency_code("USD") + // .build() + // ) + // .type_(ProductType::Service) + // .category("Subscription") + // .status(ProductStatus::Available) + // .max_amount(1000) + // .validity_days(30) + // .add_component(component1) + // .build(); + // + // let product2 = new_product_builder() + // .id(2) + // .name("Premium Plan") + // .description("Our premium service offering with priority support") + // .price( + // new_currency_builder() + // .amount(99.99) + // .currency_code("USD") + // .build() + // ) + // .type_(ProductType::Service) + // .category("Subscription") + // .status(ProductStatus::Available) + // .max_amount(500) + // .validity_days(30) + // .add_component(component2) + // .build(); + + // Insert products using model-specific methods + // db.insert_product(product1); + // db.insert_product(product2); + "#; + + engine.eval::<()>(script)?; + // Create a database instance with our models registered - let db = DBBuilder::new(&db_path) + let mut db = DBBuilder::new(&db_path) .register_model::() .register_model::() .register_model::() .build()?; + // Check if the currency created in the script is actually present, if it is this value should + // be 1 (NOTE: it will be :) ). + let currencies = db.list_currencies()?; + println!("Found {} currencies in db", currencies.len()); + for currency in currencies { + println!("{} {}", currency.amount, currency.currency_code); + } + println!("\n1. Creating Products with Builder Pattern"); println!("----------------------------------------"); - // Create a currency using the builder - let usd = CurrencyBuilder::new() - .amount(0.0) // Initial amount - .currency_code("USD") - .build()?; - - // Insert the currency - db.insert_currency(&usd)?; - println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code); + // // Create a currency using the builder + // let usd = CurrencyBuilder::new() + // .amount(0.0) // Initial amount + // .currency_code("USD") + // .build()?; + // + // // Insert the currency + // db.insert_currency(usd.clone())?; + // println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code); // Create product components using the builder let component1 = ProductComponentBuilder::new() @@ -55,16 +199,17 @@ fn main() -> Result<(), Box> { .description("24/7 phone and email support") .quantity(1) .build()?; - // Create products using the builder let product1 = ProductBuilder::new() .id(1) .name("Standard Plan") .description("Our standard service offering") - .price(CurrencyBuilder::new() - .amount(29.99) - .currency_code("USD") - .build()?) + .price( + CurrencyBuilder::new() + .amount(29.99) + .currency_code("USD") + .build()?, + ) .type_(ProductType::Service) .category("Subscription") .status(ProductStatus::Available) @@ -77,10 +222,12 @@ fn main() -> Result<(), Box> { .id(2) .name("Premium Plan") .description("Our premium service offering with priority support") - .price(CurrencyBuilder::new() - .amount(99.99) - .currency_code("USD") - .build()?) + .price( + CurrencyBuilder::new() + .amount(99.99) + .currency_code("USD") + .build()?, + ) .type_(ProductType::Service) .category("Subscription") .status(ProductStatus::Available) @@ -90,18 +237,27 @@ fn main() -> Result<(), Box> { .build()?; // Insert products using model-specific methods - db.insert_product(&product1)?; - db.insert_product(&product2)?; + db.insert_product(product1.clone())?; + db.insert_product(product2.clone())?; - println!("Product created: {} (${:.2})", product1.name, product1.price.amount); - println!("Product created: {} (${:.2})", product2.name, product2.price.amount); + println!( + "Product created: {} (${:.2})", + product1.name, product1.price.amount + ); + println!( + "Product created: {} (${:.2})", + product2.name, product2.price.amount + ); println!("\n2. Retrieving Products"); println!("--------------------"); // Retrieve products using model-specific methods let retrieved_product1 = db.get_product(1)?; - println!("Retrieved: {} (${:.2})", retrieved_product1.name, retrieved_product1.price.amount); + println!( + "Retrieved: {} (${:.2})", + retrieved_product1.name, retrieved_product1.price.amount + ); println!("Components:"); for component in &retrieved_product1.components { println!(" - {} ({})", component.name, component.description); @@ -114,10 +270,15 @@ fn main() -> Result<(), Box> { let all_products = db.list_products()?; println!("Found {} products:", all_products.len()); for product in all_products { - println!(" - {} (${:.2}, {})", - product.name, + println!( + " - {} (${:.2}, {})", + product.name, product.price.amount, - if product.is_purchasable() { "Available" } else { "Unavailable" } + if product.is_purchasable() { + "Available" + } else { + "Unavailable" + } ); } @@ -126,17 +287,19 @@ fn main() -> Result<(), Box> { // Create a sale using the builder let now = Utc::now(); - + let item1 = SaleItemBuilder::new() .id(201) .sale_id(1) .product_id(1) .name("Standard Plan") .quantity(1) - .unit_price(CurrencyBuilder::new() - .amount(29.99) - .currency_code("USD") - .build()?) + .unit_price( + CurrencyBuilder::new() + .amount(29.99) + .currency_code("USD") + .build()?, + ) .active_till(now + Duration::days(30)) .build()?; @@ -151,11 +314,10 @@ fn main() -> Result<(), Box> { .build()?; // Insert the sale using model-specific methods - db.insert_sale(&sale)?; - println!("Sale created: #{} for {} (${:.2})", - sale.id, - sale.buyer_name, - sale.total_amount.amount + db.insert_sale(sale.clone())?; + println!( + "Sale created: #{} for {} (${:.2})", + sale.id, sale.buyer_name, sale.total_amount.amount ); println!("\n5. Updating a Sale"); @@ -163,12 +325,15 @@ fn main() -> Result<(), Box> { // Retrieve the sale, update it, and save it back let mut retrieved_sale = db.get_sale(1)?; - println!("Retrieved sale: #{} with status {:?}", retrieved_sale.id, retrieved_sale.status); - + println!( + "Retrieved sale: #{} with status {:?}", + retrieved_sale.id, retrieved_sale.status + ); + // Update the status retrieved_sale.update_status(SaleStatus::Completed); - db.insert_sale(&retrieved_sale)?; - + db.insert_sale(retrieved_sale.clone())?; + println!("Updated sale status to {:?}", retrieved_sale.status); println!("\n6. Deleting Objects"); @@ -187,4 +352,4 @@ fn main() -> Result<(), Box> { println!("\nExample completed successfully!"); Ok(()) -} \ No newline at end of file +} diff --git a/herodb/src/db/base.rs b/herodb/src/db/base.rs index 30da0fd..444cf58 100644 --- a/herodb/src/db/base.rs +++ b/herodb/src/db/base.rs @@ -1,5 +1,6 @@ use bincode; use brotli::{CompressorReader, Decompressor}; +use rhai::CustomType; use serde::{Deserialize, Serialize}; use sled; use std::fmt::Debug; @@ -38,15 +39,11 @@ pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized { let mut compressed = Vec::new(); // Default Brotli parameters: quality 5, lgwin 22 (window size) const BROTLI_QUALITY: u32 = 5; - const BROTLI_LGWIN: u32 = 22; + const BROTLI_LGWIN: u32 = 22; const BUFFER_SIZE: usize = 4096; // 4KB buffer - let mut compressor = CompressorReader::new( - &encoded[..], - BUFFER_SIZE, - BROTLI_QUALITY, - BROTLI_LGWIN - ); + let mut compressor = + CompressorReader::new(&encoded[..], BUFFER_SIZE, BROTLI_QUALITY, BROTLI_LGWIN); compressor.read_to_end(&mut compressed)?; Ok(compressed) @@ -56,7 +53,7 @@ pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized { fn load_from_bytes(data: &[u8]) -> SledDBResult { let mut decompressed = Vec::new(); const BUFFER_SIZE: usize = 4096; // 4KB buffer - + let mut decompressor = Decompressor::new(data, BUFFER_SIZE); decompressor.read_to_end(&mut decompressed)?; @@ -140,8 +137,8 @@ impl SledDB { Ok(models) } - /// Provides access to the underlying Sled Db instance for advanced operations. - pub fn raw_db(&self) -> &sled::Db { + /// Provides access to the underlying Sled Db instance for advanced operations. + pub fn raw_db(&self) -> &sled::Db { &self.db } } diff --git a/herodb/src/db/db.rs b/herodb/src/db/db.rs index fa124ba..e7bc94b 100644 --- a/herodb/src/db/db.rs +++ b/herodb/src/db/db.rs @@ -1,10 +1,11 @@ use crate::db::base::*; +use bincode; +use rhai::{CustomType, EvalAltResult, TypeBuilder}; use std::any::TypeId; use std::collections::HashMap; +use std::fmt::Debug; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, RwLock}; -use std::fmt::Debug; -use bincode; /// Represents a single database operation in a transaction #[derive(Debug, Clone)] @@ -24,7 +25,7 @@ pub trait AnyDbOperations: Send + Sync { fn delete(&self, id: &str) -> SledDBResult<()>; fn get_any(&self, id: &str) -> SledDBResult>; fn list_any(&self) -> SledDBResult>; - fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>; + fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>; fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()>; } @@ -33,17 +34,17 @@ impl AnyDbOperations for SledDB { fn delete(&self, id: &str) -> SledDBResult<()> { self.delete(id) } - + fn get_any(&self, id: &str) -> SledDBResult> { let result = self.get(id)?; Ok(Box::new(result)) } - + fn list_any(&self) -> SledDBResult> { let result = self.list()?; Ok(Box::new(result)) } - + fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()> { // Downcast to the specific type T match model.downcast_ref::() { @@ -51,7 +52,7 @@ impl AnyDbOperations for SledDB { None => Err(SledDBError::TypeError), } } - + fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()> { // Deserialize the bytes into model of type T let model: T = bincode::deserialize(serialized)?; @@ -77,23 +78,25 @@ impl TransactionState { } /// Main DB manager that automatically handles all root models +#[derive(Clone, CustomType)] pub struct DB { db_path: PathBuf, - + // Type map for generic operations - type_map: HashMap>, - + type_map: HashMap>, + // Locks to ensure thread safety for key areas _write_locks: Arc>>, - + // Transaction state - transaction: RwLock>, + transaction: Arc>>, } /// Builder for DB that allows registering models +#[derive(Clone, CustomType)] pub struct DBBuilder { base_path: PathBuf, - model_registrations: Vec>, + model_registrations: Vec>, } /// Trait for model registration @@ -129,33 +132,45 @@ impl DBBuilder { model_registrations: Vec::new(), } } - + + pub fn with_path>(base_path: P) -> Self { + Self { + base_path: base_path.into(), + model_registrations: Vec::new(), + } + } + /// Register a model type with the DB pub fn register_model(mut self) -> Self { - self.model_registrations.push(Box::new(SledModelRegistration::::new())); + self.model_registrations + .push(Arc::new(SledModelRegistration::::new())); self } - + /// Build the DB with the registered models - pub fn build(self) -> SledDBResult { + pub fn build(self) -> Result> { let base_path = self.base_path; - + // Ensure base directory exists if !base_path.exists() { - std::fs::create_dir_all(&base_path)?; + std::fs::create_dir_all(&base_path).map_err(|e| { + EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e)) + })?; } - + // Register all models - let mut type_map: HashMap> = HashMap::new(); - + let mut type_map: HashMap> = HashMap::new(); + for registration in self.model_registrations { - let (type_id, db) = registration.register(&base_path)?; - type_map.insert(type_id, db); + let (type_id, db) = registration.register(&base_path).map_err(|e| { + EvalAltResult::ErrorSystem("Could not register type".to_string(), Box::new(e)) + })?; + type_map.insert(type_id, db.into()); } - + let _write_locks = Arc::new(Mutex::new(HashMap::new())); - let transaction = RwLock::new(None); - + let transaction = Arc::new(RwLock::new(None)); + Ok(DB { db_path: base_path, type_map, @@ -169,15 +184,15 @@ impl DB { /// Create a new empty DB instance without any models pub fn new>(base_path: P) -> SledDBResult { let base_path = base_path.into(); - + // Ensure base directory exists if !base_path.exists() { std::fs::create_dir_all(&base_path)?; } - + let _write_locks = Arc::new(Mutex::new(HashMap::new())); - let transaction = RwLock::new(None); - + let transaction = Arc::new(RwLock::new(None)); + Ok(Self { db_path: base_path, type_map: HashMap::new(), @@ -185,25 +200,27 @@ impl DB { transaction, }) } - + // Transaction-related methods - + /// Begin a new transaction pub fn begin_transaction(&self) -> SledDBResult<()> { let mut tx = self.transaction.write().unwrap(); if tx.is_some() { - return Err(SledDBError::GeneralError("Transaction already in progress".into())); + return Err(SledDBError::GeneralError( + "Transaction already in progress".into(), + )); } *tx = Some(TransactionState::new()); Ok(()) } - + /// Check if a transaction is active pub fn has_active_transaction(&self) -> bool { let tx = self.transaction.read().unwrap(); tx.is_some() && tx.as_ref().unwrap().active } - + /// Apply a set operation with the serialized data - bypass transaction check fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> SledDBResult<()> { // Get the database operations for this model type @@ -211,39 +228,47 @@ impl DB { // Just pass the raw serialized data to a special raw insert method return db_ops.insert_any_raw(serialized); } - - Err(SledDBError::GeneralError(format!("No DB registered for type ID {:?}", model_type))) + + Err(SledDBError::GeneralError(format!( + "No DB registered for type ID {:?}", + model_type + ))) } /// Commit the current transaction, applying all operations pub fn commit_transaction(&self) -> SledDBResult<()> { let mut tx_guard = self.transaction.write().unwrap(); - + if let Some(tx_state) = tx_guard.take() { if !tx_state.active { return Err(SledDBError::GeneralError("Transaction not active".into())); } - + // Execute all operations in the transaction for op in tx_state.operations { match op { - DbOperation::Set { model_type, serialized } => { + DbOperation::Set { + model_type, + serialized, + } => { self.apply_set_operation(model_type, &serialized)?; - }, + } DbOperation::Delete { model_type, id } => { - let db_ops = self.type_map.get(&model_type) + let db_ops = self + .type_map + .get(&model_type) .ok_or_else(|| SledDBError::TypeError)?; db_ops.delete(&id)?; } } } - + Ok(()) } else { Err(SledDBError::GeneralError("No active transaction".into())) } } - + /// Rollback the current transaction, discarding all operations pub fn rollback_transaction(&self) -> SledDBResult<()> { let mut tx = self.transaction.write().unwrap(); @@ -253,79 +278,85 @@ impl DB { *tx = None; Ok(()) } - + /// Get the path to the database pub fn path(&self) -> &PathBuf { &self.db_path } - + // Generic methods that work with any supported model type - + /// Insert a model instance into its appropriate database based on type pub fn set(&self, model: &T) -> SledDBResult<()> { // Try to acquire a write lock on the transaction let mut tx_guard = self.transaction.write().unwrap(); - + // Check if there's an active transaction if let Some(tx_state) = tx_guard.as_mut() { if tx_state.active { // Serialize the model for later use let serialized = bincode::serialize(model)?; - + // Record a Set operation in the transaction tx_state.operations.push(DbOperation::Set { model_type: TypeId::of::(), serialized, }); - + return Ok(()); } } - + // If we got here, either there's no transaction or it's not active // Drop the write lock before doing a direct database operation drop(tx_guard); - + // Execute directly match self.type_map.get(&TypeId::of::()) { Some(db_ops) => db_ops.insert_any(model), None => Err(SledDBError::TypeError), } } - + /// Check the transaction state for the given type and id fn check_transaction(&self, id: &str) -> Option, SledDBError>> { // Try to acquire a read lock on the transaction let tx_guard = self.transaction.read().unwrap(); - + if let Some(tx_state) = tx_guard.as_ref() { if !tx_state.active { return None; } - + let type_id = TypeId::of::(); let id_str = id.to_string(); - + // Process operations in reverse order (last operation wins) for op in tx_state.operations.iter().rev() { match op { // First check if this ID has been deleted in the transaction - DbOperation::Delete { model_type, id: op_id } => { + DbOperation::Delete { + model_type, + id: op_id, + } => { if *model_type == type_id && op_id == id { // Return NotFound error for deleted records return Some(Err(SledDBError::NotFound(id.to_string()))); } - }, + } // Then check if it has been set in the transaction - DbOperation::Set { model_type, serialized } => { + DbOperation::Set { + model_type, + serialized, + } => { if *model_type == type_id { // Try to deserialize and check the ID match bincode::deserialize::(serialized) { Ok(model) => { - if model.get_id() == id_str { - return Some(Ok(Some(model))); + if model.get_id() == id_str { + return Some(Ok(Some(model))); + } } - }, Err(_) => continue, // Skip if deserialization fails } } @@ -333,7 +364,7 @@ impl DB { } } } - + // Not found in transaction (continue to database) None } @@ -348,7 +379,7 @@ impl DB { Ok(None) => {} // Should never happen } } - + // If no pending value, look up from the database match self.type_map.get(&TypeId::of::()) { Some(db_ops) => { @@ -358,16 +389,16 @@ impl DB { Ok(t) => Ok(*t), Err(_) => Err(SledDBError::TypeError), } - }, + } None => Err(SledDBError::TypeError), } } - + /// Delete a model instance by its ID and type pub fn delete(&self, id: &str) -> SledDBResult<()> { // Try to acquire a write lock on the transaction let mut tx_guard = self.transaction.write().unwrap(); - + // Check if there's an active transaction if let Some(tx_state) = tx_guard.as_mut() { if tx_state.active { @@ -376,22 +407,22 @@ impl DB { model_type: TypeId::of::(), id: id.to_string(), }); - + return Ok(()); } } - + // If we got here, either there's no transaction or it's not active // Drop the write lock before doing a direct database operation drop(tx_guard); - + // Execute directly match self.type_map.get(&TypeId::of::()) { Some(db_ops) => db_ops.delete(id), None => Err(SledDBError::TypeError), } } - + /// List all model instances of a specific type pub fn list(&self) -> SledDBResult> { // Look up the correct DB operations for type T in our type map @@ -403,24 +434,27 @@ impl DB { Ok(vec_t) => Ok(*vec_t), Err(_) => Err(SledDBError::TypeError), } - }, + } None => Err(SledDBError::TypeError), } } - + // Register a model type with this DB instance pub fn register(&mut self) -> SledDBResult<()> { let db_path = self.db_path.join(T::db_prefix()); let db: SledDB = SledDB::open(db_path)?; - self.type_map.insert(TypeId::of::(), Box::new(db)); + self.type_map.insert(TypeId::of::(), Arc::new(db)); Ok(()) } - + // Get a typed handle to a registered model DB pub fn db_for(&self) -> SledDBResult<&dyn AnyDbOperations> { match self.type_map.get(&TypeId::of::()) { Some(db) => Ok(&**db), - None => Err(SledDBError::GeneralError(format!("No DB registered for type {}", std::any::type_name::()))), + None => Err(SledDBError::GeneralError(format!( + "No DB registered for type {}", + std::any::type_name::() + ))), } } } diff --git a/herodb/src/db/macros.rs b/herodb/src/db/macros.rs index e77a289..aa784a3 100644 --- a/herodb/src/db/macros.rs +++ b/herodb/src/db/macros.rs @@ -5,25 +5,27 @@ macro_rules! impl_model_methods { impl DB { paste::paste! { /// Insert a model instance into the database - pub fn [](&self, item: &$model) -> SledDBResult<()> { - self.set(item) + pub fn [](&mut self, item: $model) -> Result<(), Box> { + Ok(self.set(&item).map_err(|e| { + rhai::EvalAltResult::ErrorSystem("could not insert $singular".to_string(), Box::new(e)) + })?) } /// Get a model instance by its ID - pub fn [](&self, id: u32) -> SledDBResult<$model> { + pub fn [](&mut self, id: i64) -> SledDBResult<$model> { self.get::<$model>(&id.to_string()) } /// Delete a model instance by its ID - pub fn [](&self, id: u32) -> SledDBResult<()> { + pub fn [](&mut self, id: i64) -> SledDBResult<()> { self.delete::<$model>(&id.to_string()) } /// List all model instances - pub fn [](&self) -> SledDBResult> { + pub fn [](&mut self) -> SledDBResult> { self.list::<$model>() } } } }; -} \ No newline at end of file +} diff --git a/herodb/src/models/biz/currency.rs b/herodb/src/models/biz/currency.rs index 28464a0..ab74617 100644 --- a/herodb/src/models/biz/currency.rs +++ b/herodb/src/models/biz/currency.rs @@ -1,9 +1,10 @@ -use chrono::{DateTime, Utc, Duration}; -use serde::{Deserialize, Serialize}; -use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module +use crate::db::base::{SledModel, Storable}; +use chrono::{DateTime, Duration, Utc}; +use rhai::{CustomType, EvalAltResult, TypeBuilder}; +use serde::{Deserialize, Serialize}; // Import Sled traits from db module /// Currency represents a monetary value with amount and currency code -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct Currency { pub amount: f64, pub currency_code: String, @@ -17,9 +18,14 @@ impl Currency { currency_code, } } + + pub fn amount(&mut self) -> f64 { + self.amount + } } /// Builder for Currency +#[derive(Clone, CustomType)] pub struct CurrencyBuilder { amount: Option, currency_code: Option, @@ -47,7 +53,7 @@ impl CurrencyBuilder { } /// Build the Currency object - pub fn build(self) -> Result { + pub fn build(self) -> Result> { Ok(Currency { amount: self.amount.ok_or("amount is required")?, currency_code: self.currency_code.ok_or("currency_code is required")?, diff --git a/herodb/src/models/biz/product.rs b/herodb/src/models/biz/product.rs index 678f601..c19ed83 100644 --- a/herodb/src/models/biz/product.rs +++ b/herodb/src/models/biz/product.rs @@ -1,7 +1,7 @@ -use chrono::{DateTime, Utc, Duration}; -use serde::{Deserialize, Serialize}; -use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module - +use crate::db::base::{SledModel, Storable}; +use chrono::{DateTime, Duration, Utc}; +use rhai::{CustomType, EvalAltResult, TypeBuilder, export_module}; +use serde::{Deserialize, Serialize}; // Import Sled traits from db module /// ProductType represents the type of a product #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -20,17 +20,17 @@ pub enum ProductStatus { /// ProductComponent represents a component of a product #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProductComponent { - pub id: u32, + pub id: i64, pub name: String, pub description: String, - pub quantity: i32, + pub quantity: i64, pub created_at: DateTime, pub updated_at: DateTime, } impl ProductComponent { /// Create a new product component with default timestamps - pub fn new(id: u32, name: String, description: String, quantity: i32) -> Self { + pub fn new(id: i64, name: String, description: String, quantity: i64) -> Self { let now = Utc::now(); Self { id, @@ -44,11 +44,12 @@ impl ProductComponent { } /// Builder for ProductComponent +#[derive(Clone, CustomType)] pub struct ProductComponentBuilder { - id: Option, + id: Option, name: Option, description: Option, - quantity: Option, + quantity: Option, created_at: Option>, updated_at: Option>, } @@ -67,7 +68,7 @@ impl ProductComponentBuilder { } /// Set the id - pub fn id(mut self, id: u32) -> Self { + pub fn id(mut self, id: i64) -> Self { self.id = Some(id); self } @@ -85,7 +86,7 @@ impl ProductComponentBuilder { } /// Set the quantity - pub fn quantity(mut self, quantity: i32) -> Self { + pub fn quantity(mut self, quantity: i64) -> Self { self.quantity = Some(quantity); self } @@ -103,7 +104,7 @@ impl ProductComponentBuilder { } /// Build the ProductComponent object - pub fn build(self) -> Result { + pub fn build(self) -> Result> { let now = Utc::now(); Ok(ProductComponent { id: self.id.ok_or("id is required")?, @@ -117,9 +118,9 @@ impl ProductComponentBuilder { } /// Product represents a product or service offered by the Freezone -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct Product { - pub id: u32, + pub id: i64, pub name: String, pub description: String, pub price: Currency, @@ -128,7 +129,7 @@ pub struct Product { pub status: ProductStatus, pub created_at: DateTime, pub updated_at: DateTime, - pub max_amount: u16, // means allows us to define how many max of this there are + pub max_amount: i64, // means allows us to define how many max of this there are pub purchase_till: DateTime, pub active_till: DateTime, // after this product no longer active if e.g. a service pub components: Vec, @@ -139,14 +140,14 @@ pub struct Product { impl Product { /// Create a new product with default timestamps pub fn new( - id: u32, + id: i64, name: String, description: String, price: Currency, type_: ProductType, category: String, status: ProductStatus, - max_amount: u16, + max_amount: i64, validity_days: i64, // How many days the product is valid after purchase ) -> Self { let now = Utc::now(); @@ -167,30 +168,30 @@ impl Product { components: Vec::new(), } } - + /// Add a component to this product pub fn add_component(&mut self, component: ProductComponent) { self.components.push(component); self.updated_at = Utc::now(); } - + /// Update the purchase availability timeframe pub fn set_purchase_period(&mut self, purchase_till: DateTime) { self.purchase_till = purchase_till; self.updated_at = Utc::now(); } - + /// Update the active timeframe pub fn set_active_period(&mut self, active_till: DateTime) { self.active_till = active_till; self.updated_at = Utc::now(); } - + /// Check if the product is available for purchase pub fn is_purchasable(&self) -> bool { self.status == ProductStatus::Available && Utc::now() <= self.purchase_till } - + /// Check if the product is still active (for services) pub fn is_active(&self) -> bool { Utc::now() <= self.active_till @@ -198,8 +199,9 @@ impl Product { } /// Builder for Product +#[derive(Clone, CustomType)] pub struct ProductBuilder { - id: Option, + id: Option, name: Option, description: Option, price: Option, @@ -208,7 +210,7 @@ pub struct ProductBuilder { status: Option, created_at: Option>, updated_at: Option>, - max_amount: Option, + max_amount: Option, purchase_till: Option>, active_till: Option>, components: Vec, @@ -237,7 +239,7 @@ impl ProductBuilder { } /// Set the id - pub fn id(mut self, id: u32) -> Self { + pub fn id(mut self, id: i64) -> Self { self.id = Some(id); self } @@ -279,7 +281,7 @@ impl ProductBuilder { } /// Set the max amount - pub fn max_amount(mut self, max_amount: u16) -> Self { + pub fn max_amount(mut self, max_amount: i64) -> Self { self.max_amount = Some(max_amount); self } @@ -313,13 +315,15 @@ impl ProductBuilder { let now = Utc::now(); let created_at = self.created_at.unwrap_or(now); let updated_at = self.updated_at.unwrap_or(now); - + // Calculate purchase_till and active_till based on validity_days if not set directly let purchase_till = self.purchase_till.unwrap_or(now + Duration::days(365)); let active_till = if let Some(validity_days) = self.validity_days { - self.active_till.unwrap_or(now + Duration::days(validity_days)) + self.active_till + .unwrap_or(now + Duration::days(validity_days)) } else { - self.active_till.ok_or("Either active_till or validity_days must be provided")? + self.active_till + .ok_or("Either active_till or validity_days must be provided")? }; Ok(Product { diff --git a/herodb/src/models/biz/sale.rs b/herodb/src/models/biz/sale.rs index bcfede1..dda10f7 100644 --- a/herodb/src/models/biz/sale.rs +++ b/herodb/src/models/biz/sale.rs @@ -1,7 +1,8 @@ -use crate::models::biz::Currency; // Use crate:: for importing from the module -use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module +use crate::db::base::{SledModel, Storable}; +use crate::models::biz::Currency; // Use crate:: for importing from the module // Import Sled traits from db module // use super::db::Model; // Removed old Model trait import use chrono::{DateTime, Utc}; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; // use std::collections::HashMap; // Removed unused import @@ -43,7 +44,7 @@ impl SaleItem { amount, currency_code: unit_price.currency_code.clone(), }; - + Self { id, sale_id, @@ -58,6 +59,7 @@ impl SaleItem { } /// Builder for SaleItem +#[derive(Clone, CustomType)] pub struct SaleItemBuilder { id: Option, sale_id: Option, @@ -130,7 +132,7 @@ impl SaleItemBuilder { pub fn build(self) -> Result { let unit_price = self.unit_price.ok_or("unit_price is required")?; let quantity = self.quantity.ok_or("quantity is required")?; - + // Calculate subtotal let amount = unit_price.amount * quantity as f64; let subtotal = Currency { @@ -152,7 +154,7 @@ impl SaleItemBuilder { } /// Sale represents a sale of products or services -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct Sale { pub id: u32, pub company_id: u32, @@ -184,7 +186,10 @@ impl Sale { company_id, buyer_name, buyer_email, - total_amount: Currency { amount: 0.0, currency_code }, + total_amount: Currency { + amount: 0.0, + currency_code, + }, status, sale_date: now, created_at: now, @@ -192,12 +197,12 @@ impl Sale { items: Vec::new(), } } - + /// Add an item to the sale and update the total amount pub fn add_item(&mut self, item: SaleItem) { // Make sure the item's sale_id matches this sale assert_eq!(self.id, item.sale_id, "Item sale_id must match sale id"); - + // Update the total amount if self.items.is_empty() { // First item, initialize the total amount with the same currency @@ -210,14 +215,14 @@ impl Sale { // (Assumes all items have the same currency) self.total_amount.amount += item.subtotal.amount; } - + // Add the item to the list self.items.push(item); - + // Update the sale timestamp self.updated_at = Utc::now(); } - + /// Update the status of the sale pub fn update_status(&mut self, status: SaleStatus) { self.status = status; @@ -226,6 +231,7 @@ impl Sale { } /// Builder for Sale +#[derive(Clone, CustomType)] pub struct SaleBuilder { id: Option, company_id: Option, @@ -311,20 +317,20 @@ impl SaleBuilder { let now = Utc::now(); let id = self.id.ok_or("id is required")?; let currency_code = self.currency_code.ok_or("currency_code is required")?; - + // Initialize with empty total amount let mut total_amount = Currency { amount: 0.0, currency_code: currency_code.clone(), }; - + // Calculate total amount from items for item in &self.items { // Make sure the item's sale_id matches this sale if item.sale_id != id { return Err("Item sale_id must match sale id"); } - + if total_amount.amount == 0.0 { // First item, initialize the total amount with the same currency total_amount = Currency { From 9c7baa3b4e3a3382f196ce5bf4aa8569b467f689 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 19 Apr 2025 08:39:40 +0200 Subject: [PATCH 02/10] ... --- herodb/src/models/py/README.md | 131 +++++ herodb/src/models/py/__init__.py | 3 + .../models/py/__pycache__/api.cpython-312.pyc | Bin 0 -> 19051 bytes .../py/__pycache__/models.cpython-312.pyc | Bin 0 -> 13464 bytes herodb/src/models/py/api.py | 455 ++++++++++++++++++ herodb/src/models/py/business.db | Bin 0 -> 28672 bytes herodb/src/models/py/example.py | 190 ++++++++ herodb/src/models/py/install_and_run.sh | 49 ++ herodb/src/models/py/models.py | 297 ++++++++++++ herodb/src/models/py/server.sh | 42 ++ 10 files changed, 1167 insertions(+) create mode 100644 herodb/src/models/py/README.md create mode 100644 herodb/src/models/py/__init__.py create mode 100644 herodb/src/models/py/__pycache__/api.cpython-312.pyc create mode 100644 herodb/src/models/py/__pycache__/models.cpython-312.pyc create mode 100755 herodb/src/models/py/api.py create mode 100644 herodb/src/models/py/business.db create mode 100755 herodb/src/models/py/example.py create mode 100755 herodb/src/models/py/install_and_run.sh create mode 100644 herodb/src/models/py/models.py create mode 100755 herodb/src/models/py/server.sh diff --git a/herodb/src/models/py/README.md b/herodb/src/models/py/README.md new file mode 100644 index 0000000..07f9a9c --- /dev/null +++ b/herodb/src/models/py/README.md @@ -0,0 +1,131 @@ +# Business Models Python Port + +This directory contains a Python port of the business models from the Rust codebase, using SQLModel for database integration. + +## Overview + +This project includes: + +1. Python port of Rust business models using SQLModel +2. FastAPI server with OpenAPI/Swagger documentation +3. CRUD operations for all models +4. Convenience endpoints for common operations + +The models ported from Rust to Python include: + +- **Currency**: Represents a monetary value with amount and currency code +- **Customer**: Represents a customer who can purchase products or services +- **Product**: Represents a product or service offered +- **ProductComponent**: Represents a component of a product +- **SaleItem**: Represents an item in a sale +- **Sale**: Represents a sale of products or services + +## Structure + +- `models.py`: Contains the SQLModel definitions for all business models +- `example.py`: Demonstrates how to use the models with a sample application +- `install_and_run.sh`: Bash script to install dependencies using `uv` and run the example +- `api.py`: FastAPI server providing CRUD operations for all models +- `server.sh`: Bash script to start the FastAPI server + +## Requirements + +- Python 3.7+ +- [uv](https://github.com/astral-sh/uv) for dependency management + +## Installation + +The project uses `uv` for dependency management. To install dependencies and run the example: + +```bash +./install_and_run.sh +``` + +## API Server + +The project includes a FastAPI server that provides CRUD operations for all models and some convenience endpoints. + +### Starting the Server + +To start the API server: + +```bash +./server.sh +``` + +This script will: +1. Create a virtual environment if it doesn't exist +2. Install the required dependencies using `uv` +3. Start the FastAPI server with hot reloading enabled + +### API Documentation + +Once the server is running, you can access the OpenAPI documentation at: + +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +### Available Endpoints + +The API provides the following endpoints: + +#### Currencies +- `GET /currencies/`: List all currencies +- `POST /currencies/`: Create a new currency +- `GET /currencies/{currency_id}`: Get a specific currency +- `PUT /currencies/{currency_id}`: Update a currency +- `DELETE /currencies/{currency_id}`: Delete a currency + +#### Customers +- `GET /customers/`: List all customers +- `POST /customers/`: Create a new customer +- `GET /customers/{customer_id}`: Get a specific customer +- `PUT /customers/{customer_id}`: Update a customer +- `DELETE /customers/{customer_id}`: Delete a customer +- `GET /customers/{customer_id}/sales/`: Get all sales for a customer + +#### Products +- `GET /products/`: List all products +- `POST /products/`: Create a new product +- `GET /products/{product_id}`: Get a specific product +- `PUT /products/{product_id}`: Update a product +- `DELETE /products/{product_id}`: Delete a product +- `GET /products/available/`: Get all available products +- `POST /products/{product_id}/components/`: Add a component to a product +- `GET /products/{product_id}/components/`: Get all components for a product + +#### Sales +- `GET /sales/`: List all sales +- `POST /sales/`: Create a new sale +- `GET /sales/{sale_id}`: Get a specific sale +- `PUT /sales/{sale_id}`: Update a sale +- `DELETE /sales/{sale_id}`: Delete a sale +- `PUT /sales/{sale_id}/status/{status}`: Update the status of a sale +- `POST /sales/{sale_id}/items/`: Add an item to a sale +- `GET /sales/{sale_id}/items/`: Get all items for a sale + +## Dependencies + +- SQLModel: For database models and ORM functionality +- Pydantic: For data validation (used by SQLModel) +- FastAPI: For creating the API server +- Uvicorn: ASGI server for running FastAPI applications + +## Example Usage + +The `example.py` script demonstrates: + +1. Creating an SQLite database +2. Defining and creating tables for the models +3. Creating sample data (customers, products, sales) +4. Performing operations on the data +5. Querying and displaying the data + +To run the example manually (after activating the virtual environment): + +```bash +# From the py directory +python example.py + +# Or from the parent directory +cd py && python example.py \ No newline at end of file diff --git a/herodb/src/models/py/__init__.py b/herodb/src/models/py/__init__.py new file mode 100644 index 0000000..b4c11ea --- /dev/null +++ b/herodb/src/models/py/__init__.py @@ -0,0 +1,3 @@ +""" +Python port of the business models from Rust. +""" \ No newline at end of file diff --git a/herodb/src/models/py/__pycache__/api.cpython-312.pyc b/herodb/src/models/py/__pycache__/api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4dc565371c93f9b620d7f448380d2a6126f3ed73 GIT binary patch literal 19051 zcmeG@TW}QDmEAKv56xSz(E~k(hk)UsL0IA;@e-1dFhXDpj6Hy5p>Dy5c_`f@u!Km2 z?G%t?7w~$6oMOxJZc;0ky<5tk@~7P|s`h6lqj-j1rvjC&+Rfhp+462`KlYs4&lyG! zSIA~lm4>c|<5o`P0Al(J)3>4?#>R9_14)s|*=N>tv^w#Q;4y~=Uxcbj& zsZCP=$;vue7wcv{Y%yE%Md{~e>0NLgw{E0c`9`Zb2U~WLX3I$ltuMTvn-pkl#RpVy z1AJF#29?UFY}E(U=Q6-x1N1(wL04LAHCM{60q8ZK8>MG}sfF91z!;|4TIf;7xrQ0G z{sWT=Z_^T^S91(kS_PnTp#tZ9d613lT5dDD4t@yT%&yl#_b&l`!;088$nY3;qYmB{ zfOkfjvsSaKgSSbhfSKK_gSic0p4DI}Ep`K$xnGf(yjjz%hT#?+v_^ndh(BAoGIrat z{MimYB>pt&;02bT;8x&I8R1V3)K%~&po6|+3Ftdk#I{N0&rTh@CV*FnKdXcHicA4B z+pL4R6JQqN&kjP*rTL>_*rJ2>3P3BwpIuxzyL(yw?13HO0Id)YJ2@}gwJZ-2-Vx{}S*mUw%(f+{ z*a>{}63VX(S(1((9n^hGV0x6RVqaYr(__#>g4e5qw|@yNj{=re1j`(#inQg)nn{&4 zl(04AyC!IDRwc~F9@mX}V2M%tR-DBNRl-i{;2i{bg(Pfs@LrQCV`g91!8`;o3rScX zq4UxbreWBxgVqkv3Q5>0u9`i)EZ-2`8_-LVuroTChnJw@Dd1f-q5R5_C5d=b2eo4f zTD}ET3a*H08@EQr^sEkM=MtEnQ*BxfWY(T1Uj{bUUNP)>9rUgxpuerka4z(GOKD)y zvTKqnQF=f(!V#{UedqFKisGw50}SDwU=Lu}GxDLbwre%FjvZt#d@-bWO*A!b7&rLY ziG#KygF?Lhc zo$Pvdh$Edu(`#cKKMrtQ9VcGzKOSP)Fn4v3=kVKp7!Ux7*8bOekK?$abBO0)xB)JD z5zrCM0vG0n;{GDh+A+rSTy$t0TS7b*;ds$=tLvhg~H#%aG(O8s=#zm+6q(43w z9}`5oe0yqqloOf$!7vA7LU}aKMMN969ETOE0i7o*kk|!iIbmvQ+|jsW!W3X*L&AhPz;n=;a0Y+{(ZSJB ze?B3vf;%5q>)`#)ris@;PbRWeDVk-f{|P>KqJ*V^;@a zdj^>$fSM?Qz6!zDTL1~4ylZ@mx=R0p@ha`7 z`}_vp4x@->iSs~Wiy%pdfJ6Fz*Bdwuf-D4pI-?=(;^n{)2u$E&DBc(wOk`U;Gm1(+kyS6u%w8z>ng=AW$O1 z$6|3l0L@nT2?(E{{Mb>RsAx{uTM|sme+vvee$Ql~CmewxNpV7)5D;y$7Qqc@vx^5> zf&t(L2z^8*K6p_Ot?~!s{3ZZ0q_P=qjPmdK2sG|flk|ux4)8#}CRKr;u?K#WcPX(O zc`wJ3`Yt&TsD8>%Pn35MyWku2MY*d!1;fvbqH&OAMKh2u5{io!o*RZaT>@cx1ptx> z2siXMHZKYQelNT{1i$B7p_rnwX3FHadF;lqyVgafD#O@s_TA`PWXiC0;>L-) zJ&R0j#$Gygf^UTWzhR*e8R2qhR5XP{fWeS{wm37qfBqvVfLew^)QYQL#F1y@QWR>$ zqCC-RjW#1i+Eb-9Nh{wf^$2QJ-lfq0x^dD-s9Vy_#eHBY6vCo-n8C)q%MJ0EQQ_TC zi00Vvu)xLn{di-9?tX^fhi~x#p|xm}2@3#-2>RfXDG&T(2ej9G2YhrCegc93%G;=t zs{1wfY90*#_{~&ZW4f*-S=Tbhrs~?#b?wQz_JxL2T~Dg`Xu9}#viNwa_{7w!8GCtx zDJQI$sM5>%HHE(eA@;gx*WKlaGZLII=p;0j5@*fM0qJFBxNev<2+p`Fx~SS;7?rp0 z(US%sD>G3>$f^)^+;?HzceL|2AA!#NuB1a=&V12W92SkSDEpZ~vmiVx|2E&R`DE+ecg2399I=!(l}|FG;5C156T!+N+--D z@0v6!V}Xe>jG*-AUCLPGeR1APxXpopN@kuYK0S(_ly6WK2>uB4SZ-hp%rf{r{4sos2oTyM}#{^DE1Y9oYG8m8Z{4RXI8w<=X`Yn=xoWKWY z!-In#Cabp2Z%9;aO;sICFy4%JbK2XO^frF!-ErG?%XsVbS6263>-75Rv0qplvPP<+ z>6;m1Kh5t1lnN{N58=&ARqlcUI%&{}485Fqf^)>MCiDbQC_2HxT}~+YZGiqnt!gg7 zNrwKcPZ%2-0wX&-77ma5P5EL1wgbCKGSLeVa&3fLdaZHxJamb{?>P$Ns;tCgX>Gi_ zXOXGVt&`}4>`K^oCz#!mUABW=mfUr4KZ+FBop>42;~b`;MIlz@kM3Yd$nmhhK!G>$ z4pc;ymhdLz-WQVUWs<~I>Ld;v0cjM)5m4`P#EyZ{iP-(29RX>0$q^vd z9~}Ytq3Q^b#lUZrbefP_UiOCpP066YgXBVtRb#Cz`w}o7_2)0)$M<2;2n8sknNB&JHmRq-F%%afkKK2h{fx=K&`u}8B%7PSES4!!vZ@HCH* zZJCq@9EC0@Ed1M8AR#Agfl(<51({IJa*a&1@RE%K-sd$RO%c6IZ*CKx4>R9)hF2=>kxRp7i0W5Wb~wroDC9* zTx2v1&xgzaQn}PM@(PJ7mdiI?H%}V)b(7{1{3h?xqP7t|bh+200h6nW!v=7StAK+> zIPaP?=D}fZtc;H>4+z{&GI?-J1)~ykp-4oR5GWLfyqFLueQ|un0A9CDT1Y}*v$Fon z>=%4jL-9+HGnVqczTr?9yi3R>`)!g(fJ2L>tB`)<_%UenSFpH>1#ue=VM}usth&TO zUU!s4(ey1$8jDtCcmatIYaO|y=tNrZlny+)UH=3>;X^2eR zrZHL5IJYHPvoGb@53k+cJCnC3r@KDs{kV6wELGE(u4#sE=T4_;+EVU)Y4@R|`%ub# zc&bN>rzgCcvA8BPc9EcB%<+C^Y)B z_W~r;U5fI4gH(h^O+_fIAGi&I=KR_YjII4`Q*NZ!7vPE;;9*;DvF) zui+=$go30TRuUP=98@)=EB(nz|2&hZ^rtHKLy|D#-IVrjPkOh1=?!Qp2g`KnH1`YZ z2245Z&{7VoS<(vY>HljF%G0Uj8Wig0a$RzuYEH_UznuT1iG=1qO(C0dViK2}W>AJh z9)69zx}hLG#7fLmvJATynKe2m1G5Y*3Hz=DvrF>A))h9auty|s2nvCT5@llQE}lbf(3GJrH97o--K&EXS`wky50I!EIRr7zV*A0S|!iJ7QrXIUR) z9u<1vUeh3M3FH1Li9~r-`yqdL3qJ8KJE2FNE*Vf*OIaB6rDg;Tg`7-6cc0=& zsZ*D>+n1z0lSg~lQc;K|@KbrT3NURZtUw-HR zx`{+$rr$dfGm%JaB{(ZbVkX!jDquJ#3&&L|p9{uUsxejK#St`&d6#06A(k|g2&zF* zeB|QN;ZuE6H0qHex2oOa3Ex zxdSCifCfZLB^W6+f4>(aB{drA{N&ij$7XjWYnoD?ofs#TD4|fN97EZY5a@XJK`7)X zz0-TUce?zOnvZK{*i_ZlbX8Nbs%fqvRkbJO*qe6jPdfIe90#YmGIq~YuVh<1vTeDB zNRc4OBDV?~n3I6)%eC1=p*>OrghT7YA6GvXD~=v+01b2^ab zM#%if@Uja@t_D6pv$or9AH}E7r0fk-osaBpIq>ORbaf@HKq*Bh#UetNKSw=4L*1n) z9GGT9A%!fk0qazw9>Nsj{F1b255PT@ldn~Iykf{hB;ZE?KobMerE+Y=c*icsI|QS@ z0Wf_iChtRotl2Ax3B)Yk`ulI+dwXuf-M3TayQlgx-VJH*mZW#fm)>n!pu^l9nr``p zbu9)u+qFPvRciw5qpKGKEp%U>{}76os*W#nQ`M;kIt3UlMLAXdsNlxKsK&rGevKPA3t&P}3Xe6urQeLKhR+JeF&hCZU{~Q2NGgNB!6A+R`#8q9{NhaPK%r(q*$q( z#7a>gl2eM43W$;4D*1Stm=H^l>*$GAIGr|taA13syq^Kd&@4q>xa83}e&j3u@1T1O zB?>dCtFj1>ORJ|kKj=kIZ+Wi|tw7Pu<2R1q-LS}16z>06U4;rS$}C{@Qmw$!fmEg9 zPa!px7^)Co3gQST(Kf?mMlwTh%jYz8ADq+Uy8i zlWI7M))9+d;i4fp$jlYi)GsBHzC2Ae7CKI)ofCX1db9E&Dm((kOstVtU_9k2k;E4p zhCDjt-z6xr^=dBO$ZJw1YK z&bzb|RU;L7ui#c0O+CX}tr!TEvPybO@H@0kDqWO?8)0Hm`y1^?d)p89c8Si>F@EUM zARLE|hr(eV(G$&M@u6t!s%|H%9Ug-r@G(_EgZ-r+fpg_y0m*^(!DX|-kh@qRln5f7}<6ewo1)J8eg9O z5XT6WT$)U(;k8|Pr`1}okpgll%M;*;7aR|zVLP{A0ze1HCH2;qlXT8DEVD#p!Kj!n zx0Py|G!dIuqsS#1Ic4Ee4s`G&2)P7ENZBJ>e&PtRMVZ9UzzVuhkd{3q{BMpDOd6yebxB9v zgLt|=kgN~PwI%BhkVt>&RDzl=NIB}$j`c~$`jn$VpBfOY0|Sx4P;_8m!UBIbga1N@ z>hTz!$XtrSpC2)!F+T2pRkREYz#qj742Xu9AZu%Ud=xgNc?M>|WBy9C48tEm!QXuF zNCh6Hj%-`<=qZX7^2^4E`d3MfLor_9(K?D&_~Re)cM;?dQ+Sj+9RktBmeavD5FtUQK2#8V|M z6j@P;kt0%t+>8!JIAD`y>`Dm88x@WGSd{!(3khzdwb{B42s#$#4)6~F9ICP~4$D>6 zNYnIF#y~UA%@l3>B~|+?YVE&MttqPYm($F%5Q^sr=MKfutBuSNIn37DX_ijVRTP@XileJTH>2E1B zZFp8ty zMIflmo>5Ubm4{01$4?F)eYiDs_>HGV4_%fiF3Xa-VY^iCIrt3k5eEbV&Fn^;QT2`L zgk#g}Tsqw^O<}h>g}%HgO);vS3STka<1Bu?qVZL?=n4SSL8UNRS~*Q`Uflhxo&!gjBO39MO(qOJ3t51k9&kv>;!F$kMP3aqQ) z)C|^vT_}VE69fZIoe_~r>RdR#Z~{S{dc1a{Y6nbqumhe-qS!`gKX#Tq!)G|B z(*OyYsxT#rw#+v#Fh6U3c>W=QbPj}M!)6th#RFm_Ej7iD9c7a6nBXCF198FE2=+XH o#fKXy#&K)n=f0 z9Vm$il`ys2q1vRT+D=3_$yk|mrgY3{mFb7DGnuLT&7FQBLU;&W(y2YoOsAhpn@l^C zPyM~UJKz98vFc=|H^koC-FJJpZ+G8&zqiYO@%ud-E@i|v@ryki_wST3E{9HR9ky`X zT~6c@oM;iPS<93qVVSZftQN}KvbHHc!84lA+7ot^*|Ux*XTmwBs^2z zgm=o9@L4!3cbXHOZ*ZbZ@=N@?y!NI<3oCb{+*4N`VC7zv`|8SrtlW?C7Re_DR)W=* zLaZi)n%0JzR#p>6O1wEn%<}PA@tEi3*}_CV zCrsz%f{-5<3KNoWWk$*5Bt;RX@}iVggmF1PCA>1D6a=ccDqQ&D*>hAg&npxJ|WxAtPl))%S{&O;MpT zk(pLqM$2h^XxR;;%dV|%-0pG`m#|>0t)eAi6Rinev|&v7gk#nocc`8VsjPINkSfe5 z#l1CJkfmu^Qlwl#VLenB2lY24D0o73PD?oveN)}({M2+-Do7&8R4y%LvywO#cdD*r zGMAc?l1bH*OhPa-S)zT(3gg2QlALE1 z;uS{YrpW1GZES|8XLV|5dRF$KRhAH;v>|zed%z#Av~T_v8&#fW%NE>xHvm475AGmjY2MfK@;(`_5`GC5a{>yOj;U?JLDjJO^A|KO2SAW zzgNzWTsnDCbzV62%9ln@o{~GLj(vD1Q86PSb|rv>dU{0bX-mhqPHVix_L(~A*ZZYU zr2}=@#*Uh2lhH$W>Ut`ZObvrJ0l4IqQnW5Woc} zwc_5BvIMmzq=cMwqxLBeeIwrTj#b`|rsO9mp{WWDXG>?5TB$=Ty$;yCA9wUI8kZJY7>#G6q*gI3g(NqXja$Vw({c(+ z;mN^<&q7vgH~7V{XdUOpNZm8-Vz0hL-SA}GVuy*F8~2FOH|*5~8xcDtr`}4}8+@We zjNyrH{HSGz*n_et7WEEV)Jh>gCCSARgQ{QDX}w;!F_9NwM1|=YIX#h5Bth4XijbED z-2^CO7iH=ho5m_nmQ?k^BBtdG^A=Rc^vo5k@uKxBufAiE<6u=o>3pt`N*9t!MpTlM zNTNlbpv%rv!Sry(-}-}97pCZSE$xa>WY(?jBAdY)cr=Igzm;nf`+jadrwOR=8x$8 z5iX35C_)NTbZ>$ zp9&Y6^_Dr8T7SplHu)vIL$!-DQ`1_1<5u|`isbW16lzabq4F5$NA&eM;%!I5`nq%X z-*o-3Yx&fA-@yg`16Qx6gXrQasdXkFjD_NC1f(nHo4~z&d zW=EI?Cp)6Oj_k+_#72vR*^#!6N~paO?XQG8x1D^OXTkNchx0`%0|ypfEc^N@fsTcM zHtrr{vSh@%!6%V3u^a*MX@hSllZS`?geVFrSaG&&h0zhAkk=-@{^>OI^cvMMQ%L9X zH{_GZ7-N3{w0Z1RZ%Pzl-C1MBwrclHr8K+>`IDFeE-u#p_V8Q7>s|X6`0wcbb*D3O z8ZJB>2Y3F;qy%Mxp3^577t!?F`Yw{Ya1g!+Z)wi@J?;m#TQ)3igU@uo#n0L1`0Ez= z;2b|`ZcFExvl)xS!eJV5wP{qSPQ^(B)|%Qi$(1*XXRT1iw&GDuvI<HPr}Tt)&H0^;IH=_>TSwTY+( zOtPnU6r;^Xg=#M1LFEjRdG5nd*P?gn$)ztZomp!cT(DIF;f;V$3J42)#oMwd-hRH~ z3%+^g_L(JH+1FhW1{OxjzMfz9?tVM}R(`#Acwyu_=Qo|4FUF>D+%gvLd<-v1K7^Oc zhbf^s&9thi=d^%W_hmOxFH%B7qPk$brX{&BtIJH03I$1)KSvDAlll@-U!$bKr+S*o zDA|XfvIm-*e5$eLK9$G+`e-H8w$8Uze1X@`Jm}fEe7M|mV2y8IRMzuCIa)XD(ULt%`9t>x?kPWvlNWdBK2N-dh_{o~5ik5Xw>`QDKM%L%FuUDPv)lV^ z@2CH{laXs*oA`tnmYoTjQta%udk z7Yz(uTip=l@6Abcnw><+Ga1odVoq1w#`;b8B*Ht?bu@vud@FtfZX{Yv zc=Vtd%Pi4>y)HIBy1H9dtK4X*Kht zx)A)x6*5>8#~~-Vx^{l!(LKGPj0kMRoHp-xUY0Ufa}2N<^`w7cdh~BoMwgN<4p5T19W_&X{IWLzKhhL$sD&e z%9qUYHDc~UqQg7gEVuW-EC)SU4Q`L7#KNCJgOR1t)OK|M)B@<&MDa8!f-dUYy)3@xD+}jHV=aa606d<1&_BF7SMEPMe|q8a zVy5I5*7>sxMLO&rQY*NTPQY!|#%^oJU-^WxI+_HfsR#P_I`v>I+^qrY?@v82W7-~Zdu7s*8r9}#Wiy=XJo8+Gq z&`86z60Y)xSX*sITc66`%J|z2eyE_xN=H=%g-t3bs`IqHtG_+bj@pQhRXdGZv1=t( z{hAIbDs}1{-Ay^Vq^Q`VbMz926mxUpU{#HEN)g1>y`mSG(}!raA9!>JeyvhK6rkSP z#6A;V?G|@RohT>N+YfBogBZJC&krEqD+a3hUC8ec1t|bjy&JTk(|bVoK~2Wx3_hng ztgqa9g=i>_8f1lL9G7H-g-yuSq!f7yd?LINR8nwKvoljrc{ft>RRsYL)Ff3`8d&ox zK~#qpI#fMVshde7er2HLWFeEy!q-g~GS?-R`GR~5->G_l4JAaW(MqG<^%}u1C|+42 zX1FZ~w342+GfGpcq-W&`R!@j`6z!=VLcPfhNJaGn24+Mk_N17a)sZmUiNPauwctl; zXX_7fP5QV3V~n%nr<%8@n-&Ze!XSKg6%<|RW~NB9Tohw z_W@~h?EXG+m$P`TaiC9C7rt&Xu~AV66M0I7yuO`}G79iFK2F}du$ z#Nt{EOB;Ga(03a$S*%|cbr_csRU3=*+9Yl+7@FU8f(ZlQB#kzcKt7qgSd1CsX;S&O z)B^cr5K_NY}psG3XMhmQ;?Zs?+g;QKk?fqxU&-gQ zjo{)sF*0z$j2!LfXo{BtB@9i-vqXK45*8boCyFMy{01d|OvwTz9h8vL(Ci-hVayqu z0ipaDYlYy*=RXaOJm~DbA6bp89XMX@q>#rm2zOKnqEvjH>wIS=7=HbHB^+DlW0epU zJm~1Ycd6X54|s%M=l3;1Elr95WOfWJkE~9Wd(Y0lxG=G(+@1nxc_8fnQ48LAbn(TK zw`ZLn0^i;v?|r!(KTplCweKngcdzs33GmF8`$ws8F;NNv@Qf1RiE!P0AGdefJs-A& zHto1?I=D#ZrjsZa7mRMYiSls4*rt~#9~X{n`iW}cLcN;-qJms|bTdR$D;JGzhKXw9 z+B-L?UA(~){-gtx9)_W^BM7{#?fD#w!>o?zaTuF@|NZDDhubPbGq`PfkSt%;iU#x| ztNn`Q9>0Z{&=t$3%W8jW>F_o(PiezQBbdgodXaxa*VbQQG=Ilv4FM``OOK*2wJklX zNSk`Po=kX6P{xS3_*lF}*S!h9m0O`-ezhgWcn5ZdilhN*a09Jt+vz(uCU&c0k0ZcYBROeRsEib7^`cEfSS<~gJA$w zVJV1RdM7sc!q^7%h&=#P99 z7@s**$M!0m`Ur5BY&uqCpf692Q}rq{R|?o8&*IC9ky>M>;#XCdk<(&ORgV;3N4_gr zx-If2;mg@3ac{iltVKL?%U&Q(b^-I)nwgf`$%9)C4I?$%2B0$vKu(T19#}}?!a}p{BqmrkNOw>w{{g7c7B1_<4z6EG(5!&7h|XA z6(^F1VT-RiG{7w%5TAxyppja;H$pp0p`G_GmqJf0IDQ$95m@QMKIFkt*THSOP2Zvl zhAW}y-R?Wxm9`im8o(K((f)0}lW>dEh?Ye^gQ=szkpGktX2HHg6q|pnugn1b8I>_H zF{%7HWtqTg2Cczv(b8e-=bu0*z0eS3w}yX<-D1{>86((>-u~sot6whnoMzFm^zB#I z`O^=2;;S9yo)hyU3xkVCN}ev*w2y>cO(JQ1Ppp2m-1icD_G-!ByUxG#QUBomw)dVZ z_kVHz+~U!tlXsq5=fC)1*OTuLttAR;qci1Q*I6tsS_|Mwa=*+h-px`A%$CgJbP-srP;}FdJ1iD#l9MPG*WR<~Cd$J_ zdN;j9`8ZG8Cw@?R=nj--D@bZIC99X=nQoOK!QahRun=1FsJyePceN;f)s8$O>R;p6 zqWJD-@xhPPeoOr>0c?X8he-@OMfSruWn~;G35qQ%f$H~>^=Q@YLeO|CS_zq2F$eC9 z;9=14WD{gH+8btH($<|8EC zurN`vgN5Yi2uY95MRP54}yG`rGTPR|3!9fC2 zS=iWR2MJgg*+(;!Mr;i`x7rZ`7Yzr6nK-OFWjELmW(smq;hwVkM7iU|1>Z*qGeHG+ z?$VADnD;DHo-BJ^sDnof)W5A9TgQ(RI8vUe1p?hVdMi}58#hKwBhS1`Fb{d13&i-Ec1Wof5*Qu_-qM( zzGpx14WLQ4|4q+r&xWhBbsYq_@yo0<%6q_?_GBF)WYmd1~T6mz1vPURkfr1|p z^)@999@c3pqvXHvQ>cIBVI6JmVfpAx>VvKw_gy~?{3KBBIzB(TaC4nM-UQV*DS|_` zb5B!$X!S<9|Fp>yI{neEz4s5l_iB09h57S~mzUCa;JICRu;+N@y1OUgzYF*YG$l`z z(#$btV^=?(s_R+ppD2#WKLRs`lT&_(WYcD`Sa4R@!vBwhv&8?EYySo3{Wq@fKe&S> z?%=kAw{$Ot7sYK3#CD6#5?$ datetime.utcnow(), + Product.istemplate == istemplate + ) + products = session.exec(query).all() + return products + + +@app.get("/customers/{customer_id}/sales/", response_model=List[Sale], tags=["Convenience"]) +def get_customer_sales( + customer_id: int, + status: Optional[SaleStatus] = None, + session: Session = Depends(get_session) +): + """Get all sales for a customer""" + customer = session.get(Customer, customer_id) + if not customer: + raise HTTPException(status_code=404, detail="Customer not found") + + query = select(Sale).where(Sale.customer_id == customer_id) + + if status: + query = query.where(Sale.status == status) + + sales = session.exec(query).all() + return sales + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/herodb/src/models/py/business.db b/herodb/src/models/py/business.db new file mode 100644 index 0000000000000000000000000000000000000000..a304f0921b93d772386f06909b78473012c09e00 GIT binary patch literal 28672 zcmeI4&u`mg7{~22ZQ=-Orcza9h58zmZG}) zms-cwdp)v6>uc4wrO;{pnmeae`ZZa$+4WR6BJ~X2?O3+1MvFV%BiD45UZni4T;CSu z2jpYv0a@3(8~&N^G-auNcgL5lk1F0EvUEq1rJb6zH^2~E__Rr(@*{DqN~padE!DGa z%}@_x)h4cHx>9E2cz-^%vbo7V?F_6tYKUs~Ez{5pH+sAfvUW80-&nn?IjXH&u5KEy z?-i=8DX!M-(ElD|ox-!aGxJ9m@R}As)e4avi3ir8V69|oWqFzXrZ(<2zNgHG0Q!;{ z&>UBBJtx4jE^M6jc>nt1ul=HIRL^nEzGg?@47-TZH$5ZTv(D=&6K4#83*@nW9H}p& zIMD$z8z>7H?eDG+fHR`ec@~YD-F)#zg}7s>GnDR0@_wBz{Td#L z`(nM}FNmA_lSaSgvvIK?6op}2T#7`@r#aDBS`f;9G#wLV3qmEAJj$|*%<8SR53aDH zn5HdnD3)tl_w*+Ro0et|7TFuBMtTGBuH4*e*IH6fQ(fDn7wC-J+>LK?xmvBv%!z4O zS;$QZ|0~%-b#D0n{{_Rpcyk6uO&|aQAOHd&00JNY0w4eaAOHd&00NT)KBbceuFx3+ z99wH{?>3~Cv=#gKp^zmnRdk;ds7OsrWDVV_xBx zD%I>heG;&Ai_V7NYP;PKVgFP5->Q@r1PLPwGz7E>4*MP^uJ*!}$L>!~e=Z zf5i(#F%SR&5C8!X009sH0T2KI5C8!X0D=FCKsCWJH(2`SpFO+&Ve|wcvTmVJ&gH09 d=0lcaRymQQs>AgM^tr&<^$&%7wwPZ?{{uE{a@GI< literal 0 HcmV?d00001 diff --git a/herodb/src/models/py/example.py b/herodb/src/models/py/example.py new file mode 100755 index 0000000..37d3f56 --- /dev/null +++ b/herodb/src/models/py/example.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating the use of the business models. +""" +import datetime +from typing import List + +from sqlmodel import Session, SQLModel, create_engine, select + +from models import ( + Currency, + Customer, + Product, + ProductComponent, + ProductStatus, + ProductType, + Sale, + SaleItem, + SaleStatus, +) + + +def create_tables(engine): + """Create all tables in the database""" + SQLModel.metadata.create_all(engine) + + +def create_sample_data(session: Session) -> None: + """Create sample data for demonstration""" + # Create currencies + usd = Currency(currency_code="USD", amount=0.0) + eur = Currency(currency_code="EUR", amount=0.0) + session.add(usd) + session.add(eur) + session.commit() + + # Create a customer + customer = Customer.new( + name="Acme Corporation", + description="A fictional company", + pubkey="acme123456", + contact_sids=["circle1_contact123", "circle2_contact456"] + ) + session.add(customer) + session.commit() + + # Create product components + cpu_component = ProductComponent.new( + name="CPU", + description="Central Processing Unit", + quantity=1, + ) + ram_component = ProductComponent.new( + name="RAM", + description="Random Access Memory", + quantity=2, + ) + session.add(cpu_component) + session.add(ram_component) + session.commit() + + # Create products + laptop_price = Currency(currency_code="USD", amount=1200.0) + session.add(laptop_price) + session.commit() + + laptop = Product.new( + name="Laptop", + description="High-performance laptop", + price=laptop_price, + type_=ProductType.PRODUCT, + category="Electronics", + status=ProductStatus.AVAILABLE, + max_amount=100, + validity_days=365, + istemplate=False, + ) + laptop.add_component(cpu_component) + laptop.add_component(ram_component) + session.add(laptop) + session.commit() + + support_price = Currency(currency_code="USD", amount=50.0) + session.add(support_price) + session.commit() + + support = Product.new( + name="Technical Support", + description="24/7 technical support", + price=support_price, + type_=ProductType.SERVICE, + category="Support", + status=ProductStatus.AVAILABLE, + max_amount=1000, + validity_days=30, + istemplate=True, # This is a template product + ) + session.add(support) + session.commit() + + # Create a sale + sale = Sale.new( + customer=customer, + currency_code="USD", + ) + session.add(sale) + session.commit() + + # Create sale items + laptop_unit_price = Currency(currency_code="USD", amount=1200.0) + session.add(laptop_unit_price) + session.commit() + + laptop_item = SaleItem.new( + product=laptop, + quantity=1, + unit_price=laptop_unit_price, + active_till=datetime.datetime.utcnow() + datetime.timedelta(days=365), + ) + sale.add_item(laptop_item) + + support_unit_price = Currency(currency_code="USD", amount=50.0) + session.add(support_unit_price) + session.commit() + + support_item = SaleItem.new( + product=support, + quantity=2, + unit_price=support_unit_price, + active_till=datetime.datetime.utcnow() + datetime.timedelta(days=30), + ) + sale.add_item(support_item) + + # Complete the sale + sale.update_status(SaleStatus.COMPLETED) + session.commit() + + +def query_data(session: Session) -> None: + """Query and display data from the database""" + print("\n=== Customers ===") + customers = session.exec(select(Customer)).all() + for customer in customers: + print(f"Customer: {customer.name} ({customer.pubkey})") + print(f" Description: {customer.description}") + print(f" Contact SIDs: {', '.join(customer.contact_sids)}") + print(f" Created at: {customer.created_at}") + + print("\n=== Products ===") + products = session.exec(select(Product)).all() + for product in products: + print(f"Product: {product.name} ({product.type_.value})") + print(f" Description: {product.description}") + print(f" Price: {product.price.amount} {product.price.currency_code}") + print(f" Status: {product.status.value}") + print(f" Is Template: {product.istemplate}") + print(f" Components:") + for component in product.components: + print(f" - {component.name}: {component.quantity}") + + print("\n=== Sales ===") + sales = session.exec(select(Sale)).all() + for sale in sales: + print(f"Sale to: {sale.customer.name}") + print(f" Status: {sale.status.value}") + print(f" Total: {sale.total_amount.amount} {sale.total_amount.currency_code}") + print(f" Items:") + for item in sale.items: + print(f" - {item.name}: {item.quantity} x {item.unit_price.amount} = {item.subtotal.amount} {item.subtotal.currency_code}") + + +def main(): + """Main function""" + print("Creating in-memory SQLite database...") + engine = create_engine("sqlite:///business.db", echo=False) + + print("Creating tables...") + create_tables(engine) + + print("Creating sample data...") + with Session(engine) as session: + create_sample_data(session) + + print("Querying data...") + with Session(engine) as session: + query_data(session) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/herodb/src/models/py/install_and_run.sh b/herodb/src/models/py/install_and_run.sh new file mode 100755 index 0000000..103c3e8 --- /dev/null +++ b/herodb/src/models/py/install_and_run.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Script to install dependencies using uv and run the example script + +set -e # Exit on error + +# Change to the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" +echo "Changed to directory: $SCRIPT_DIR" + +# Define variables +VENV_DIR=".venv" +REQUIREMENTS="sqlmodel pydantic" + +# Check if uv is installed +if ! command -v uv &> /dev/null; then + echo "Error: uv is not installed." + echo "Please install it using: curl -LsSf https://astral.sh/uv/install.sh | sh" + exit 1 +fi + +# Create virtual environment if it doesn't exist +if [ ! -d "$VENV_DIR" ]; then + echo "Creating virtual environment..." + uv venv "$VENV_DIR" +fi + +# Activate virtual environment +echo "Activating virtual environment..." +source "$VENV_DIR/bin/activate" + +# Install dependencies +echo "Installing dependencies using uv..." +uv pip install $REQUIREMENTS + +# Make example.py executable +chmod +x example.py + +# Remove existing database file if it exists +if [ -f "business.db" ]; then + echo "Removing existing database file..." + rm business.db +fi + +# Run the example script +echo "Running example script..." +python example.py + +echo "Done!" \ No newline at end of file diff --git a/herodb/src/models/py/models.py b/herodb/src/models/py/models.py new file mode 100644 index 0000000..d396a2f --- /dev/null +++ b/herodb/src/models/py/models.py @@ -0,0 +1,297 @@ +""" +Python port of the business models from Rust using SQLModel. +""" +from datetime import datetime, timedelta +from enum import Enum +import json +from typing import List, Optional + +from sqlmodel import Field, Relationship, SQLModel + + +class SaleStatus(str, Enum): + """SaleStatus represents the status of a sale""" + PENDING = "pending" + COMPLETED = "completed" + CANCELLED = "cancelled" + + +class ProductType(str, Enum): + """ProductType represents the type of a product""" + PRODUCT = "product" + SERVICE = "service" + + +class ProductStatus(str, Enum): + """ProductStatus represents the status of a product""" + AVAILABLE = "available" + UNAVAILABLE = "unavailable" + + +class Currency(SQLModel, table=True): + """Currency represents a monetary value with amount and currency code""" + id: Optional[int] = Field(default=None, primary_key=True) + amount: float + currency_code: str + + @classmethod + def new(cls, amount: float, currency_code: str) -> "Currency": + """Create a new currency with amount and code""" + return cls(amount=amount, currency_code=currency_code) + + +class Customer(SQLModel, table=True): + """Customer represents a customer who can purchase products or services""" + id: Optional[int] = Field(default=None, primary_key=True) + name: str + description: str + pubkey: str + contact_sids_json: str = Field(default="[]") + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + # Relationships + sales: List["Sale"] = Relationship(back_populates="customer") + + @property + def contact_sids(self) -> List[str]: + """Get the contact SIDs as a list""" + return json.loads(self.contact_sids_json) + + @contact_sids.setter + def contact_sids(self, value: List[str]) -> None: + """Set the contact SIDs from a list""" + self.contact_sids_json = json.dumps(value) + + @classmethod + def new(cls, name: str, description: str, pubkey: str, contact_sids: List[str] = None) -> "Customer": + """Create a new customer with default timestamps""" + customer = cls( + name=name, + description=description, + pubkey=pubkey, + ) + if contact_sids: + customer.contact_sids = contact_sids + return customer + + def add_contact(self, contact_id: int) -> None: + """Add a contact ID to the customer""" + # In a real implementation, this would add a relationship to a Contact model + # For simplicity, we're not implementing the Contact model in this example + self.updated_at = datetime.utcnow() + + def add_contact_sid(self, circle_id: str, object_id: str) -> None: + """Add a smart ID (sid) to the customer's contact_sids list""" + sid = f"{circle_id}_{object_id}" + sids = self.contact_sids + if sid not in sids: + sids.append(sid) + self.contact_sids = sids + self.updated_at = datetime.utcnow() + + +class ProductComponent(SQLModel, table=True): + """ProductComponent represents a component of a product""" + id: Optional[int] = Field(default=None, primary_key=True) + name: str + description: str + quantity: int + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + # Relationships + product_id: Optional[int] = Field(default=None, foreign_key="product.id") + product: Optional["Product"] = Relationship(back_populates="components") + + @classmethod + def new(cls, name: str, description: str, quantity: int) -> "ProductComponent": + """Create a new product component with default timestamps""" + return cls( + name=name, + description=description, + quantity=quantity, + ) + + +class Product(SQLModel, table=True): + """Product represents a product or service offered""" + id: Optional[int] = Field(default=None, primary_key=True) + name: str + description: str + type_: ProductType = Field(sa_column_kwargs={"name": "type"}) + category: str + status: ProductStatus + max_amount: int + purchase_till: datetime + active_till: datetime + istemplate: bool = Field(default=False) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + # Price relationship + price_id: Optional[int] = Field(default=None, foreign_key="currency.id") + price: Optional[Currency] = Relationship() + + # Relationships + components: List[ProductComponent] = Relationship(back_populates="product") + sale_items: List["SaleItem"] = Relationship(back_populates="product") + + @classmethod + def new( + cls, + name: str, + description: str, + price: Currency, + type_: ProductType, + category: str, + status: ProductStatus, + max_amount: int, + validity_days: int, + istemplate: bool = False, + ) -> "Product": + """Create a new product with default timestamps""" + now = datetime.utcnow() + return cls( + name=name, + description=description, + price=price, + type_=type_, + category=category, + status=status, + max_amount=max_amount, + purchase_till=now + timedelta(days=365), + active_till=now + timedelta(days=validity_days), + istemplate=istemplate, + ) + + def add_component(self, component: ProductComponent) -> None: + """Add a component to this product""" + component.product = self + self.components.append(component) + self.updated_at = datetime.utcnow() + + def set_purchase_period(self, purchase_till: datetime) -> None: + """Update the purchase availability timeframe""" + self.purchase_till = purchase_till + self.updated_at = datetime.utcnow() + + def set_active_period(self, active_till: datetime) -> None: + """Update the active timeframe""" + self.active_till = active_till + self.updated_at = datetime.utcnow() + + def is_purchasable(self) -> bool: + """Check if the product is available for purchase""" + return self.status == ProductStatus.AVAILABLE and datetime.utcnow() <= self.purchase_till + + def is_active(self) -> bool: + """Check if the product is still active (for services)""" + return datetime.utcnow() <= self.active_till + + +class SaleItem(SQLModel, table=True): + """SaleItem represents an item in a sale""" + id: Optional[int] = Field(default=None, primary_key=True) + name: str + quantity: int + active_till: datetime + + # Relationships + sale_id: Optional[int] = Field(default=None, foreign_key="sale.id") + sale: Optional["Sale"] = Relationship(back_populates="items") + + product_id: Optional[int] = Field(default=None, foreign_key="product.id") + product: Optional[Product] = Relationship(back_populates="sale_items") + + unit_price_id: Optional[int] = Field(default=None, foreign_key="currency.id") + unit_price: Optional[Currency] = Relationship(sa_relationship_kwargs={"foreign_keys": "[SaleItem.unit_price_id]"}) + + subtotal_id: Optional[int] = Field(default=None, foreign_key="currency.id") + subtotal: Optional[Currency] = Relationship(sa_relationship_kwargs={"foreign_keys": "[SaleItem.subtotal_id]"}) + + @classmethod + def new( + cls, + product: Product, + quantity: int, + unit_price: Currency, + active_till: datetime, + ) -> "SaleItem": + """Create a new sale item""" + # Calculate subtotal + amount = unit_price.amount * quantity + subtotal = Currency( + amount=amount, + currency_code=unit_price.currency_code, + ) + + return cls( + name=product.name, + product=product, + quantity=quantity, + unit_price=unit_price, + subtotal=subtotal, + active_till=active_till, + ) + + +class Sale(SQLModel, table=True): + """Sale represents a sale of products or services""" + id: Optional[int] = Field(default=None, primary_key=True) + status: SaleStatus + sale_date: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + + # Relationships + customer_id: Optional[int] = Field(default=None, foreign_key="customer.id") + customer: Optional[Customer] = Relationship(back_populates="sales") + + total_amount_id: Optional[int] = Field(default=None, foreign_key="currency.id") + total_amount: Optional[Currency] = Relationship() + + items: List[SaleItem] = Relationship(back_populates="sale") + + @classmethod + def new( + cls, + customer: Customer, + currency_code: str, + status: SaleStatus = SaleStatus.PENDING, + ) -> "Sale": + """Create a new sale with default timestamps""" + total_amount = Currency(amount=0.0, currency_code=currency_code) + + return cls( + customer=customer, + total_amount=total_amount, + status=status, + ) + + def add_item(self, item: SaleItem) -> None: + """Add an item to the sale and update the total amount""" + item.sale = self + + # Update the total amount + if not self.items: + # First item, initialize the total amount with the same currency + self.total_amount = Currency( + amount=item.subtotal.amount, + currency_code=item.subtotal.currency_code, + ) + else: + # Add to the existing total + # (Assumes all items have the same currency) + self.total_amount.amount += item.subtotal.amount + + # Add the item to the list + self.items.append(item) + + # Update the sale timestamp + self.updated_at = datetime.utcnow() + + def update_status(self, status: SaleStatus) -> None: + """Update the status of the sale""" + self.status = status + self.updated_at = datetime.utcnow() \ No newline at end of file diff --git a/herodb/src/models/py/server.sh b/herodb/src/models/py/server.sh new file mode 100755 index 0000000..d291078 --- /dev/null +++ b/herodb/src/models/py/server.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Script to start the FastAPI server + +set -e # Exit on error + +# Change to the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" +echo "Changed to directory: $SCRIPT_DIR" + +# Define variables +VENV_DIR=".venv" +REQUIREMENTS="sqlmodel pydantic fastapi uvicorn" + +# Check if uv is installed +if ! command -v uv &> /dev/null; then + echo "Error: uv is not installed." + echo "Please install it using: curl -LsSf https://astral.sh/uv/install.sh | sh" + exit 1 +fi + +# Create virtual environment if it doesn't exist +if [ ! -d "$VENV_DIR" ]; then + echo "Creating virtual environment..." + uv venv "$VENV_DIR" +fi + +# Activate virtual environment +echo "Activating virtual environment..." +source "$VENV_DIR/bin/activate" + +# Install dependencies +echo "Installing dependencies using uv..." +uv pip install $REQUIREMENTS + +# Make api.py executable +chmod +x api.py + +# Start the FastAPI server +echo "Starting FastAPI server..." +echo "API documentation available at: http://localhost:8000/docs" +uvicorn api:app --host 0.0.0.0 --port 8000 --reload \ No newline at end of file From 56f73e580212d9d02d835240264fb087dab47e69 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 19 Apr 2025 09:02:35 +0200 Subject: [PATCH 03/10] ... --- herodb/Cargo.toml | 8 +- herodb/README.md | 7 +- herodb/examples/rhai_demo.rs | 38 - herodb/src/cmd/dbexample/DB_README.md | 91 - herodb/src/cmd/dbexample/db_tests.rs | 168 -- herodb/src/cmd/dbexample/examples.rs | 64 - herodb/src/cmd/dbexample/factory.rs | 33 - .../main.rs | 42 +- .../src/cmd/dbexample_governance/Cargo.lock | 2161 ----------------- .../src/cmd/dbexample_governance/Cargo.toml | 12 - .../{dbexample2 => dbexample_prod}/main.rs | 0 .../GOVERNANCE_ENHANCEMENT_PLAN.md | 2 +- .../src/models/{governance => gov}/README.md | 0 .../models/{governance => gov}/committee.rs | 2 +- .../src/models/{governance => gov}/company.rs | 4 +- .../models/{governance => gov}/compliance.rs | 2 +- .../src/models/{governance => gov}/meeting.rs | 4 +- herodb/src/models/{governance => gov}/mod.rs | 0 .../models/{governance => gov}/resolution.rs | 10 +- .../models/{governance => gov}/shareholder.rs | 0 herodb/src/models/{governance => gov}/user.rs | 0 herodb/src/models/{governance => gov}/vote.rs | 4 +- herodb/src/models/mod.rs | 2 +- herodb/tmp/dbexample2/currency/conf | 4 + herodb/tmp/dbexample2/currency/db | Bin 0 -> 524287 bytes .../dbexample2/currency/snap.000000000000008A | Bin 0 -> 80 bytes herodb/tmp/dbexample2/product/conf | 4 + herodb/tmp/dbexample2/product/db | Bin 0 -> 524287 bytes .../dbexample2/product/snap.0000000000000060 | Bin 0 -> 69 bytes herodb/tmp/dbexample2/sale/conf | 4 + herodb/tmp/dbexample2/sale/db | Bin 0 -> 524287 bytes .../tmp/dbexample2/sale/snap.0000000000000060 | Bin 0 -> 69 bytes 32 files changed, 60 insertions(+), 2606 deletions(-) delete mode 100644 herodb/examples/rhai_demo.rs delete mode 100644 herodb/src/cmd/dbexample/DB_README.md delete mode 100644 herodb/src/cmd/dbexample/db_tests.rs delete mode 100644 herodb/src/cmd/dbexample/examples.rs delete mode 100644 herodb/src/cmd/dbexample/factory.rs rename herodb/src/cmd/{dbexample_governance => dbexample_gov}/main.rs (94%) delete mode 100644 herodb/src/cmd/dbexample_governance/Cargo.lock delete mode 100644 herodb/src/cmd/dbexample_governance/Cargo.toml rename herodb/src/cmd/{dbexample2 => dbexample_prod}/main.rs (100%) rename herodb/src/models/{governance => gov}/GOVERNANCE_ENHANCEMENT_PLAN.md (99%) rename herodb/src/models/{governance => gov}/README.md (100%) rename herodb/src/models/{governance => gov}/committee.rs (99%) rename herodb/src/models/{governance => gov}/company.rs (95%) rename herodb/src/models/{governance => gov}/compliance.rs (99%) rename herodb/src/models/{governance => gov}/meeting.rs (96%) rename herodb/src/models/{governance => gov}/mod.rs (100%) rename herodb/src/models/{governance => gov}/resolution.rs (93%) rename herodb/src/models/{governance => gov}/shareholder.rs (100%) rename herodb/src/models/{governance => gov}/user.rs (100%) rename herodb/src/models/{governance => gov}/vote.rs (95%) create mode 100644 herodb/tmp/dbexample2/currency/conf create mode 100644 herodb/tmp/dbexample2/currency/db create mode 100644 herodb/tmp/dbexample2/currency/snap.000000000000008A create mode 100644 herodb/tmp/dbexample2/product/conf create mode 100644 herodb/tmp/dbexample2/product/db create mode 100644 herodb/tmp/dbexample2/product/snap.0000000000000060 create mode 100644 herodb/tmp/dbexample2/sale/conf create mode 100644 herodb/tmp/dbexample2/sale/db create mode 100644 herodb/tmp/dbexample2/sale/snap.0000000000000060 diff --git a/herodb/Cargo.toml b/herodb/Cargo.toml index 7318bfc..8d34a4c 100644 --- a/herodb/Cargo.toml +++ b/herodb/Cargo.toml @@ -28,9 +28,13 @@ name = "rhai_demo" path = "examples/rhai_demo.rs" [[bin]] -name = "dbexample2" -path = "src/cmd/dbexample2/main.rs" +name = "dbexample_prod" +path = "src/cmd/dbexample_prod/main.rs" [[bin]] name = "dbexample_mcc" path = "src/cmd/dbexample_mcc/main.rs" + +[[bin]] +name = "dbexample_gov" +path = "src/cmd/dbexample_gov/main.rs" diff --git a/herodb/README.md b/herodb/README.md index edbc14c..4075cfd 100644 --- a/herodb/README.md +++ b/herodb/README.md @@ -7,7 +7,12 @@ A database library built on top of sled with model support. ## example ```bash -cargo run --bin dbexample2 +#test for mcc module +cargo run --bin dbexample_mcc +#test for governance module +cargo run --bin dbexample_gov +#test for products +cargo run --bin dbexample_prod ``` ## Features diff --git a/herodb/examples/rhai_demo.rs b/herodb/examples/rhai_demo.rs deleted file mode 100644 index 1639e7c..0000000 --- a/herodb/examples/rhai_demo.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Demonstrates how to use the Rhai wrappers for our models - -use herodb::zaz::rhai::{run_script_file, run_example_script}; -use std::path::PathBuf; -use std::fs; -use std::time::SystemTime; - -fn main() -> Result<(), String> { - println!("=== RHAI MODEL WRAPPERS DEMONSTRATION ==="); - - // Run our test script that creates model objects - let test_script_path = "src/zaz/rhai/test.rhai"; - println!("\n1. Running model creation test script: {}", test_script_path); - run_script_file(test_script_path)?; - - // Create temporary directory for DB example - let temp_dir = create_temp_dir() - .map_err(|e| format!("Failed to create temp dir: {}", e))?; - - // Run our example script that uses the DB - println!("\n2. Running example with database at: {:?}", temp_dir); - run_example_script(temp_dir.to_str().unwrap())?; - - println!("\n=== DEMONSTRATION COMPLETED SUCCESSFULLY ==="); - Ok(()) -} - -/// Creates a simple temporary directory -fn create_temp_dir() -> std::io::Result { - let temp_dir = std::env::temp_dir(); - let random_name = format!("rhai-demo-{}", SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis()); - let path = temp_dir.join(random_name); - fs::create_dir_all(&path)?; - Ok(path) -} \ No newline at end of file diff --git a/herodb/src/cmd/dbexample/DB_README.md b/herodb/src/cmd/dbexample/DB_README.md deleted file mode 100644 index 25e312f..0000000 --- a/herodb/src/cmd/dbexample/DB_README.md +++ /dev/null @@ -1,91 +0,0 @@ -# HeroDB Architecture - -This document explains the architecture of HeroDB, focusing on the separation between model definitions and database logic. - -## Core Principles - -1. **Separation of Concerns**: The DB core should not know about specific models -2. **Registration-Based System**: Models get registered with the DB through a factory pattern -3. **Type-Safety**: Despite the separation, we maintain full type safety - -## Components - -### Core Module - -The `core` module provides the database foundation without knowing about specific models: - -- `SledModel` trait: Defines the interface models must implement -- `Storable` trait: Provides serialization/deserialization capabilities -- `SledDB`: Generic database wrapper for any model type -- `DB`: Main database manager that holds registered models -- `DBBuilder`: Builder for creating a DB with registered models - -### Zaz Module - -The `zaz` module contains domain-specific models and factories: - -- `models`: Defines specific model types like User, Company, etc. -- `factory`: Provides functions to create a DB with zaz models registered - -## Using the DB - -### Option 1: Factory Function - -The easiest way to create a DB with all zaz models is to use the factory: - -```rust -use herodb::zaz::create_zaz_db; - -// Create a DB with all zaz models registered -let db = create_zaz_db("/path/to/db")?; - -// Use the DB with specific model types -let user = User::new(...); -db.set(&user)?; -let retrieved: User = db.get(&id)?; -``` - -### Option 2: Builder Pattern - -For more control, use the builder pattern to register only the models you need: - -```rust -use herodb::core::{DBBuilder, DB}; -use herodb::zaz::models::{User, Company}; - -// Create a DB with only User and Company models -let db = DBBuilder::new("/path/to/db") - .register_model::() - .register_model::() - .build()?; -``` - -### Option 3: Dynamic Registration - -You can also register models with an existing DB: - -```rust -use herodb::core::DB; -use herodb::zaz::models::User; - -// Create an empty DB -let mut db = DB::new("/path/to/db")?; - -// Register the User model -db.register::()?; -``` - -## Benefits of this Architecture - -1. **Modularity**: The core DB code doesn't need to change when models change -2. **Extensibility**: New model types can be added without modifying core DB code -3. **Flexibility**: Different modules can define and use their own models with the same DB code -4. **Type Safety**: Full compile-time type checking is maintained - -## Implementation Details - -The key to this architecture is the combination of generic types and trait objects: - -- `SledDB` provides type-safe operations for specific model types -- `AnyDbOperations` trait allows type-erased operations through a common interface -- `TypeId` mapping enables runtime lookup of the correct DB for a given model type diff --git a/herodb/src/cmd/dbexample/db_tests.rs b/herodb/src/cmd/dbexample/db_tests.rs deleted file mode 100644 index b29dece..0000000 --- a/herodb/src/cmd/dbexample/db_tests.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Integration tests for zaz database module - -#[cfg(test)] -mod tests { - use sled; - use bincode; - use chrono::{DateTime, Utc}; - use serde::{Deserialize, Serialize}; - use std::path::Path; - use tempfile::tempdir; - use std::collections::HashMap; - - /// Test model for database operations - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - struct User { - id: u32, - name: String, - email: String, - balance: f64, - created_at: DateTime, - updated_at: DateTime, - } - - impl User { - fn new(id: u32, name: String, email: String, balance: f64) -> Self { - let now = Utc::now(); - Self { - id, - name, - email, - balance, - created_at: now, - updated_at: now, - } - } - } - - /// Test basic CRUD operations - #[test] - fn test_basic_crud() { - // Create a temporary directory for testing - let temp_dir = tempdir().expect("Failed to create temp directory"); - println!("Created temporary directory at: {:?}", temp_dir.path()); - - // Open a sled database in the temporary directory - let db = sled::open(temp_dir.path().join("users")).expect("Failed to open database"); - println!("Opened database at: {:?}", temp_dir.path().join("users")); - - // CREATE a user - let user = User::new(1, "Test User".to_string(), "test@example.com".to_string(), 100.0); - let user_key = user.id.to_string(); - let user_value = bincode::serialize(&user).expect("Failed to serialize user"); - db.insert(user_key.as_bytes(), user_value).expect("Failed to insert user"); - db.flush().expect("Failed to flush database"); - println!("Created user: {} ({})", user.name, user.email); - - // READ the user - let result = db.get(user_key.as_bytes()).expect("Failed to query database"); - assert!(result.is_some(), "User should exist"); - if let Some(data) = result { - let retrieved_user: User = bincode::deserialize(&data).expect("Failed to deserialize user"); - println!("Retrieved user: {} ({})", retrieved_user.name, retrieved_user.email); - assert_eq!(user, retrieved_user, "Retrieved user should match original"); - } - - // UPDATE the user - let updated_user = User::new(1, "Updated User".to_string(), "updated@example.com".to_string(), 150.0); - let updated_value = bincode::serialize(&updated_user).expect("Failed to serialize updated user"); - db.insert(user_key.as_bytes(), updated_value).expect("Failed to update user"); - db.flush().expect("Failed to flush database"); - println!("Updated user: {} ({})", updated_user.name, updated_user.email); - - let result = db.get(user_key.as_bytes()).expect("Failed to query database"); - if let Some(data) = result { - let retrieved_user: User = bincode::deserialize(&data).expect("Failed to deserialize user"); - assert_eq!(updated_user, retrieved_user, "Retrieved user should match updated version"); - } else { - panic!("User should exist after update"); - } - - // DELETE the user - db.remove(user_key.as_bytes()).expect("Failed to delete user"); - db.flush().expect("Failed to flush database"); - println!("Deleted user"); - - let result = db.get(user_key.as_bytes()).expect("Failed to query database"); - assert!(result.is_none(), "User should be deleted"); - - // Clean up - drop(db); - temp_dir.close().expect("Failed to cleanup temporary directory"); - } - - /// Test transaction-like behavior with multiple operations - #[test] - fn test_transaction_behavior() { - // Create a temporary directory for testing - let temp_dir = tempdir().expect("Failed to create temp directory"); - println!("Created temporary directory at: {:?}", temp_dir.path()); - - // Open a sled database in the temporary directory - let db = sled::open(temp_dir.path().join("tx_test")).expect("Failed to open database"); - println!("Opened transaction test database at: {:?}", temp_dir.path().join("tx_test")); - - // Create initial users - let user1 = User::new(1, "User One".to_string(), "one@example.com".to_string(), 100.0); - let user2 = User::new(2, "User Two".to_string(), "two@example.com".to_string(), 50.0); - - // Insert initial users - db.insert(user1.id.to_string().as_bytes(), bincode::serialize(&user1).unwrap()).unwrap(); - db.insert(user2.id.to_string().as_bytes(), bincode::serialize(&user2).unwrap()).unwrap(); - db.flush().unwrap(); - println!("Inserted initial users"); - - // Simulate a transaction - transfer 25.0 from user1 to user2 - println!("Starting transaction simulation: transfer 25.0 from user1 to user2"); - - // Create transaction workspace - let mut tx_workspace = HashMap::new(); - - // Retrieve current state - if let Some(data) = db.get(user1.id.to_string().as_bytes()).unwrap() { - let user: User = bincode::deserialize(&data).unwrap(); - tx_workspace.insert(user1.id.to_string(), user); - } - - if let Some(data) = db.get(user2.id.to_string().as_bytes()).unwrap() { - let user: User = bincode::deserialize(&data).unwrap(); - tx_workspace.insert(user2.id.to_string(), user); - } - - // Modify both users in the transaction - let mut updated_user1 = tx_workspace.get(&user1.id.to_string()).unwrap().clone(); - let mut updated_user2 = tx_workspace.get(&user2.id.to_string()).unwrap().clone(); - - updated_user1.balance -= 25.0; - updated_user2.balance += 25.0; - - // Update the workspace - tx_workspace.insert(user1.id.to_string(), updated_user1); - tx_workspace.insert(user2.id.to_string(), updated_user2); - - // Commit the transaction - println!("Committing transaction"); - for (key, user) in tx_workspace { - let user_bytes = bincode::serialize(&user).unwrap(); - db.insert(key.as_bytes(), user_bytes).unwrap(); - } - db.flush().unwrap(); - - // Verify the results - if let Some(data) = db.get(user1.id.to_string().as_bytes()).unwrap() { - let final_user1: User = bincode::deserialize(&data).unwrap(); - assert_eq!(final_user1.balance, 75.0, "User1 balance should be 75.0"); - println!("Verified user1 balance is now {}", final_user1.balance); - } - - if let Some(data) = db.get(user2.id.to_string().as_bytes()).unwrap() { - let final_user2: User = bincode::deserialize(&data).unwrap(); - assert_eq!(final_user2.balance, 75.0, "User2 balance should be 75.0"); - println!("Verified user2 balance is now {}", final_user2.balance); - } - - // Clean up - drop(db); - temp_dir.close().expect("Failed to cleanup temporary directory"); - } -} diff --git a/herodb/src/cmd/dbexample/examples.rs b/herodb/src/cmd/dbexample/examples.rs deleted file mode 100644 index 199d074..0000000 --- a/herodb/src/cmd/dbexample/examples.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Examples for using the Zaz database - -use crate::zaz::models::*; -use crate::zaz::factory::create_zaz_db; -use std::path::PathBuf; -use chrono::Utc; - -/// Run a simple example of the DB operations -pub fn run_db_examples() -> Result<(), Box> { - println!("Running Zaz DB examples..."); - - // Create a temp DB path - let db_path = PathBuf::from("/tmp/zaz-examples"); - std::fs::create_dir_all(&db_path)?; - - // Create DB instance - let db = create_zaz_db(&db_path)?; - - // Example 1: User operations - println!("\n--- User Examples ---"); - let user = User::new( - 1, - "John Doe".to_string(), - "john@example.com".to_string(), - "secure123".to_string(), - "Example Corp".to_string(), - "User".to_string(), - ); - - db.set(&user)?; - println!("Inserted user: {}", user.name); - - let retrieved_user = db.get::(&user.id.to_string())?; - println!("Retrieved user: {} ({})", retrieved_user.name, retrieved_user.email); - - // Example 2: Company operations - println!("\n--- Company Examples ---"); - let company = Company::new( - 1, - "Example Corp".to_string(), - "EX123456".to_string(), - Utc::now(), - "12-31".to_string(), - "info@example.com".to_string(), - "123-456-7890".to_string(), - "www.example.com".to_string(), - "123 Example St, Example City".to_string(), - BusinessType::Global, - "Technology".to_string(), - "An example company".to_string(), - CompanyStatus::Active, - ); - - db.set(&company)?; - println!("Inserted company: {}", company.name); - - let companies = db.list::()?; - println!("Found {} companies", companies.len()); - - // Clean up - std::fs::remove_dir_all(db_path)?; - - Ok(()) -} diff --git a/herodb/src/cmd/dbexample/factory.rs b/herodb/src/cmd/dbexample/factory.rs deleted file mode 100644 index b59c8d0..0000000 --- a/herodb/src/cmd/dbexample/factory.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Factory module for creating a DB with all zaz models registered - -use crate::core::{DB, DBBuilder, SledDBResult}; -use crate::zaz::models::*; -use std::path::PathBuf; - -/// Create a new DB instance with all zaz models registered -pub fn create_zaz_db>(path: P) -> SledDBResult { - // Using the builder pattern to register all models - DBBuilder::new(path) - .register_model::() - .register_model::() - .register_model::() - .register_model::() - .register_model::() - .register_model::() - .register_model::() - .build() -} - -/// Register all zaz models with an existing DB instance -pub fn register_zaz_models(db: &mut DB) -> SledDBResult<()> { - // Dynamically register all zaz models - db.register::()?; - db.register::()?; - db.register::()?; - db.register::()?; - db.register::()?; - db.register::()?; - db.register::()?; - - Ok(()) -} \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_governance/main.rs b/herodb/src/cmd/dbexample_gov/main.rs similarity index 94% rename from herodb/src/cmd/dbexample_governance/main.rs rename to herodb/src/cmd/dbexample_gov/main.rs index 0ac3b99..c344fee 100644 --- a/herodb/src/cmd/dbexample_governance/main.rs +++ b/herodb/src/cmd/dbexample_gov/main.rs @@ -1,6 +1,6 @@ use chrono::{Utc, Duration}; -use herodb::db::DBBuilder; -use herodb::models::governance::{ +use herodb::db::{DBBuilder, SledDB, SledModel}; +use herodb::models::gov::{ Company, CompanyStatus, BusinessType, Shareholder, ShareholderType, Meeting, Attendee, MeetingStatus, AttendeeRole, AttendeeStatus, @@ -12,11 +12,11 @@ use std::path::PathBuf; use std::fs; fn main() -> Result<(), Box> { - println!("DB Example: Governance Module"); + println!("DB Example: Gov Module"); println!("============================"); // Create a temporary directory for the database - let db_path = PathBuf::from("/tmp/dbexample_governance"); + let db_path = PathBuf::from("/tmp/dbexample_gov"); if db_path.exists() { fs::remove_dir_all(&db_path)?; } @@ -54,7 +54,7 @@ fn main() -> Result<(), Box> { ); // Insert the company - db.insert(&company)?; + db.set(&company)?; println!("Company created: {} (ID: {})", company.name, company.id); println!("Status: {:?}, Business Type: {:?}", company.status, company.business_type); @@ -90,9 +90,9 @@ fn main() -> Result<(), Box> { ); // Insert the users - db.insert(&user1)?; - db.insert(&user2)?; - db.insert(&user3)?; + db.set(&user1)?; + db.set(&user2)?; + db.set(&user3)?; println!("User created: {} ({})", user1.name, user1.role); println!("User created: {} ({})", user2.name, user2.role); @@ -133,9 +133,9 @@ fn main() -> Result<(), Box> { ); // Insert the shareholders - db.insert(&shareholder1)?; - db.insert(&shareholder2)?; - db.insert(&shareholder3)?; + db.set(&shareholder1)?; + db.set(&shareholder2)?; + db.set(&shareholder3)?; println!("Shareholder created: {} ({} shares, {}%)", shareholder1.name, shareholder1.shares, shareholder1.percentage); @@ -146,7 +146,7 @@ fn main() -> Result<(), Box> { // Update shareholder shares shareholder1.update_shares(1100.0, 44.0); - db.insert(&shareholder1)?; + db.set(&shareholder1)?; println!("Updated shareholder: {} ({} shares, {}%)", shareholder1.name, shareholder1.shares, shareholder1.percentage); @@ -194,7 +194,7 @@ fn main() -> Result<(), Box> { meeting.add_attendee(attendee3); // Insert the meeting - db.insert(&meeting)?; + db.set(&meeting)?; println!("Meeting created: {} ({})", meeting.title, meeting.date.format("%Y-%m-%d %H:%M")); println!("Status: {:?}, Attendees: {}", meeting.status, meeting.attendees.len()); @@ -205,7 +205,7 @@ fn main() -> Result<(), Box> { if let Some(attendee) = meeting.find_attendee_by_user_id_mut(user3.id) { attendee.update_status(AttendeeStatus::Confirmed); } - db.insert(&meeting)?; + db.set(&meeting)?; // Get confirmed attendees let confirmed = meeting.confirmed_attendees(); @@ -238,19 +238,19 @@ fn main() -> Result<(), Box> { resolution.link_to_meeting(meeting.id); // Insert the resolution - db.insert(&resolution)?; + db.set(&resolution)?; println!("Resolution created: {} (Status: {:?})", resolution.title, resolution.status); // Propose the resolution resolution.propose(); - db.insert(&resolution)?; + db.set(&resolution)?; println!("Resolution proposed on {}", resolution.proposed_at.format("%Y-%m-%d")); // Add approvals resolution.add_approval(user1.id, user1.name.clone(), true, "Approved as proposed".to_string()); resolution.add_approval(user2.id, user2.name.clone(), true, "Financials look good".to_string()); resolution.add_approval(user3.id, user3.name.clone(), true, "No concerns".to_string()); - db.insert(&resolution)?; + db.set(&resolution)?; // Check approval status println!("Approvals: {}, Rejections: {}", @@ -259,7 +259,7 @@ fn main() -> Result<(), Box> { // Approve the resolution resolution.approve(); - db.insert(&resolution)?; + db.set(&resolution)?; println!("Resolution approved on {}", resolution.approved_at.unwrap().format("%Y-%m-%d")); @@ -283,7 +283,7 @@ fn main() -> Result<(), Box> { vote.add_option("Abstain".to_string(), 0); // Insert the vote - db.insert(&vote)?; + db.set(&vote)?; println!("Vote created: {} (Status: {:?})", vote.title, vote.status); println!("Voting period: {} to {}", vote.start_date.format("%Y-%m-%d"), @@ -293,7 +293,7 @@ fn main() -> Result<(), Box> { vote.add_ballot(user1.id, 1, 1000); // User 1 votes "Approve" with 1000 shares vote.add_ballot(user2.id, 1, 750); // User 2 votes "Approve" with 750 shares vote.add_ballot(user3.id, 3, 750); // User 3 votes "Abstain" with 750 shares - db.insert(&vote)?; + db.set(&vote)?; // Check voting results println!("Voting results:"); @@ -314,7 +314,7 @@ fn main() -> Result<(), Box> { // Link the resolution to the vote vote_resolution.link_to_vote(vote.id); vote_resolution.propose(); - db.insert(&vote_resolution)?; + db.set(&vote_resolution)?; println!("Created resolution linked to vote: {}", vote_resolution.title); println!("\n7. Retrieving Related Objects"); diff --git a/herodb/src/cmd/dbexample_governance/Cargo.lock b/herodb/src/cmd/dbexample_governance/Cargo.lock deleted file mode 100644 index cddb29b..0000000 --- a/herodb/src/cmd/dbexample_governance/Cargo.lock +++ /dev/null @@ -1,2161 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "const-random", - "getrandom 0.2.15", - "once_cell", - "version_check", - "zerocopy 0.7.35", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "aes-gcm", - "base64", - "hkdf", - "hmac", - "percent-encoding", - "rand", - "sha2", - "subtle", - "time", - "version_check", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "dbexample_governance" -version = "0.1.0" -dependencies = [ - "chrono", - "herodb", -] - -[[package]] -name = "deranged" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "0.99.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.100", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.8.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - -[[package]] -name = "herodb" -version = "0.1.0" -dependencies = [ - "bincode", - "brotli", - "chrono", - "lazy_static", - "paste", - "poem", - "poem-openapi", - "rhai", - "serde", - "serde_json", - "sled", - "tempfile", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "linux-raw-sys" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "multer" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin", - "tokio", - "version_check", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.9.0", - "cfg-if", - "libc", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.10", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "poem" -version = "1.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504774c97b0744c1ee108a37e5a65a9745a4725c4c06277521dabc28eb53a904" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "cookie", - "futures-util", - "headers", - "http", - "hyper", - "mime", - "multer", - "nix", - "parking_lot 0.12.3", - "percent-encoding", - "pin-project-lite", - "poem-derive", - "quick-xml 0.30.0", - "regex", - "rfc7239", - "serde", - "serde_json", - "serde_urlencoded", - "serde_yaml", - "smallvec", - "tempfile", - "thiserror", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "wildmatch", -] - -[[package]] -name = "poem-derive" -version = "1.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ddcf4680d8d867e1e375116203846acb088483fa2070244f90589f458bbb31" -dependencies = [ - "proc-macro-crate 2.0.2", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "poem-openapi" -version = "2.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e26f78b6195ea1b7a16f46bda1961c598e5a66912f2aa1b8b7a2f395aebb9fc" -dependencies = [ - "base64", - "bytes", - "derive_more", - "futures-util", - "mime", - "num-traits", - "poem", - "poem-openapi-derive", - "quick-xml 0.26.0", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "serde_yaml", - "thiserror", - "tokio", -] - -[[package]] -name = "poem-openapi-derive" -version = "2.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c3e2975c930dc72c024e75b230c3b6058fb3a746d5739b83aa8f28ab1a42d4" -dependencies = [ - "darling", - "http", - "indexmap 1.9.3", - "mime", - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", - "thiserror", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy 0.8.24", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro2" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quick-xml" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rfc7239" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a82f1d1e38e9a85bb58ffcfadf22ed6f2c94e8cd8581ec2b0f80a2a6858350f" -dependencies = [ - "uncased", -] - -[[package]] -name = "rhai" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" -dependencies = [ - "ahash", - "bitflags 2.9.0", - "instant", - "num-traits", - "once_cell", - "rhai_codegen", - "smallvec", - "smartstring", - "thin-vec", -] - -[[package]] -name = "rhai_codegen" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" -dependencies = [ - "bitflags 2.9.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.8.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - -[[package]] -name = "smallvec" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" - -[[package]] -name = "smartstring" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" -dependencies = [ - "autocfg", - "static_assertions", - "version_check", -] - -[[package]] -name = "socket2" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom 0.3.2", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thin-vec" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tokio" -version = "1.44.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot 0.12.3", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.8.0", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.8.0", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "uuid" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" -dependencies = [ - "getrandom 0.3.2", - "serde", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.100", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wildmatch" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive 0.8.24", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] diff --git a/herodb/src/cmd/dbexample_governance/Cargo.toml b/herodb/src/cmd/dbexample_governance/Cargo.toml deleted file mode 100644 index 0b589b7..0000000 --- a/herodb/src/cmd/dbexample_governance/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "dbexample_governance" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "dbexample_governance" -path = "main.rs" - -[dependencies] -herodb = { path = "../../.." } -chrono = "0.4" \ No newline at end of file diff --git a/herodb/src/cmd/dbexample2/main.rs b/herodb/src/cmd/dbexample_prod/main.rs similarity index 100% rename from herodb/src/cmd/dbexample2/main.rs rename to herodb/src/cmd/dbexample_prod/main.rs diff --git a/herodb/src/models/governance/GOVERNANCE_ENHANCEMENT_PLAN.md b/herodb/src/models/gov/GOVERNANCE_ENHANCEMENT_PLAN.md similarity index 99% rename from herodb/src/models/governance/GOVERNANCE_ENHANCEMENT_PLAN.md rename to herodb/src/models/gov/GOVERNANCE_ENHANCEMENT_PLAN.md index 8153489..6e31a4b 100644 --- a/herodb/src/models/governance/GOVERNANCE_ENHANCEMENT_PLAN.md +++ b/herodb/src/models/gov/GOVERNANCE_ENHANCEMENT_PLAN.md @@ -115,7 +115,7 @@ pub mod governance; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::db::{SledModel, Storable, SledDB, SledDBError}; -use crate::models::governance::{Meeting, Vote}; +use crate::models::gov::{Meeting, Vote}; /// ResolutionStatus represents the status of a resolution #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/herodb/src/models/governance/README.md b/herodb/src/models/gov/README.md similarity index 100% rename from herodb/src/models/governance/README.md rename to herodb/src/models/gov/README.md diff --git a/herodb/src/models/governance/committee.rs b/herodb/src/models/gov/committee.rs similarity index 99% rename from herodb/src/models/governance/committee.rs rename to herodb/src/models/gov/committee.rs index 04ff00f..c7977f9 100644 --- a/herodb/src/models/governance/committee.rs +++ b/herodb/src/models/gov/committee.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::db::{SledModel, Storable, SledDB, SledDBError}; -use crate::models::governance::User; +use crate::models::gov::User; /// CommitteeRole represents the role of a member in a committee #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/herodb/src/models/governance/company.rs b/herodb/src/models/gov/company.rs similarity index 95% rename from herodb/src/models/governance/company.rs rename to herodb/src/models/gov/company.rs index 0ab0a2c..d224197 100644 --- a/herodb/src/models/governance/company.rs +++ b/herodb/src/models/gov/company.rs @@ -123,8 +123,8 @@ impl Company { } /// Get all resolutions for this company - pub fn get_resolutions(&self, db: &SledDB) -> Result, SledDBError> { - let all_resolutions = db.list()?; + pub fn get_resolutions(&self, db: &crate::db::DB) -> Result, SledDBError> { + let all_resolutions = db.list::()?; let company_resolutions = all_resolutions .into_iter() .filter(|resolution| resolution.company_id == self.id) diff --git a/herodb/src/models/governance/compliance.rs b/herodb/src/models/gov/compliance.rs similarity index 99% rename from herodb/src/models/governance/compliance.rs rename to herodb/src/models/gov/compliance.rs index d3f8bbf..9f8063e 100644 --- a/herodb/src/models/governance/compliance.rs +++ b/herodb/src/models/gov/compliance.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::db::{SledModel, Storable, SledDB, SledDBError}; -use crate::models::governance::Company; +use crate::models::gov::Company; /// ComplianceRequirement represents a regulatory requirement #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/herodb/src/models/governance/meeting.rs b/herodb/src/models/gov/meeting.rs similarity index 96% rename from herodb/src/models/governance/meeting.rs rename to herodb/src/models/gov/meeting.rs index 6b15ef7..c355840 100644 --- a/herodb/src/models/governance/meeting.rs +++ b/herodb/src/models/gov/meeting.rs @@ -164,8 +164,8 @@ impl Meeting { } /// Get all resolutions discussed in this meeting - pub fn get_resolutions(&self, db: &SledDB) -> Result, SledDBError> { - let all_resolutions = db.list()?; + pub fn get_resolutions(&self, db: &crate::db::DB) -> Result, SledDBError> { + let all_resolutions = db.list::()?; let meeting_resolutions = all_resolutions .into_iter() .filter(|resolution| resolution.meeting_id == Some(self.id)) diff --git a/herodb/src/models/governance/mod.rs b/herodb/src/models/gov/mod.rs similarity index 100% rename from herodb/src/models/governance/mod.rs rename to herodb/src/models/gov/mod.rs diff --git a/herodb/src/models/governance/resolution.rs b/herodb/src/models/gov/resolution.rs similarity index 93% rename from herodb/src/models/governance/resolution.rs rename to herodb/src/models/gov/resolution.rs index 59fe9a8..1416e35 100644 --- a/herodb/src/models/governance/resolution.rs +++ b/herodb/src/models/gov/resolution.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::db::{SledModel, Storable, SledDB, SledDBError}; -use crate::models::governance::{Meeting, Vote}; +use crate::models::gov::{Meeting, Vote}; /// ResolutionStatus represents the status of a resolution #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -158,10 +158,10 @@ impl Resolution { } /// Get the meeting associated with this resolution - pub fn get_meeting(&self, db: &SledDB) -> Result, SledDBError> { + pub fn get_meeting(&self, db: &crate::db::DB) -> Result, SledDBError> { match self.meeting_id { Some(meeting_id) => { - let meeting = db.get(&meeting_id.to_string())?; + let meeting = db.get::(&meeting_id.to_string())?; Ok(Some(meeting)) } None => Ok(None), @@ -169,10 +169,10 @@ impl Resolution { } /// Get the vote associated with this resolution - pub fn get_vote(&self, db: &SledDB) -> Result, SledDBError> { + pub fn get_vote(&self, db: &crate::db::DB) -> Result, SledDBError> { match self.vote_id { Some(vote_id) => { - let vote = db.get(&vote_id.to_string())?; + let vote = db.get::(&vote_id.to_string())?; Ok(Some(vote)) } None => Ok(None), diff --git a/herodb/src/models/governance/shareholder.rs b/herodb/src/models/gov/shareholder.rs similarity index 100% rename from herodb/src/models/governance/shareholder.rs rename to herodb/src/models/gov/shareholder.rs diff --git a/herodb/src/models/governance/user.rs b/herodb/src/models/gov/user.rs similarity index 100% rename from herodb/src/models/governance/user.rs rename to herodb/src/models/gov/user.rs diff --git a/herodb/src/models/governance/vote.rs b/herodb/src/models/gov/vote.rs similarity index 95% rename from herodb/src/models/governance/vote.rs rename to herodb/src/models/gov/vote.rs index 93406d5..5080f73 100644 --- a/herodb/src/models/governance/vote.rs +++ b/herodb/src/models/gov/vote.rs @@ -128,8 +128,8 @@ impl Vote { } /// Get the resolution associated with this vote - pub fn get_resolution(&self, db: &SledDB) -> Result, SledDBError> { - let all_resolutions = db.list()?; + pub fn get_resolution(&self, db: &crate::db::DB) -> Result, SledDBError> { + let all_resolutions = db.list::()?; let vote_resolution = all_resolutions .into_iter() .find(|resolution| resolution.vote_id == Some(self.id)); diff --git a/herodb/src/models/mod.rs b/herodb/src/models/mod.rs index bc6d16a..1c3df0f 100644 --- a/herodb/src/models/mod.rs +++ b/herodb/src/models/mod.rs @@ -1,4 +1,4 @@ pub mod biz; pub mod mcc; pub mod circle; -pub mod governance; \ No newline at end of file +pub mod gov; \ No newline at end of file diff --git a/herodb/tmp/dbexample2/currency/conf b/herodb/tmp/dbexample2/currency/conf new file mode 100644 index 0000000..4154d7c --- /dev/null +++ b/herodb/tmp/dbexample2/currency/conf @@ -0,0 +1,4 @@ +segment_size: 524288 +use_compression: false +version: 0.34 +vQ \ No newline at end of file diff --git a/herodb/tmp/dbexample2/currency/db b/herodb/tmp/dbexample2/currency/db new file mode 100644 index 0000000000000000000000000000000000000000..8fa8e24ce06c91bd171a2852bc8abbae8df28c24 GIT binary patch literal 524287 zcmeIuF-ijg5CzcLtgcHCf?8T7$FQ;S1d8<{$%;i#ObkRUBHGyG5K2(+2suD31hMuC zR(66#5?H;1#rw_7z|0hHK7DD-tBj(`_3r5XCtLRC53}KF5=neEZ}0OwYQGEd)#{(+ z^+>BZNuu_8GaY9e5sjkgcgs#ubjstSLH8mZrXN|owLD#K4|=`ww0*gAc(Av%y%{lC zyL)_|G{-0VZ%D}T>b0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U TAV7cs0RjXF5FkL{p9w4g*xD*) literal 0 HcmV?d00001 diff --git a/herodb/tmp/dbexample2/currency/snap.000000000000008A b/herodb/tmp/dbexample2/currency/snap.000000000000008A new file mode 100644 index 0000000000000000000000000000000000000000..65dfc915a939f3c5183c252bc6da4a7640f6a7c7 GIT binary patch literal 80 vcmeBXfB;5DcPNX&U6N4-#+Kn^QiZWq1rlJ)1T98W7~53H1d{tdEh8}5np*>>Yr0aC+0xn*h_L_x;bE&>BQp~U zD5U9PmZ8qp%2;!PL7SzUu57|4kn;0FJrfrSLcztk+glP6f|$Ptf~)f?%My};*r`4g3S!Q@*ij!p zBM6csk;;-y1(8U>rl<0%N}{o2rRhH>eLT6ESYA<9P_;37|GkH2H->^AFErnh`rfnQ ziHpDAIkxcNg6pI6GQSDcKV7ta&v$#?7+$g>oEZCM`reJD=gwZ9e`M}(Tg!vHK5SaK z`C!|c{_?u|+?(r`u063LE|NK|_U-JUyKAo8F>z1tXwJ6_xBigRIC5%HW^H)o)|A4L zW!3K=9a~>mu{rtO@z0~_-8&EDbq+j#aLqSeWZ1%3hYvNKLZ(h>%#rm4Q zOYO@q6l8bLNZC+!{8Ih*>APPaOiMcc-vcs^%>HQdfc(;?qOboL>}eXgVar`_p07>I z*xmj}&!NS?MUFi>fANk5SF@I-RJEjbJoHk-JF7}Qm{av?+S1z-_RT6^+d4D%bYEXX z*R`r?x6RDGZ+dw3z*|}6H*LEy^knaqu2pmX>`Xj+t!-A^bE}I#`6=$aOTS5+UdjUzRv8KcH=?y`i)1^^OiK# zpIkJov+3S}6|Ge@v7$q}9?zRTBODGc%nIj~mGzx@*f}Zva`lDg85eg}ZhD*brLQx- zCiJF#b=$QU^&`7dPwzQ?xV!B`=Gc8p7k=8&m2`G%TJ^_?RpHj&mhBssWjC%nF(vz4 z%h~QB$pHbd%5esZ);Di&mTT8 zSe`#HdU(il^>QXzi-~_A*wG(=mH Date: Sat, 19 Apr 2025 10:49:45 +0200 Subject: [PATCH 04/10] ... --- herodb/src/cmd/dbexample_prod/main.rs | 7 ++++++- .../tmp/dbexample2/currency/conf | 0 .../dbexample_prod/tmp/dbexample2/currency/db | Bin 0 -> 138 bytes .../tmp/dbexample2/product/conf | 0 .../dbexample_prod/tmp/dbexample2/product/db | Bin 0 -> 96 bytes .../dbexample_prod}/tmp/dbexample2/sale/conf | 0 .../cmd/dbexample_prod/tmp/dbexample2/sale/db | Bin 0 -> 96 bytes herodb/tmp/dbexample2/currency/db | Bin 524287 -> 0 bytes .../dbexample2/currency/snap.000000000000008A | Bin 80 -> 0 bytes herodb/tmp/dbexample2/product/db | Bin 524287 -> 0 bytes .../dbexample2/product/snap.0000000000000060 | Bin 69 -> 0 bytes herodb/tmp/dbexample2/sale/db | Bin 524287 -> 0 bytes .../tmp/dbexample2/sale/snap.0000000000000060 | Bin 69 -> 0 bytes 13 files changed, 6 insertions(+), 1 deletion(-) rename herodb/{ => src/cmd/dbexample_prod}/tmp/dbexample2/currency/conf (100%) create mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db rename herodb/{ => src/cmd/dbexample_prod}/tmp/dbexample2/product/conf (100%) create mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db rename herodb/{ => src/cmd/dbexample_prod}/tmp/dbexample2/sale/conf (100%) create mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/sale/db delete mode 100644 herodb/tmp/dbexample2/currency/db delete mode 100644 herodb/tmp/dbexample2/currency/snap.000000000000008A delete mode 100644 herodb/tmp/dbexample2/product/db delete mode 100644 herodb/tmp/dbexample2/product/snap.0000000000000060 delete mode 100644 herodb/tmp/dbexample2/sale/db delete mode 100644 herodb/tmp/dbexample2/sale/snap.0000000000000060 diff --git a/herodb/src/cmd/dbexample_prod/main.rs b/herodb/src/cmd/dbexample_prod/main.rs index e9ce238..b7cf3fe 100644 --- a/herodb/src/cmd/dbexample_prod/main.rs +++ b/herodb/src/cmd/dbexample_prod/main.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { println!("============================================================"); // Create a temporary directory for the database - let db_path = PathBuf::from("./tmp/dbexample2"); + let db_path = PathBuf::from("/tmp/dbexample_prod"); if db_path.exists() { fs::remove_dir_all(&db_path)?; } @@ -155,6 +155,11 @@ fn main() -> Result<(), Box> { // db.insert_product(product2); "#; + + println!("\n0. Executing Script"); + println!("----------------------------------------"); + + engine.eval::<()>(script)?; // Create a database instance with our models registered diff --git a/herodb/tmp/dbexample2/currency/conf b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf similarity index 100% rename from herodb/tmp/dbexample2/currency/conf rename to herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db new file mode 100644 index 0000000000000000000000000000000000000000..d5807330460e68a71f2e8bf53ebc9b4dff42a52a GIT binary patch literal 138 zcmeyr>p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3OB8!Eg3c|k{=otO{+TLf literal 0 HcmV?d00001 diff --git a/herodb/tmp/dbexample2/product/conf b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf similarity index 100% rename from herodb/tmp/dbexample2/product/conf rename to herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db new file mode 100644 index 0000000000000000000000000000000000000000..03cab51e3623cec460294de8760063899e7041de GIT binary patch literal 96 zcmeyr>p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3BZNuu_8GaY9e5sjkgcgs#ubjstSLH8mZrXN|owLD#K4|=`ww0*gAc(Av%y%{lC zyL)_|G{-0VZ%D}T>b0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U TAV7cs0RjXF5FkL{p9w4g*xD*) diff --git a/herodb/tmp/dbexample2/currency/snap.000000000000008A b/herodb/tmp/dbexample2/currency/snap.000000000000008A deleted file mode 100644 index 65dfc915a939f3c5183c252bc6da4a7640f6a7c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80 vcmeBXfB;5DcPNX&U6N4-#+Kn^QiZWq1rlJ)1T98W7~53H1d{tdEh8}5np*>>Yr0aC+0xn*h_L_x;bE&>BQp~U zD5U9PmZ8qp%2;!PL7SzUu57|4kn;0FJrfrSLcztk+glP6f|$Ptf~)f?%My};*r`4g3S!Q@*ij!p zBM6csk;;-y1(8U>rl<0%N}{o2rRhH>eLT6ESYA<9P_;37|GkH2H->^AFErnh`rfnQ ziHpDAIkxcNg6pI6GQSDcKV7ta&v$#?7+$g>oEZCM`reJD=gwZ9e`M}(Tg!vHK5SaK z`C!|c{_?u|+?(r`u063LE|NK|_U-JUyKAo8F>z1tXwJ6_xBigRIC5%HW^H)o)|A4L zW!3K=9a~>mu{rtO@z0~_-8&EDbq+j#aLqSeWZ1%3hYvNKLZ(h>%#rm4Q zOYO@q6l8bLNZC+!{8Ih*>APPaOiMcc-vcs^%>HQdfc(;?qOboL>}eXgVar`_p07>I z*xmj}&!NS?MUFi>fANk5SF@I-RJEjbJoHk-JF7}Qm{av?+S1z-_RT6^+d4D%bYEXX z*R`r?x6RDGZ+dw3z*|}6H*LEy^knaqu2pmX>`Xj+t!-A^bE}I#`6=$aOTS5+UdjUzRv8KcH=?y`i)1^^OiK# zpIkJov+3S}6|Ge@v7$q}9?zRTBODGc%nIj~mGzx@*f}Zva`lDg85eg}ZhD*brLQx- zCiJF#b=$QU^&`7dPwzQ?xV!B`=Gc8p7k=8&m2`G%TJ^_?RpHj&mhBssWjC%nF(vz4 z%h~QB$pHbd%5esZ);Di&mTT8 zSe`#HdU(il^>QXzi-~_A*wG(=mH Date: Sat, 19 Apr 2025 10:50:26 +0200 Subject: [PATCH 05/10] ... --- herodb/.gitignore | 5 +++++ .../dbexample_prod/tmp/dbexample2/currency/conf | 4 ---- .../cmd/dbexample_prod/tmp/dbexample2/currency/db | Bin 138 -> 0 bytes .../dbexample_prod/tmp/dbexample2/product/conf | 4 ---- .../cmd/dbexample_prod/tmp/dbexample2/product/db | Bin 96 -> 0 bytes .../cmd/dbexample_prod/tmp/dbexample2/sale/conf | 4 ---- .../src/cmd/dbexample_prod/tmp/dbexample2/sale/db | Bin 96 -> 0 bytes 7 files changed, 5 insertions(+), 12 deletions(-) create mode 100644 herodb/.gitignore delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/sale/conf delete mode 100644 herodb/src/cmd/dbexample_prod/tmp/dbexample2/sale/db diff --git a/herodb/.gitignore b/herodb/.gitignore new file mode 100644 index 0000000..cedc139 --- /dev/null +++ b/herodb/.gitignore @@ -0,0 +1,5 @@ +target/ +temp/ +tmp/ +*.log +*.tmp diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf deleted file mode 100644 index 4154d7c..0000000 --- a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/conf +++ /dev/null @@ -1,4 +0,0 @@ -segment_size: 524288 -use_compression: false -version: 0.34 -vQ \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/currency/db deleted file mode 100644 index d5807330460e68a71f2e8bf53ebc9b4dff42a52a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeyr>p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3OB8!Eg3c|k{=otO{+TLf diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf deleted file mode 100644 index 4154d7c..0000000 --- a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/conf +++ /dev/null @@ -1,4 +0,0 @@ -segment_size: 524288 -use_compression: false -version: 0.34 -vQ \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db b/herodb/src/cmd/dbexample_prod/tmp/dbexample2/product/db deleted file mode 100644 index 03cab51e3623cec460294de8760063899e7041de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96 zcmeyr>p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3p1^^2&iX(fCl^e3%}Wb?7~IIDp(mA7#UtGHZSDhU|@nOVECw1oX^3 Date: Sat, 19 Apr 2025 11:24:06 +0200 Subject: [PATCH 06/10] ... --- herodb/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/herodb/src/lib.rs b/herodb/src/lib.rs index f1f91be..f5b6a46 100644 --- a/herodb/src/lib.rs +++ b/herodb/src/lib.rs @@ -7,6 +7,7 @@ pub mod db; pub mod error; pub mod models; +// pub mod rhaiengine; // Re-exports pub use error::Error; From 6e9305d4e6e319e18ba0ec0b15adcf83cb19975c Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 19 Apr 2025 11:32:36 +0200 Subject: [PATCH 07/10] ... --- herodb/src/models/biz/invoice.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/herodb/src/models/biz/invoice.rs b/herodb/src/models/biz/invoice.rs index e4030c1..5c66257 100644 --- a/herodb/src/models/biz/invoice.rs +++ b/herodb/src/models/biz/invoice.rs @@ -27,15 +27,17 @@ pub struct Payment { pub amount: Currency, pub date: DateTime, pub method: String, + pub comment: String, } impl Payment { /// Create a new payment - pub fn new(amount: Currency, method: String) -> Self { + pub fn new(amount: Currency, method: String, comment: String) -> Self { Self { amount, date: Utc::now(), method, + comment, } } } From 5b5b64658c73a617dba0f32d98f8ef82bb0a9775 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 19 Apr 2025 13:53:41 +0200 Subject: [PATCH 08/10] ... --- herodb/src/lib.rs | 2 +- herodb/src/models/biz/README.md | 301 ++++++++++++-- herodb/src/models/biz/business_models_plan.md | 371 ------------------ herodb/src/models/biz/sale.rs | 240 +++++++++-- herodb/src/models/biz/service.rs | 24 ++ 5 files changed, 520 insertions(+), 418 deletions(-) delete mode 100644 herodb/src/models/biz/business_models_plan.md diff --git a/herodb/src/lib.rs b/herodb/src/lib.rs index f5b6a46..9e1771a 100644 --- a/herodb/src/lib.rs +++ b/herodb/src/lib.rs @@ -7,7 +7,7 @@ pub mod db; pub mod error; pub mod models; -// pub mod rhaiengine; +pub mod rhaiengine; // Re-exports pub use error::Error; diff --git a/herodb/src/models/biz/README.md b/herodb/src/models/biz/README.md index 4aa684c..b6a9859 100644 --- a/herodb/src/models/biz/README.md +++ b/herodb/src/models/biz/README.md @@ -9,27 +9,53 @@ The business models are implemented as Rust structs and enums with serialization ## Model Relationships ``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Currency │◄────┤ Product │◄────┤ SaleItem │ -└─────────────┘ └─────────────┘ └──────┬──────┘ - ▲ │ - │ │ - ┌─────┴──────────┐ │ - │ProductComponent│ │ - └────────────────┘ │ - ▼ ┌─────────────┐ - │ Sale │ - └─────────────┘ + │ Customer │ + └──────┬──────┘ + │ + ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Currency │◄────┤ Product │◄────┤ SaleItem │◄────┤ Sale │ +└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ + ▲ │ + │ │ + ┌─────┴──────────┐ │ + │ProductComponent│ │ + └────────────────┘ │ + │ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ Currency │◄────┤ Service │◄────┤ ServiceItem │◄───────────┘ +└─────────────┘ └─────────────┘ └─────────────┘ + │ + │ + ▼ + ┌─────────────┐ ┌─────────────┐ + │ InvoiceItem │◄────┤ Invoice │ + └─────────────┘ └─────────────┘ ``` +## Business Logic Relationships + +- **Customer**: The entity purchasing products or services +- **Product/Service**: Defines what is being sold, including its base price + - Can be marked as a template (`is_template=true`) to create copies for actual sales +- **Sale**: Represents the transaction of selling products/services to customers, including tax calculations + - Can be linked to a Service when the sale creates an ongoing service +- **Service**: Represents an ongoing service provided to a customer + - Created from a Product template when the product type is Service +- **Invoice**: Represents the billing document for a sale, with payment tracking + - Created from a Sale object to handle billing and payment tracking + ## Root Objects -- root objects are the one who are stored in the DB -- Root Objects are - - currency - - product +- Root objects are the ones stored directly in the DB +- Root Objects are: + - Customer + - Currency + - Product - Sale + - Service + - Invoice ## Models @@ -44,6 +70,23 @@ Represents a monetary value with an amount and currency code. **Builder:** - `CurrencyBuilder` - Provides a fluent interface for creating Currency instances +### Customer (Root Object) + +Represents a customer who can purchase products or services. + +**Properties:** +- `id`: u32 - Unique identifier +- `name`: String - Customer name +- `description`: String - Customer description +- `pubkey`: String - Customer's public key +- `contact_ids`: Vec - List of contact IDs +- `created_at`: DateTime - Creation timestamp +- `updated_at`: DateTime - Last update timestamp + +**Methods:** +- `add_contact()` - Adds a contact ID to the customer +- `remove_contact()` - Removes a contact ID from the customer + ### Product #### ProductType Enum @@ -70,11 +113,11 @@ Represents a component part of a product. **Builder:** - `ProductComponentBuilder` - Provides a fluent interface for creating ProductComponent instances -#### Product (Root Object) +#### Product (Root Object) Represents a product or service offered. **Properties:** -- `id`: u32 - Unique identifier +- `id`: i64 - Unique identifier - `name`: String - Product name - `description`: String - Product description - `price`: Currency - Product price @@ -83,10 +126,11 @@ Represents a product or service offered. - `status`: ProductStatus - Available or Unavailable - `created_at`: DateTime - Creation timestamp - `updated_at`: DateTime - Last update timestamp -- `max_amount`: u16 - Maximum quantity available +- `max_amount`: i64 - Maximum quantity available - `purchase_till`: DateTime - Deadline for purchasing - `active_till`: DateTime - When product/service expires - `components`: Vec - List of product components +- `is_template`: bool - Whether this is a template product (to be added) **Methods:** - `add_component()` - Adds a component to this product @@ -104,7 +148,62 @@ Represents a product or service offered. - `get_id()` - Returns the ID as a string - `db_prefix()` - Returns "product" as the database prefix -### Sale +### Service (Root Object) + +#### BillingFrequency Enum +Defines how often a service is billed: +- `Hourly` - Billed by the hour +- `Daily` - Billed daily +- `Weekly` - Billed weekly +- `Monthly` - Billed monthly +- `Yearly` - Billed yearly + +#### ServiceStatus Enum +Tracks the status of a service: +- `Active` - Service is currently active +- `Paused` - Service is temporarily paused +- `Cancelled` - Service has been cancelled +- `Completed` - Service has been completed + +#### ServiceItem +Represents an item within a service. + +**Properties:** +- `id`: u32 - Unique identifier +- `service_id`: u32 - Parent service ID +- `product_id`: u32 - ID of the product this service is based on +- `name`: String - Service name +- `description`: String - Detailed description of the service item +- `comments`: String - Additional notes or comments about the service item +- `quantity`: i32 - Number of units +- `unit_price`: Currency - Price per unit +- `subtotal`: Currency - Total price before tax +- `tax_rate`: f64 - Tax rate as a percentage +- `tax_amount`: Currency - Calculated tax amount +- `is_taxable`: bool - Whether this item is taxable +- `active_till`: DateTime - When service expires + +#### Service +Represents an ongoing service provided to a customer. + +**Properties:** +- `id`: u32 - Unique identifier +- `customer_id`: u32 - ID of the customer receiving the service +- `total_amount`: Currency - Total service amount including tax +- `status`: ServiceStatus - Current service status +- `billing_frequency`: BillingFrequency - How often the service is billed +- `service_date`: DateTime - When service started +- `created_at`: DateTime - Creation timestamp +- `updated_at`: DateTime - Last update timestamp +- `items`: Vec - List of items in the service +- `is_template`: bool - Whether this is a template service (to be added) + +**Methods:** +- `add_item()` - Adds an item to the service and updates total +- `calculate_total()` - Recalculates the total amount +- `update_status()` - Updates the status of the service + +### Sale #### SaleStatus Enum Tracks the status of a sale: @@ -120,11 +219,18 @@ Represents an item within a sale. - `sale_id`: u32 - Parent sale ID - `product_id`: u32 - ID of the product sold - `name`: String - Product name at time of sale +- `description`: String - Detailed description of the item +- `comments`: String - Additional notes or comments about the item - `quantity`: i32 - Number of items purchased - `unit_price`: Currency - Price per unit -- `subtotal`: Currency - Total price for this item (calculated) +- `subtotal`: Currency - Total price for this item before tax (calculated) +- `tax_rate`: f64 - Tax rate as a percentage (e.g., 20.0 for 20%) +- `tax_amount`: Currency - Calculated tax amount for this item - `active_till`: DateTime - When item/service expires +**Methods:** +- `total_with_tax()` - Returns the total amount including tax + **Builder:** - `SaleItemBuilder` - Provides a fluent interface for creating SaleItem instances @@ -134,18 +240,24 @@ Represents a complete sale transaction. **Properties:** - `id`: u32 - Unique identifier - `company_id`: u32 - ID of the company making the sale +- `customer_id`: u32 - ID of the customer making the purchase (to be added) - `buyer_name`: String - Name of the buyer - `buyer_email`: String - Email of the buyer -- `total_amount`: Currency - Total sale amount +- `subtotal_amount`: Currency - Total sale amount before tax +- `tax_amount`: Currency - Total tax amount for the sale +- `total_amount`: Currency - Total sale amount including tax - `status`: SaleStatus - Current sale status +- `service_id`: Option - ID of the service created from this sale (to be added) - `sale_date`: DateTime - When sale occurred - `created_at`: DateTime - Creation timestamp - `updated_at`: DateTime - Last update timestamp - `items`: Vec - List of items in the sale **Methods:** -- `add_item()` - Adds an item to the sale and updates total +- `add_item()` - Adds an item to the sale and updates totals - `update_status()` - Updates the status of the sale +- `recalculate_totals()` - Recalculates all totals based on items +- `create_service()` - Creates a service from this sale (to be added) **Builder:** - `SaleBuilder` - Provides a fluent interface for creating Sale instances @@ -223,6 +335,7 @@ let item = SaleItemBuilder::new() .name("Premium Service") .quantity(1) .unit_price(unit_price) + .tax_rate(20.0) // 20% tax rate .active_till(now + Duration::days(30)) .build() .expect("Failed to build sale item"); @@ -241,6 +354,29 @@ let mut sale = SaleBuilder::new() // Update the sale status sale.update_status(SaleStatus::Completed); + +// The sale now contains: +// - subtotal_amount: The sum of all items before tax +// - tax_amount: The sum of all tax amounts +// - total_amount: The total including tax +``` + +### Relationship Between Sale and Invoice + +The Sale model represents what is sold to a customer (products or services), including tax calculations. The Invoice model represents the billing document for that sale. + +An InvoiceItem can be linked to a Sale via the `sale_id` field, establishing a connection between what was sold and how it's billed. + +```rust +// Create an invoice item linked to a sale +let invoice_item = InvoiceItemBuilder::new() + .id(1) + .invoice_id(1) + .description("Premium Service") + .amount(sale.total_amount.clone()) // Use the total amount from the sale + .sale_id(sale.id) // Link to the sale + .build() + .expect("Failed to build invoice item"); ``` ## Database Operations @@ -266,4 +402,125 @@ These methods are available for all root objects: - `insert_product`, `get_product`, `delete_product`, `list_products` for Product - `insert_currency`, `get_currency`, `delete_currency`, `list_currencies` for Currency - `insert_sale`, `get_sale`, `delete_sale`, `list_sales` for Sale +- `insert_service`, `get_service`, `delete_service`, `list_services` for Service +- `insert_invoice`, `get_invoice`, `delete_invoice`, `list_invoices` for Invoice +- `insert_customer`, `get_customer`, `delete_customer`, `list_customers` for Customer +### Invoice (Root Object) + +#### InvoiceStatus Enum +Tracks the status of an invoice: +- `Draft` - Invoice is in draft state +- `Sent` - Invoice has been sent to the customer +- `Paid` - Invoice has been paid +- `Overdue` - Invoice is past due date +- `Cancelled` - Invoice has been cancelled + +#### PaymentStatus Enum +Tracks the payment status of an invoice: +- `Unpaid` - Invoice has not been paid +- `PartiallyPaid` - Invoice has been partially paid +- `Paid` - Invoice has been fully paid + +#### Payment +Represents a payment made against an invoice. + +**Properties:** +- `amount`: Currency - Payment amount +- `date`: DateTime - Payment date +- `method`: String - Payment method +- `comment`: String - Payment comment + +#### InvoiceItem +Represents an item in an invoice. + +**Properties:** +- `id`: u32 - Unique identifier +- `invoice_id`: u32 - Parent invoice ID +- `description`: String - Item description +- `amount`: Currency - Item amount +- `service_id`: Option - ID of the service this item is for +- `sale_id`: Option - ID of the sale this item is for + +**Methods:** +- `link_to_service()` - Links the invoice item to a service +- `link_to_sale()` - Links the invoice item to a sale + +#### Invoice +Represents an invoice sent to a customer. + +**Properties:** +- `id`: u32 - Unique identifier +- `customer_id`: u32 - ID of the customer being invoiced +- `total_amount`: Currency - Total invoice amount +- `balance_due`: Currency - Amount still due +- `status`: InvoiceStatus - Current invoice status +- `payment_status`: PaymentStatus - Current payment status +- `issue_date`: DateTime - When invoice was issued +- `due_date`: DateTime - When payment is due +- `created_at`: DateTime - Creation timestamp +- `updated_at`: DateTime - Last update timestamp +- `items`: Vec - List of items in the invoice +- `payments`: Vec - List of payments made + +**Methods:** +- `add_item()` - Adds an item to the invoice +- `calculate_total()` - Calculates the total amount +- `add_payment()` - Adds a payment to the invoice +- `calculate_balance()` - Calculates the balance due +- `update_payment_status()` - Updates the payment status +- `update_status()` - Updates the status of the invoice +- `is_overdue()` - Checks if the invoice is overdue +- `check_if_overdue()` - Marks the invoice as overdue if past due date + +### Relationships Between Models + +#### Product/Service Templates and Instances + +Products and Services can be marked as templates (`is_template=true`). When a customer purchases a product or service, a copy is created from the template with the specific details of what was sold. + +#### Sale to Service Relationship + +When a product of type `Service` is sold, a Service instance can be created from the Sale: + +```rust +// Create a service from a sale +let service = sale.create_service( + service_id, + ServiceStatus::Active, + BillingFrequency::Monthly +); +``` + +#### Sale to Invoice Relationship + +An Invoice is created from a Sale to handle billing and payment tracking: + +```rust +// Create an invoice from a sale +let invoice = Invoice::from_sale( + invoice_id, + sale, + due_date +); +``` + +#### Customer-Centric View + +The models allow tracking all customer interactions: + +- What products/services they've purchased (via Sale records) +- What ongoing services they have (via Service records) +- What they've been invoiced for (via Invoice records) +- What they've paid (via Payment records in Invoices) + +```rust +// Get all sales for a customer +let customer_sales = db.list_sales_by_customer(customer_id); + +// Get all services for a customer +let customer_services = db.list_services_by_customer(customer_id); + +// Get all invoices for a customer +let customer_invoices = db.list_invoices_by_customer(customer_id); +``` diff --git a/herodb/src/models/biz/business_models_plan.md b/herodb/src/models/biz/business_models_plan.md deleted file mode 100644 index d68ec76..0000000 --- a/herodb/src/models/biz/business_models_plan.md +++ /dev/null @@ -1,371 +0,0 @@ -# Business Models Implementation Plan - -## Overview - -This document outlines the plan for implementing new business models in the codebase: - -1. **Service**: For tracking recurring payments (similar to Sale) -2. **Customer**: For storing customer information -3. **Contract**: For linking services or sales to customers -4. **Invoice**: For invoicing customers - -## Model Diagrams - -### Core Models and Relationships - -```mermaid -classDiagram - class Service { - +id: u32 - +customer_id: u32 - +total_amount: Currency - +status: ServiceStatus - +billing_frequency: BillingFrequency - +service_date: DateTime~Utc~ - +created_at: DateTime~Utc~ - +updated_at: DateTime~Utc~ - +items: Vec~ServiceItem~ - +calculate_total() - } - - class ServiceItem { - +id: u32 - +service_id: u32 - +name: String - +quantity: i32 - +unit_price: Currency - +subtotal: Currency - +tax_rate: f64 - +tax_amount: Currency - +is_taxable: bool - +active_till: DateTime~Utc~ - } - - class Customer { - +id: u32 - +name: String - +description: String - +pubkey: String - +contact_ids: Vec~u32~ - +created_at: DateTime~Utc~ - +updated_at: DateTime~Utc~ - } - - class Contract { - +id: u32 - +customer_id: u32 - +service_id: Option~u32~ - +sale_id: Option~u32~ - +terms: String - +start_date: DateTime~Utc~ - +end_date: DateTime~Utc~ - +auto_renewal: bool - +renewal_terms: String - +status: ContractStatus - +created_at: DateTime~Utc~ - +updated_at: DateTime~Utc~ - } - - class Invoice { - +id: u32 - +customer_id: u32 - +total_amount: Currency - +balance_due: Currency - +status: InvoiceStatus - +payment_status: PaymentStatus - +issue_date: DateTime~Utc~ - +due_date: DateTime~Utc~ - +created_at: DateTime~Utc~ - +updated_at: DateTime~Utc~ - +items: Vec~InvoiceItem~ - +payments: Vec~Payment~ - } - - class InvoiceItem { - +id: u32 - +invoice_id: u32 - +description: String - +amount: Currency - +service_id: Option~u32~ - +sale_id: Option~u32~ - } - - class Payment { - +amount: Currency - +date: DateTime~Utc~ - +method: String - } - - Service "1" -- "many" ServiceItem : contains - Customer "1" -- "many" Service : has - Customer "1" -- "many" Contract : has - Contract "1" -- "0..1" Service : references - Contract "1" -- "0..1" Sale : references - Invoice "1" -- "many" InvoiceItem : contains - Invoice "1" -- "many" Payment : contains - Customer "1" -- "many" Invoice : has - InvoiceItem "1" -- "0..1" Service : references - InvoiceItem "1" -- "0..1" Sale : references -``` - -### Enums and Supporting Types - -```mermaid -classDiagram - class BillingFrequency { - <> - Hourly - Daily - Weekly - Monthly - Yearly - } - - class ServiceStatus { - <> - Active - Paused - Cancelled - Completed - } - - class ContractStatus { - <> - Active - Expired - Terminated - } - - class InvoiceStatus { - <> - Draft - Sent - Paid - Overdue - Cancelled - } - - class PaymentStatus { - <> - Unpaid - PartiallyPaid - Paid - } - - Service -- ServiceStatus : has - Service -- BillingFrequency : has - Contract -- ContractStatus : has - Invoice -- InvoiceStatus : has - Invoice -- PaymentStatus : has -``` - -## Detailed Implementation Plan - -### 1. Service and ServiceItem (service.rs) - -The Service model will be similar to Sale but designed for recurring payments: - -- **Service**: Main struct for tracking recurring services - - Fields: - - id: u32 - - customer_id: u32 - - total_amount: Currency - - status: ServiceStatus - - billing_frequency: BillingFrequency - - service_date: DateTime - - created_at: DateTime - - updated_at: DateTime - - items: Vec - - Methods: - - calculate_total(): Updates the total_amount based on all items - - add_item(item: ServiceItem): Adds an item and updates the total - - update_status(status: ServiceStatus): Updates the status and timestamp - -- **ServiceItem**: Items within a service (similar to SaleItem) - - Fields: - - id: u32 - - service_id: u32 - - name: String - - quantity: i32 - - unit_price: Currency - - subtotal: Currency - - tax_rate: f64 - - tax_amount: Currency - - is_taxable: bool - - active_till: DateTime - - Methods: - - calculate_subtotal(): Calculates subtotal based on quantity and unit_price - - calculate_tax(): Calculates tax amount based on subtotal and tax_rate - -- **BillingFrequency**: Enum for different billing periods - - Variants: Hourly, Daily, Weekly, Monthly, Yearly - -- **ServiceStatus**: Enum for service status - - Variants: Active, Paused, Cancelled, Completed - -### 2. Customer (customer.rs) - -The Customer model will store customer information: - -- **Customer**: Main struct for customer data - - Fields: - - id: u32 - - name: String - - description: String - - pubkey: String - - contact_ids: Vec - - created_at: DateTime - - updated_at: DateTime - - Methods: - - add_contact(contact_id: u32): Adds a contact ID to the list - - remove_contact(contact_id: u32): Removes a contact ID from the list - -### 3. Contract (contract.rs) - -The Contract model will link services or sales to customers: - -- **Contract**: Main struct for contract data - - Fields: - - id: u32 - - customer_id: u32 - - service_id: Option - - sale_id: Option - - terms: String - - start_date: DateTime - - end_date: DateTime - - auto_renewal: bool - - renewal_terms: String - - status: ContractStatus - - created_at: DateTime - - updated_at: DateTime - - Methods: - - is_active(): bool - Checks if the contract is currently active - - is_expired(): bool - Checks if the contract has expired - - renew(): Updates the contract dates based on renewal terms - -- **ContractStatus**: Enum for contract status - - Variants: Active, Expired, Terminated - -### 4. Invoice (invoice.rs) - -The Invoice model will handle billing: - -- **Invoice**: Main struct for invoice data - - Fields: - - id: u32 - - customer_id: u32 - - total_amount: Currency - - balance_due: Currency - - status: InvoiceStatus - - payment_status: PaymentStatus - - issue_date: DateTime - - due_date: DateTime - - created_at: DateTime - - updated_at: DateTime - - items: Vec - - payments: Vec - - Methods: - - calculate_total(): Updates the total_amount based on all items - - add_item(item: InvoiceItem): Adds an item and updates the total - - add_payment(payment: Payment): Adds a payment and updates balance_due and payment_status - - update_status(status: InvoiceStatus): Updates the status and timestamp - - calculate_balance(): Updates the balance_due based on total_amount and payments - -- **InvoiceItem**: Items within an invoice - - Fields: - - id: u32 - - invoice_id: u32 - - description: String - - amount: Currency - - service_id: Option - - sale_id: Option - -- **Payment**: Struct for tracking payments - - Fields: - - amount: Currency - - date: DateTime - - method: String - -- **InvoiceStatus**: Enum for invoice status - - Variants: Draft, Sent, Paid, Overdue, Cancelled - -- **PaymentStatus**: Enum for payment status - - Variants: Unpaid, PartiallyPaid, Paid - -### 5. Updates to mod.rs - -We'll need to update the mod.rs file to include the new modules and re-export the types: - -```rust -pub mod currency; -pub mod product; -pub mod sale; -pub mod exchange_rate; -pub mod service; -pub mod customer; -pub mod contract; -pub mod invoice; - -// Re-export all model types for convenience -pub use product::{Product, ProductComponent, ProductType, ProductStatus}; -pub use sale::{Sale, SaleItem, SaleStatus}; -pub use currency::Currency; -pub use exchange_rate::{ExchangeRate, ExchangeRateService, EXCHANGE_RATE_SERVICE}; -pub use service::{Service, ServiceItem, ServiceStatus, BillingFrequency}; -pub use customer::Customer; -pub use contract::{Contract, ContractStatus}; -pub use invoice::{Invoice, InvoiceItem, InvoiceStatus, PaymentStatus, Payment}; - -// Re-export builder types -pub use product::{ProductBuilder, ProductComponentBuilder}; -pub use sale::{SaleBuilder, SaleItemBuilder}; -pub use currency::CurrencyBuilder; -pub use exchange_rate::ExchangeRateBuilder; -pub use service::{ServiceBuilder, ServiceItemBuilder}; -pub use customer::CustomerBuilder; -pub use contract::ContractBuilder; -pub use invoice::{InvoiceBuilder, InvoiceItemBuilder}; -``` - -### 6. Updates to model_methods.rs - -We'll need to update the model_methods.rs file to implement the model methods for the new models: - -```rust -use crate::db::db::DB; -use crate::db::base::{SledDBResult, SledModel}; -use crate::impl_model_methods; -use crate::models::biz::{Product, Sale, Currency, ExchangeRate, Service, Customer, Contract, Invoice}; - -// Implement model-specific methods for Product -impl_model_methods!(Product, product, products); - -// Implement model-specific methods for Sale -impl_model_methods!(Sale, sale, sales); - -// Implement model-specific methods for Currency -impl_model_methods!(Currency, currency, currencies); - -// Implement model-specific methods for ExchangeRate -impl_model_methods!(ExchangeRate, exchange_rate, exchange_rates); - -// Implement model-specific methods for Service -impl_model_methods!(Service, service, services); - -// Implement model-specific methods for Customer -impl_model_methods!(Customer, customer, customers); - -// Implement model-specific methods for Contract -impl_model_methods!(Contract, contract, contracts); - -// Implement model-specific methods for Invoice -impl_model_methods!(Invoice, invoice, invoices); -``` - -## Implementation Approach - -1. Create the new model files (service.rs, customer.rs, contract.rs, invoice.rs) -2. Implement the structs, enums, and methods for each model -3. Update mod.rs to include the new modules and re-export the types -4. Update model_methods.rs to implement the model methods for the new models -5. Test the new models with example code \ No newline at end of file diff --git a/herodb/src/models/biz/sale.rs b/herodb/src/models/biz/sale.rs index dda10f7..00f7c43 100644 --- a/herodb/src/models/biz/sale.rs +++ b/herodb/src/models/biz/sale.rs @@ -21,9 +21,13 @@ pub struct SaleItem { pub sale_id: u32, pub product_id: u32, pub name: String, + pub description: String, // Description of the item + pub comments: String, // Additional comments about the item pub quantity: i32, pub unit_price: Currency, pub subtotal: Currency, + pub tax_rate: f64, // Tax rate as a percentage (e.g., 20.0 for 20%) + pub tax_amount: Currency, // Calculated tax amount pub active_till: DateTime, // after this product no longer active if e.g. a service } @@ -34,28 +38,50 @@ impl SaleItem { sale_id: u32, product_id: u32, name: String, + description: String, + comments: String, quantity: i32, unit_price: Currency, + tax_rate: f64, active_till: DateTime, ) -> Self { - // Calculate subtotal + // Calculate subtotal (before tax) let amount = unit_price.amount * quantity as f64; let subtotal = Currency { amount, currency_code: unit_price.currency_code.clone(), }; + + // Calculate tax amount + let tax_amount_value = subtotal.amount * (tax_rate / 100.0); + let tax_amount = Currency { + amount: tax_amount_value, + currency_code: unit_price.currency_code.clone(), + }; Self { id, sale_id, product_id, name, + description, + comments, quantity, unit_price, subtotal, + tax_rate, + tax_amount, active_till, } } + + /// Get the total amount including tax + pub fn total_with_tax(&self) -> Currency { + Currency { + amount: self.subtotal.amount + self.tax_amount.amount, + currency_code: self.subtotal.currency_code.clone(), + } + } } /// Builder for SaleItem @@ -65,9 +91,13 @@ pub struct SaleItemBuilder { sale_id: Option, product_id: Option, name: Option, + description: Option, + comments: Option, quantity: Option, unit_price: Option, subtotal: Option, + tax_rate: Option, + tax_amount: Option, active_till: Option>, } @@ -79,9 +109,13 @@ impl SaleItemBuilder { sale_id: None, product_id: None, name: None, + description: None, + comments: None, quantity: None, unit_price: None, subtotal: None, + tax_rate: None, + tax_amount: None, active_till: None, } } @@ -109,6 +143,18 @@ impl SaleItemBuilder { self.name = Some(name.into()); self } + + /// Set the description + pub fn description>(mut self, description: S) -> Self { + self.description = Some(description.into()); + self + } + + /// Set the comments + pub fn comments>(mut self, comments: S) -> Self { + self.comments = Some(comments.into()); + self + } /// Set the quantity pub fn quantity(mut self, quantity: i32) -> Self { @@ -122,6 +168,12 @@ impl SaleItemBuilder { self } + /// Set the tax_rate + pub fn tax_rate(mut self, tax_rate: f64) -> Self { + self.tax_rate = Some(tax_rate); + self + } + /// Set the active_till pub fn active_till(mut self, active_till: DateTime) -> Self { self.active_till = Some(active_till); @@ -132,6 +184,7 @@ impl SaleItemBuilder { pub fn build(self) -> Result { let unit_price = self.unit_price.ok_or("unit_price is required")?; let quantity = self.quantity.ok_or("quantity is required")?; + let tax_rate = self.tax_rate.unwrap_or(0.0); // Default to 0% tax if not specified // Calculate subtotal let amount = unit_price.amount * quantity as f64; @@ -139,15 +192,26 @@ impl SaleItemBuilder { amount, currency_code: unit_price.currency_code.clone(), }; + + // Calculate tax amount + let tax_amount_value = subtotal.amount * (tax_rate / 100.0); + let tax_amount = Currency { + amount: tax_amount_value, + currency_code: unit_price.currency_code.clone(), + }; Ok(SaleItem { id: self.id.ok_or("id is required")?, sale_id: self.sale_id.ok_or("sale_id is required")?, product_id: self.product_id.ok_or("product_id is required")?, name: self.name.ok_or("name is required")?, + description: self.description.unwrap_or_default(), + comments: self.comments.unwrap_or_default(), quantity, unit_price, subtotal, + tax_rate, + tax_amount, active_till: self.active_till.ok_or("active_till is required")?, }) } @@ -158,10 +222,14 @@ impl SaleItemBuilder { pub struct Sale { pub id: u32, pub company_id: u32, + pub customer_id: u32, // ID of the customer making the purchase pub buyer_name: String, pub buyer_email: String, - pub total_amount: Currency, + pub subtotal_amount: Currency, // Total before tax + pub tax_amount: Currency, // Total tax + pub total_amount: Currency, // Total including tax pub status: SaleStatus, + pub service_id: Option, // ID of the service created from this sale (if applicable) pub sale_date: DateTime, pub created_at: DateTime, pub updated_at: DateTime, @@ -175,22 +243,29 @@ impl Sale { pub fn new( id: u32, company_id: u32, + customer_id: u32, buyer_name: String, buyer_email: String, currency_code: String, status: SaleStatus, ) -> Self { let now = Utc::now(); + let zero_currency = Currency { + amount: 0.0, + currency_code: currency_code.clone(), + }; + Self { id, company_id, + customer_id, buyer_name, buyer_email, - total_amount: Currency { - amount: 0.0, - currency_code, - }, + subtotal_amount: zero_currency.clone(), + tax_amount: zero_currency.clone(), + total_amount: zero_currency, status, + service_id: None, sale_date: now, created_at: now, updated_at: now, @@ -203,17 +278,27 @@ impl Sale { // Make sure the item's sale_id matches this sale assert_eq!(self.id, item.sale_id, "Item sale_id must match sale id"); - // Update the total amount + // Update the amounts if self.items.is_empty() { - // First item, initialize the total amount with the same currency - self.total_amount = Currency { + // First item, initialize the amounts with the same currency + self.subtotal_amount = Currency { amount: item.subtotal.amount, currency_code: item.subtotal.currency_code.clone(), }; + self.tax_amount = Currency { + amount: item.tax_amount.amount, + currency_code: item.tax_amount.currency_code.clone(), + }; + self.total_amount = Currency { + amount: item.subtotal.amount + item.tax_amount.amount, + currency_code: item.subtotal.currency_code.clone(), + }; } else { - // Add to the existing total + // Add to the existing totals // (Assumes all items have the same currency) - self.total_amount.amount += item.subtotal.amount; + self.subtotal_amount.amount += item.subtotal.amount; + self.tax_amount.amount += item.tax_amount.amount; + self.total_amount.amount = self.subtotal_amount.amount + self.tax_amount.amount; } // Add the item to the list @@ -222,12 +307,93 @@ impl Sale { // Update the sale timestamp self.updated_at = Utc::now(); } + + /// Recalculate all totals based on items + pub fn recalculate_totals(&mut self) { + if self.items.is_empty() { + return; + } + + // Get the currency code from the first item + let currency_code = self.items[0].subtotal.currency_code.clone(); + + // Calculate the totals + let mut subtotal = 0.0; + let mut tax_total = 0.0; + + for item in &self.items { + subtotal += item.subtotal.amount; + tax_total += item.tax_amount.amount; + } + + // Update the amounts + self.subtotal_amount = Currency { + amount: subtotal, + currency_code: currency_code.clone(), + }; + self.tax_amount = Currency { + amount: tax_total, + currency_code: currency_code.clone(), + }; + self.total_amount = Currency { + amount: subtotal + tax_total, + currency_code, + }; + + // Update the timestamp + self.updated_at = Utc::now(); + } /// Update the status of the sale pub fn update_status(&mut self, status: SaleStatus) { self.status = status; self.updated_at = Utc::now(); } + + /// Create a service from this sale + /// This method should be called when a product of type Service is sold + pub fn create_service(&mut self, service_id: u32, status: crate::models::biz::ServiceStatus, billing_frequency: crate::models::biz::BillingFrequency) -> Result { + use crate::models::biz::{Service, ServiceItem, ServiceStatus, BillingFrequency}; + + // Create a new service + let mut service = Service::new( + service_id, + self.customer_id, + self.total_amount.currency_code.clone(), + status, + billing_frequency, + ); + + // Convert sale items to service items + for sale_item in &self.items { + // Check if the product is a service type + // In a real implementation, you would check the product type from the database + + // Create a service item from the sale item + let service_item = ServiceItem::new( + sale_item.id, + service_id, + sale_item.product_id, + sale_item.name.clone(), + sale_item.description.clone(), // Copy description from sale item + sale_item.comments.clone(), // Copy comments from sale item + sale_item.quantity, + sale_item.unit_price.clone(), + sale_item.tax_rate, + true, // is_taxable + sale_item.active_till, + ); + + // Add the service item to the service + service.add_item(service_item); + } + + // Link this sale to the service + self.service_id = Some(service_id); + self.updated_at = Utc::now(); + + Ok(service) + } } /// Builder for Sale @@ -235,10 +401,14 @@ impl Sale { pub struct SaleBuilder { id: Option, company_id: Option, + customer_id: Option, buyer_name: Option, buyer_email: Option, + subtotal_amount: Option, + tax_amount: Option, total_amount: Option, status: Option, + service_id: Option, sale_date: Option>, created_at: Option>, updated_at: Option>, @@ -252,10 +422,14 @@ impl SaleBuilder { Self { id: None, company_id: None, + customer_id: None, buyer_name: None, buyer_email: None, + subtotal_amount: None, + tax_amount: None, total_amount: None, status: None, + service_id: None, sale_date: None, created_at: None, updated_at: None, @@ -275,6 +449,12 @@ impl SaleBuilder { self.company_id = Some(company_id); self } + + /// Set the customer_id + pub fn customer_id(mut self, customer_id: u32) -> Self { + self.customer_id = Some(customer_id); + self + } /// Set the buyer_name pub fn buyer_name>(mut self, buyer_name: S) -> Self { @@ -299,6 +479,12 @@ impl SaleBuilder { self.status = Some(status); self } + + /// Set the service_id + pub fn service_id(mut self, service_id: u32) -> Self { + self.service_id = Some(service_id); + self + } /// Set the sale_date pub fn sale_date(mut self, sale_date: DateTime) -> Self { @@ -318,39 +504,45 @@ impl SaleBuilder { let id = self.id.ok_or("id is required")?; let currency_code = self.currency_code.ok_or("currency_code is required")?; - // Initialize with empty total amount + // Initialize with empty amounts + let mut subtotal_amount = Currency { + amount: 0.0, + currency_code: currency_code.clone(), + }; + let mut tax_amount = Currency { + amount: 0.0, + currency_code: currency_code.clone(), + }; let mut total_amount = Currency { amount: 0.0, currency_code: currency_code.clone(), }; - // Calculate total amount from items + // Calculate amounts from items for item in &self.items { // Make sure the item's sale_id matches this sale if item.sale_id != id { return Err("Item sale_id must match sale id"); } - if total_amount.amount == 0.0 { - // First item, initialize the total amount with the same currency - total_amount = Currency { - amount: item.subtotal.amount, - currency_code: item.subtotal.currency_code.clone(), - }; - } else { - // Add to the existing total - // (Assumes all items have the same currency) - total_amount.amount += item.subtotal.amount; - } + subtotal_amount.amount += item.subtotal.amount; + tax_amount.amount += item.tax_amount.amount; } + + // Calculate total amount + total_amount.amount = subtotal_amount.amount + tax_amount.amount; Ok(Sale { id, company_id: self.company_id.ok_or("company_id is required")?, + customer_id: self.customer_id.ok_or("customer_id is required")?, buyer_name: self.buyer_name.ok_or("buyer_name is required")?, buyer_email: self.buyer_email.ok_or("buyer_email is required")?, + subtotal_amount: self.subtotal_amount.unwrap_or(subtotal_amount), + tax_amount: self.tax_amount.unwrap_or(tax_amount), total_amount: self.total_amount.unwrap_or(total_amount), status: self.status.ok_or("status is required")?, + service_id: self.service_id, sale_date: self.sale_date.unwrap_or(now), created_at: self.created_at.unwrap_or(now), updated_at: self.updated_at.unwrap_or(now), diff --git a/herodb/src/models/biz/service.rs b/herodb/src/models/biz/service.rs index 2c1eb05..c22b57f 100644 --- a/herodb/src/models/biz/service.rs +++ b/herodb/src/models/biz/service.rs @@ -29,6 +29,8 @@ pub struct ServiceItem { pub service_id: u32, pub product_id: u32, pub name: String, + pub description: String, // Description of the service item + pub comments: String, // Additional comments about the service item pub quantity: i32, pub unit_price: Currency, pub subtotal: Currency, @@ -45,6 +47,8 @@ impl ServiceItem { service_id: u32, product_id: u32, name: String, + description: String, + comments: String, quantity: i32, unit_price: Currency, tax_rate: f64, @@ -76,6 +80,8 @@ impl ServiceItem { service_id, product_id, name, + description, + comments, quantity, unit_price, subtotal, @@ -117,6 +123,8 @@ pub struct ServiceItemBuilder { service_id: Option, product_id: Option, name: Option, + description: Option, + comments: Option, quantity: Option, unit_price: Option, subtotal: Option, @@ -134,6 +142,8 @@ impl ServiceItemBuilder { service_id: None, product_id: None, name: None, + description: None, + comments: None, quantity: None, unit_price: None, subtotal: None, @@ -167,6 +177,18 @@ impl ServiceItemBuilder { self.name = Some(name.into()); self } + + /// Set the description + pub fn description>(mut self, description: S) -> Self { + self.description = Some(description.into()); + self + } + + /// Set the comments + pub fn comments>(mut self, comments: S) -> Self { + self.comments = Some(comments.into()); + self + } /// Set the quantity pub fn quantity(mut self, quantity: i32) -> Self { @@ -230,6 +252,8 @@ impl ServiceItemBuilder { service_id: self.service_id.ok_or("service_id is required")?, product_id: self.product_id.ok_or("product_id is required")?, name: self.name.ok_or("name is required")?, + description: self.description.unwrap_or_default(), + comments: self.comments.unwrap_or_default(), quantity, unit_price, subtotal, From 6b169f578635d7c19d5bb0ff7c77cf0dc68117b0 Mon Sep 17 00:00:00 2001 From: despiegk Date: Sat, 19 Apr 2025 18:57:09 +0200 Subject: [PATCH 09/10] ... --- herodb/Cargo.toml | 8 + herodb/examples/business_models_demo.rs | 428 ++++++++++++++++++++++++ herodb/src/cmd/dbexample_biz/README.md | 48 +++ herodb/src/cmd/dbexample_biz/main.rs | 275 +++++++++++++++ herodb/src/cmd/dbexample_biz/mod.rs | 10 + herodb/src/cmd/mod.rs | 7 + herodb/src/lib.rs | 4 +- herodb/src/rhaiengine/mod.rs | 7 + 8 files changed, 786 insertions(+), 1 deletion(-) create mode 100644 herodb/examples/business_models_demo.rs create mode 100644 herodb/src/cmd/dbexample_biz/README.md create mode 100644 herodb/src/cmd/dbexample_biz/main.rs create mode 100644 herodb/src/cmd/dbexample_biz/mod.rs create mode 100644 herodb/src/cmd/mod.rs create mode 100644 herodb/src/rhaiengine/mod.rs diff --git a/herodb/Cargo.toml b/herodb/Cargo.toml index 8d34a4c..795f874 100644 --- a/herodb/Cargo.toml +++ b/herodb/Cargo.toml @@ -27,6 +27,10 @@ lazy_static = "1.4.0" name = "rhai_demo" path = "examples/rhai_demo.rs" +[[example]] +name = "business_models_demo" +path = "examples/business_models_demo.rs" + [[bin]] name = "dbexample_prod" path = "src/cmd/dbexample_prod/main.rs" @@ -38,3 +42,7 @@ path = "src/cmd/dbexample_mcc/main.rs" [[bin]] name = "dbexample_gov" path = "src/cmd/dbexample_gov/main.rs" + +[[bin]] +name = "dbexample_biz" +path = "src/cmd/dbexample_biz/main.rs" diff --git a/herodb/examples/business_models_demo.rs b/herodb/examples/business_models_demo.rs new file mode 100644 index 0000000..31a46b8 --- /dev/null +++ b/herodb/examples/business_models_demo.rs @@ -0,0 +1,428 @@ +use chrono::{DateTime, Duration, Utc}; +use serde::{Deserialize, Serialize}; + +/// This example demonstrates business models in action: +/// 1. Defining products (2 types of server nodes) +/// 2. Defining components (parts of the nodes) +/// 3. Setting up pricing +/// 4. Creating a function to check which products can be bought +/// 5. Simulating a user buying a product +/// 6. Generating an invoice +/// 7. Simulating payment + +fn main() { + println!("Business Models Example"); + println!("=======================\n"); + + // Create a customer + let customer = create_customer(); + println!("Created customer: {}", customer.name); + + // Define products (server nodes) + let (standard_node, premium_node) = create_server_products(); + println!("Created server products:"); + println!(" - Standard Node: ${} {}", standard_node.price.amount, standard_node.price.currency_code); + println!(" - Premium Node: ${} {}", premium_node.price.amount, premium_node.price.currency_code); + + // Check which products can be purchased + println!("\nChecking which products can be purchased:"); + let purchasable_products = get_purchasable_products(&[&standard_node, &premium_node]); + for product in purchasable_products { + println!(" - {} is available for purchase", product.name); + } + + // Simulate a user buying a product + println!("\nSimulating purchase of a Premium Node:"); + let sale = create_sale(&customer, &premium_node); + println!(" - Sale created with ID: {}", sale.id); + println!(" - Total amount: ${} {}", sale.total_amount.amount, sale.total_amount.currency_code); + + // Generate an invoice + println!("\nGenerating invoice:"); + let invoice = create_invoice(&customer, &sale); + println!(" - Invoice created with ID: {}", invoice.id); + println!(" - Total amount: ${} {}", invoice.total_amount.amount, invoice.total_amount.currency_code); + println!(" - Due date: {}", invoice.due_date); + println!(" - Status: {:?}", invoice.status); + + // Simulate payment + println!("\nSimulating payment:"); + let paid_invoice = process_payment(invoice); + println!(" - Payment processed"); + println!(" - New balance due: ${} {}", paid_invoice.balance_due.amount, paid_invoice.balance_due.currency_code); + println!(" - Payment status: {:?}", paid_invoice.payment_status); + println!(" - Invoice status: {:?}", paid_invoice.status); + + println!("\nBusiness transaction completed successfully!"); +} + +// ===== Model Definitions ===== + +// Currency represents a monetary value with amount and currency code +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Currency { + amount: f64, + currency_code: String, +} + +// Customer represents a customer who can purchase products +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Customer { + id: u32, + name: String, + description: String, + pubkey: String, +} + +// ProductType represents the type of a product +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum ProductType { + Product, + Service, +} + +// ProductStatus represents the status of a product +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum ProductStatus { + Available, + Unavailable, +} + +// ProductComponent represents a component of a product +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ProductComponent { + id: i64, + name: String, + description: String, + quantity: i64, +} + +// Product represents a product or service offered +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Product { + id: i64, + name: String, + description: String, + price: Currency, + type_: ProductType, + category: String, + status: ProductStatus, + max_amount: i64, + purchase_till: DateTime, + active_till: DateTime, + components: Vec, +} + +// SaleStatus represents the status of a sale +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum SaleStatus { + Pending, + Completed, + Cancelled, +} + +// SaleItem represents an item in a sale +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SaleItem { + id: u32, + sale_id: u32, + product_id: u32, + name: String, + description: String, + comments: String, + quantity: i32, + unit_price: Currency, + subtotal: Currency, + tax_rate: f64, + tax_amount: Currency, + active_till: DateTime, +} + +// Sale represents a sale of products or services +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Sale { + id: u32, + company_id: u32, + customer_id: u32, + buyer_name: String, + buyer_email: String, + subtotal_amount: Currency, + tax_amount: Currency, + total_amount: Currency, + status: SaleStatus, + service_id: Option, + sale_date: DateTime, + items: Vec, +} + +// InvoiceStatus represents the status of an invoice +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum InvoiceStatus { + Draft, + Sent, + Paid, + Overdue, + Cancelled, +} + +// PaymentStatus represents the payment status of an invoice +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +enum PaymentStatus { + Unpaid, + PartiallyPaid, + Paid, +} + +// Payment represents a payment made against an invoice +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Payment { + amount: Currency, + date: DateTime, + method: String, + comment: String, +} + +// InvoiceItem represents an item in an invoice +#[derive(Debug, Clone, Serialize, Deserialize)] +struct InvoiceItem { + id: u32, + invoice_id: u32, + description: String, + amount: Currency, + sale_id: Option, +} + +// Invoice represents an invoice sent to a customer +#[derive(Debug, Clone, Serialize, Deserialize)] +struct Invoice { + id: u32, + customer_id: u32, + total_amount: Currency, + balance_due: Currency, + status: InvoiceStatus, + payment_status: PaymentStatus, + issue_date: DateTime, + due_date: DateTime, + items: Vec, + payments: Vec, +} + +// ===== Implementation Functions ===== + +// Create a customer for our example +fn create_customer() -> Customer { + Customer { + id: 1, + name: "TechCorp Inc.".to_string(), + description: "Enterprise technology company".to_string(), + pubkey: "tech-corp-public-key-123".to_string(), + } +} + +// Create two types of server node products with their components +fn create_server_products() -> (Product, Product) { + let now = Utc::now(); + + // Create currency for pricing + let usd = |amount| { + Currency { + amount, + currency_code: "USD".to_string(), + } + }; + + // Standard Node Components + let cpu_standard = ProductComponent { + id: 1, + name: "CPU".to_string(), + description: "4-core CPU".to_string(), + quantity: 1, + }; + + let ram_standard = ProductComponent { + id: 2, + name: "RAM".to_string(), + description: "16GB RAM".to_string(), + quantity: 1, + }; + + let storage_standard = ProductComponent { + id: 3, + name: "Storage".to_string(), + description: "500GB SSD".to_string(), + quantity: 1, + }; + + // Premium Node Components + let cpu_premium = ProductComponent { + id: 4, + name: "CPU".to_string(), + description: "8-core CPU".to_string(), + quantity: 1, + }; + + let ram_premium = ProductComponent { + id: 5, + name: "RAM".to_string(), + description: "32GB RAM".to_string(), + quantity: 1, + }; + + let storage_premium = ProductComponent { + id: 6, + name: "Storage".to_string(), + description: "1TB SSD".to_string(), + quantity: 1, + }; + + let gpu_premium = ProductComponent { + id: 7, + name: "GPU".to_string(), + description: "Dedicated GPU".to_string(), + quantity: 1, + }; + + // Create Standard Node Product + let standard_node = Product { + id: 1, + name: "Standard Server Node".to_string(), + description: "Basic server node for general workloads".to_string(), + price: usd(99.99), + type_: ProductType::Product, + category: "Servers".to_string(), + status: ProductStatus::Available, + max_amount: 100, + purchase_till: now + Duration::days(365), + active_till: now + Duration::days(365), + components: vec![cpu_standard, ram_standard, storage_standard], + }; + + // Create Premium Node Product + let premium_node = Product { + id: 2, + name: "Premium Server Node".to_string(), + description: "High-performance server node for demanding workloads".to_string(), + price: usd(199.99), + type_: ProductType::Product, + category: "Servers".to_string(), + status: ProductStatus::Available, + max_amount: 50, + purchase_till: now + Duration::days(365), + active_till: now + Duration::days(365), + components: vec![cpu_premium, ram_premium, storage_premium, gpu_premium], + }; + + (standard_node, premium_node) +} + +// Check which products can be purchased +fn get_purchasable_products<'a>(products: &[&'a Product]) -> Vec<&'a Product> { + products.iter() + .filter(|p| p.status == ProductStatus::Available && Utc::now() <= p.purchase_till) + .copied() + .collect() +} + +// Create a sale for a customer buying a product +fn create_sale(customer: &Customer, product: &Product) -> Sale { + let now = Utc::now(); + let active_till = now + Duration::days(365); + + // Create a sale item for the product + let sale_item = SaleItem { + id: 1, + sale_id: 1, + product_id: product.id as u32, + name: product.name.clone(), + description: product.description.clone(), + comments: "Customer requested expedited setup".to_string(), + quantity: 1, + unit_price: product.price.clone(), + subtotal: Currency { + amount: product.price.amount * 1.0, + currency_code: product.price.currency_code.clone(), + }, + tax_rate: 10.0, // 10% tax rate + tax_amount: Currency { + amount: product.price.amount * 0.1, + currency_code: product.price.currency_code.clone(), + }, + active_till, + }; + + // Calculate totals + let subtotal = sale_item.subtotal.clone(); + let tax_amount = sale_item.tax_amount.clone(); + let total_amount = Currency { + amount: subtotal.amount + tax_amount.amount, + currency_code: subtotal.currency_code.clone(), + }; + + // Create the sale + Sale { + id: 1, + company_id: 101, // Assuming company ID 101 + customer_id: customer.id, + buyer_name: customer.name.clone(), + buyer_email: "contact@techcorp.com".to_string(), // Example email + subtotal_amount: subtotal, + tax_amount, + total_amount, + status: SaleStatus::Completed, + service_id: None, + sale_date: now, + items: vec![sale_item], + } +} + +// Create an invoice for a sale +fn create_invoice(customer: &Customer, sale: &Sale) -> Invoice { + let now = Utc::now(); + let due_date = now + Duration::days(30); // Due in 30 days + + // Create an invoice item for the sale + let invoice_item = InvoiceItem { + id: 1, + invoice_id: 1, + description: format!("Purchase of {}", sale.items[0].name), + amount: sale.total_amount.clone(), + sale_id: Some(sale.id), + }; + + // Create the invoice + Invoice { + id: 1, + customer_id: customer.id, + total_amount: sale.total_amount.clone(), + balance_due: sale.total_amount.clone(), + status: InvoiceStatus::Sent, + payment_status: PaymentStatus::Unpaid, + issue_date: now, + due_date, + items: vec![invoice_item], + payments: Vec::new(), + } +} + +// Process a payment for an invoice +fn process_payment(mut invoice: Invoice) -> Invoice { + // Create a payment for the full amount + let payment = Payment { + amount: invoice.total_amount.clone(), + date: Utc::now(), + method: "Credit Card".to_string(), + comment: "Payment received via credit card ending in 1234".to_string(), + }; + + // Add the payment to the invoice + invoice.payments.push(payment); + + // Update the balance due + invoice.balance_due.amount = 0.0; + + // Update the payment status + invoice.payment_status = PaymentStatus::Paid; + invoice.status = InvoiceStatus::Paid; + + invoice +} \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_biz/README.md b/herodb/src/cmd/dbexample_biz/README.md new file mode 100644 index 0000000..37cc41c --- /dev/null +++ b/herodb/src/cmd/dbexample_biz/README.md @@ -0,0 +1,48 @@ +# Business Models Example + +This example demonstrates the business models in HeroDB, showcasing a complete business transaction flow from product definition to payment processing. + +## Features Demonstrated + +1. **Product Definition**: Creating two types of server node products with different components and pricing +2. **Component Definition**: Defining the parts that make up each server node (CPU, RAM, Storage, GPU) +3. **Pricing Setup**: Setting up prices for products using the Currency model +4. **Product Availability**: Checking which products can be purchased based on their status and availability +5. **Sales Process**: Simulating a customer purchasing a product +6. **Invoice Generation**: Creating an invoice for the sale +7. **Payment Processing**: Processing a payment for the invoice and updating its status + +## Business Flow + +The example follows this business flow: + +``` +Define Products → Check Availability → Customer Purchase → Generate Invoice → Process Payment +``` + +## Models Used + +- **Product & ProductComponent**: For defining server nodes and their components +- **Customer**: For representing the buyer +- **Sale & SaleItem**: For recording the purchase transaction +- **Invoice & InvoiceItem**: For billing the customer +- **Payment**: For recording the payment + +## Running the Example + +To run this example, use: + +```bash +cargo run --bin dbexample_biz +``` + +The output will show each step of the business process with relevant details. + +## Key Concepts + +- **Builder Pattern**: All models use builders for flexible object creation +- **Status Tracking**: Sales and invoices have status enums to track their state +- **Relationship Modeling**: The example shows how different business entities relate to each other +- **Financial Calculations**: Demonstrates tax and total calculations + +This example provides a template for implementing business logic in your own applications using HeroDB. \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_biz/main.rs b/herodb/src/cmd/dbexample_biz/main.rs new file mode 100644 index 0000000..c03c079 --- /dev/null +++ b/herodb/src/cmd/dbexample_biz/main.rs @@ -0,0 +1,275 @@ +use chrono::{Duration, Utc}; +use crate::models::biz::{ + Currency, CurrencyBuilder, + Product, ProductBuilder, ProductComponent, ProductComponentBuilder, ProductType, ProductStatus, + Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus, + Invoice, InvoiceBuilder, InvoiceItem, InvoiceItemBuilder, InvoiceStatus, Payment, PaymentStatus, + Customer, CustomerBuilder, +}; +use crate::db::base::SledModel; + +/// This example demonstrates the business models in action: +/// 1. Defining products (2 types of server nodes) +/// 2. Defining components (parts of the nodes) +/// 3. Setting up pricing +/// 4. Creating a function to check which products can be bought +/// 5. Simulating a user buying a product +/// 6. Generating an invoice +/// 7. Simulating payment + +fn main() { + println!("Business Models Example"); + println!("=======================\n"); + + // Create a customer + let customer = create_customer(); + println!("Created customer: {}", customer.name); + + // Define products (server nodes) + let (standard_node, premium_node) = create_server_products(); + println!("Created server products:"); + println!(" - Standard Node: ${} {}", standard_node.price.amount, standard_node.price.currency_code); + println!(" - Premium Node: ${} {}", premium_node.price.amount, premium_node.price.currency_code); + + // Check which products can be purchased + println!("\nChecking which products can be purchased:"); + let purchasable_products = get_purchasable_products(&[&standard_node, &premium_node]); + for product in purchasable_products { + println!(" - {} is available for purchase", product.name); + } + + // Simulate a user buying a product + println!("\nSimulating purchase of a Premium Node:"); + let sale = create_sale(&customer, &premium_node); + println!(" - Sale created with ID: {}", sale.id); + println!(" - Total amount: ${} {}", sale.total_amount.amount, sale.total_amount.currency_code); + + // Generate an invoice + println!("\nGenerating invoice:"); + let invoice = create_invoice(&customer, &sale); + println!(" - Invoice created with ID: {}", invoice.id); + println!(" - Total amount: ${} {}", invoice.total_amount.amount, invoice.total_amount.currency_code); + println!(" - Due date: {}", invoice.due_date); + println!(" - Status: {:?}", invoice.status); + + // Simulate payment + println!("\nSimulating payment:"); + let paid_invoice = process_payment(invoice); + println!(" - Payment processed"); + println!(" - New balance due: ${} {}", paid_invoice.balance_due.amount, paid_invoice.balance_due.currency_code); + println!(" - Payment status: {:?}", paid_invoice.payment_status); + println!(" - Invoice status: {:?}", paid_invoice.status); + + println!("\nBusiness transaction completed successfully!"); +} + +/// Create a customer for our example +fn create_customer() -> Customer { + CustomerBuilder::new() + .id(1) + .name("TechCorp Inc.") + .description("Enterprise technology company") + .pubkey("tech-corp-public-key-123") + .build() + .expect("Failed to create customer") +} + +/// Create two types of server node products with their components +fn create_server_products() -> (Product, Product) { + // Create currency for pricing + let usd = |amount| { + CurrencyBuilder::new() + .amount(amount) + .currency_code("USD") + .build() + .expect("Failed to create currency") + }; + + // Standard Node Components + let cpu_standard = ProductComponentBuilder::new() + .id(1) + .name("CPU") + .description("4-core CPU") + .quantity(1) + .build() + .expect("Failed to create CPU component"); + + let ram_standard = ProductComponentBuilder::new() + .id(2) + .name("RAM") + .description("16GB RAM") + .quantity(1) + .build() + .expect("Failed to create RAM component"); + + let storage_standard = ProductComponentBuilder::new() + .id(3) + .name("Storage") + .description("500GB SSD") + .quantity(1) + .build() + .expect("Failed to create Storage component"); + + // Premium Node Components + let cpu_premium = ProductComponentBuilder::new() + .id(4) + .name("CPU") + .description("8-core CPU") + .quantity(1) + .build() + .expect("Failed to create CPU component"); + + let ram_premium = ProductComponentBuilder::new() + .id(5) + .name("RAM") + .description("32GB RAM") + .quantity(1) + .build() + .expect("Failed to create RAM component"); + + let storage_premium = ProductComponentBuilder::new() + .id(6) + .name("Storage") + .description("1TB SSD") + .quantity(1) + .build() + .expect("Failed to create Storage component"); + + let gpu_premium = ProductComponentBuilder::new() + .id(7) + .name("GPU") + .description("Dedicated GPU") + .quantity(1) + .build() + .expect("Failed to create GPU component"); + + // Create Standard Node Product + let standard_node = ProductBuilder::new() + .id(1) + .name("Standard Server Node") + .description("Basic server node for general workloads") + .price(usd(99.99)) + .type_(ProductType::Product) + .category("Servers") + .status(ProductStatus::Available) + .max_amount(100) + .validity_days(365) + .add_component(cpu_standard) + .add_component(ram_standard) + .add_component(storage_standard) + .build() + .expect("Failed to create Standard Node product"); + + // Create Premium Node Product + let premium_node = ProductBuilder::new() + .id(2) + .name("Premium Server Node") + .description("High-performance server node for demanding workloads") + .price(usd(199.99)) + .type_(ProductType::Product) + .category("Servers") + .status(ProductStatus::Available) + .max_amount(50) + .validity_days(365) + .add_component(cpu_premium) + .add_component(ram_premium) + .add_component(storage_premium) + .add_component(gpu_premium) + .build() + .expect("Failed to create Premium Node product"); + + (standard_node, premium_node) +} + +/// Check which products can be purchased +fn get_purchasable_products<'a>(products: &[&'a Product]) -> Vec<&'a Product> { + products.iter() + .filter(|p| p.is_purchasable()) + .copied() + .collect() +} + +/// Create a sale for a customer buying a product +fn create_sale(customer: &Customer, product: &Product) -> Sale { + let now = Utc::now(); + let active_till = now + Duration::days(365); + + // Create a sale item for the product + let sale_item = SaleItemBuilder::new() + .id(1) + .sale_id(1) + .product_id(product.id as u32) + .name(product.name.clone()) + .description(product.description.clone()) + .comments("Customer requested expedited setup") + .quantity(1) + .unit_price(product.price.clone()) + .tax_rate(10.0) // 10% tax rate + .active_till(active_till) + .build() + .expect("Failed to create sale item"); + + // Create the sale + let sale = SaleBuilder::new() + .id(1) + .company_id(101) // Assuming company ID 101 + .customer_id(customer.id) + .buyer_name(customer.name.clone()) + .buyer_email("contact@techcorp.com") // Example email + .currency_code(product.price.currency_code.clone()) + .status(SaleStatus::Completed) + .add_item(sale_item) + .build() + .expect("Failed to create sale"); + + sale +} + +/// Create an invoice for a sale +fn create_invoice(customer: &Customer, sale: &Sale) -> Invoice { + let now = Utc::now(); + let due_date = now + Duration::days(30); // Due in 30 days + + // Create an invoice item for the sale + let invoice_item = InvoiceItemBuilder::new() + .id(1) + .invoice_id(1) + .description(format!("Purchase of {}", sale.items[0].name)) + .amount(sale.total_amount.clone()) + .sale_id(sale.id) + .build() + .expect("Failed to create invoice item"); + + // Create the invoice + let invoice = InvoiceBuilder::new() + .id(1) + .customer_id(customer.id) + .currency_code(sale.total_amount.currency_code.clone()) + .status(InvoiceStatus::Sent) + .issue_date(now) + .due_date(due_date) + .add_item(invoice_item) + .build() + .expect("Failed to create invoice"); + + invoice +} + +/// Process a payment for an invoice +fn process_payment(mut invoice: Invoice) -> Invoice { + // Create a payment for the full amount + let payment = Payment::new( + invoice.total_amount.clone(), + "Credit Card".to_string(), + "Payment received via credit card ending in 1234".to_string() + ); + + // Add the payment to the invoice + invoice.add_payment(payment); + + // The invoice should now be marked as paid + assert_eq!(invoice.payment_status, PaymentStatus::Paid); + assert_eq!(invoice.status, InvoiceStatus::Paid); + + invoice +} \ No newline at end of file diff --git a/herodb/src/cmd/dbexample_biz/mod.rs b/herodb/src/cmd/dbexample_biz/mod.rs new file mode 100644 index 0000000..bcc5b77 --- /dev/null +++ b/herodb/src/cmd/dbexample_biz/mod.rs @@ -0,0 +1,10 @@ +//! Business example for HeroDB +//! +//! This module demonstrates business models in action, +//! including products, sales, invoices, and payments. + +// Re-export the main function +pub use self::main::*; + +// Include the main module +mod main; \ No newline at end of file diff --git a/herodb/src/cmd/mod.rs b/herodb/src/cmd/mod.rs new file mode 100644 index 0000000..71b73ae --- /dev/null +++ b/herodb/src/cmd/mod.rs @@ -0,0 +1,7 @@ +//! Command examples for HeroDB +//! +//! This module contains various example commands and applications +//! that demonstrate how to use HeroDB in different scenarios. + +// Export the example modules +pub mod dbexample_biz; \ No newline at end of file diff --git a/herodb/src/lib.rs b/herodb/src/lib.rs index 9e1771a..b018f41 100644 --- a/herodb/src/lib.rs +++ b/herodb/src/lib.rs @@ -7,7 +7,9 @@ pub mod db; pub mod error; pub mod models; -pub mod rhaiengine; +// Temporarily commented out due to compilation errors +// pub mod rhaiengine; +pub mod cmd; // Re-exports pub use error::Error; diff --git a/herodb/src/rhaiengine/mod.rs b/herodb/src/rhaiengine/mod.rs new file mode 100644 index 0000000..57dee57 --- /dev/null +++ b/herodb/src/rhaiengine/mod.rs @@ -0,0 +1,7 @@ +//! Rhai Engine module for scripting support +//! +//! This module provides integration with the Rhai scripting language. + +// Re-export the engine module +pub mod engine; +pub use engine::*; \ No newline at end of file From cff05330afdcaf33d44b3fc4daf27cc1f29aef9f Mon Sep 17 00:00:00 2001 From: despiegk Date: Sun, 20 Apr 2025 05:38:42 +0200 Subject: [PATCH 10/10] .... --- herodb/src/models/circle/circle.rs | 27 ---------- herodb/src/models/circle/member.rs | 82 +++++++++++++++++++++++++++++ herodb/src/models/circle/mod.rs | 6 ++- herodb/src/models/circle/wallet.rs | 84 ++++++++++++++++++++++++++++++ herodb/src/models/instructions.md | 18 +++++++ 5 files changed, 189 insertions(+), 28 deletions(-) create mode 100644 herodb/src/models/circle/member.rs create mode 100644 herodb/src/models/circle/wallet.rs create mode 100644 herodb/src/models/instructions.md diff --git a/herodb/src/models/circle/circle.rs b/herodb/src/models/circle/circle.rs index a8d521d..62e1995 100644 --- a/herodb/src/models/circle/circle.rs +++ b/herodb/src/models/circle/circle.rs @@ -2,33 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::db::{SledModel, Storable}; use std::collections::HashMap; -/// Role represents the role of a member in a circle -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum Role { - Admin, - Stakeholder, - Member, - Contributor, - Guest, -} - -/// Member represents a member of a circle -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Member { - pub pubkeys: Vec, // public keys of the member - pub emails: Vec, // list of emails - pub name: String, // name of the member - pub description: String, // optional description - pub role: Role, // role of the member in the circle -} - /// Circle represents a collection of members (users or other circles) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Circle { pub id: u32, // unique id pub name: String, // name of the circle pub description: String, // optional description - pub members: Vec, // members of the circle } impl Circle { @@ -38,15 +17,9 @@ impl Circle { id, name, description, - members: Vec::new(), } } - /// Add a member to the circle - pub fn add_member(&mut self, member: Member) { - self.members.push(member); - } - /// Returns a map of index keys for this circle pub fn index_keys(&self) -> HashMap { let mut keys = HashMap::new(); diff --git a/herodb/src/models/circle/member.rs b/herodb/src/models/circle/member.rs new file mode 100644 index 0000000..c39fa1f --- /dev/null +++ b/herodb/src/models/circle/member.rs @@ -0,0 +1,82 @@ +use serde::{Deserialize, Serialize}; +use crate::db::{SledModel, Storable}; +use std::collections::HashMap; + +/// Role represents the role of a member in a circle +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Role { + Admin, + Stakeholder, + Member, + Contributor, + Guest, +} + +/// Member represents a member of a circle +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Member { + pub id: u32, // unique id + pub emails: Vec, // list of emails + pub name: String, // name of the member + pub description: String, // optional description + pub role: Role, // role of the member in the circle + pub contact_ids: Vec, // IDs of contacts linked to this member + pub wallet_ids: Vec, // IDs of wallets owned by this member +} + +impl Member { + /// Create a new member + pub fn new(id: u32, name: String, description: String, role: Role) -> Self { + Self { + id, + emails: Vec::new(), + name, + description, + role, + contact_ids: Vec::new(), + wallet_ids: Vec::new(), + } + } + + /// Add an email to this member + pub fn add_email(&mut self, email: String) { + if !self.emails.contains(&email) { + self.emails.push(email); + } + } + + /// Link a contact to this member + pub fn link_contact(&mut self, contact_id: u32) { + if !self.contact_ids.contains(&contact_id) { + self.contact_ids.push(contact_id); + } + } + + /// Link a wallet to this member + pub fn link_wallet(&mut self, wallet_id: u32) { + if !self.wallet_ids.contains(&wallet_id) { + self.wallet_ids.push(wallet_id); + } + } + + /// Returns a map of index keys for this member + pub fn index_keys(&self) -> HashMap { + let mut keys = HashMap::new(); + keys.insert("name".to_string(), self.name.clone()); + keys + } +} + +// Implement Storable trait (provides default dump/load) +impl Storable for Member {} + +// Implement SledModel trait +impl SledModel for Member { + fn get_id(&self) -> String { + self.id.to_string() + } + + fn db_prefix() -> &'static str { + "member" + } +} \ No newline at end of file diff --git a/herodb/src/models/circle/mod.rs b/herodb/src/models/circle/mod.rs index b47d562..2f578c4 100644 --- a/herodb/src/models/circle/mod.rs +++ b/herodb/src/models/circle/mod.rs @@ -1,9 +1,13 @@ pub mod circle; +pub mod member; pub mod name; +pub mod wallet; // Re-export all model types for convenience -pub use circle::{Circle, Member, Role}; +pub use circle::Circle; +pub use member::{Member, Role}; pub use name::{Name, Record, RecordType}; +pub use wallet::{Wallet, Asset}; // Re-export database components from db module pub use crate::db::{SledDB, SledDBError, SledDBResult, Storable, SledModel, DB}; diff --git a/herodb/src/models/circle/wallet.rs b/herodb/src/models/circle/wallet.rs new file mode 100644 index 0000000..ff4b5e1 --- /dev/null +++ b/herodb/src/models/circle/wallet.rs @@ -0,0 +1,84 @@ +use serde::{Deserialize, Serialize}; +use crate::db::{SledModel, Storable}; +use std::collections::HashMap; + +/// Asset represents a cryptocurrency asset in a wallet +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Asset { + pub name: String, // Asset name (e.g., "USDC") + pub amount: f64, // Amount of the asset +} + +impl Asset { + /// Create a new asset + pub fn new(name: String, amount: f64) -> Self { + Self { + name, + amount, + } + } +} + +/// Wallet represents a cryptocurrency wallet +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Wallet { + pub id: u32, // unique id + pub name: String, // name of the wallet + pub description: String, // optional description + pub blockchain_name: String, // name of the blockchain + pub pubkey: String, // public key of the wallet + pub assets: Vec, // assets in the wallet +} + +impl Wallet { + /// Create a new wallet + pub fn new(id: u32, name: String, description: String, blockchain_name: String, pubkey: String) -> Self { + Self { + id, + name, + description, + blockchain_name, + pubkey, + assets: Vec::new(), + } + } + + /// Set an asset in the wallet (replaces if exists, adds if not) + pub fn set_asset(&mut self, name: String, amount: f64) { + // Check if the asset already exists + if let Some(asset) = self.assets.iter_mut().find(|a| a.name == name) { + // Update the amount + asset.amount = amount; + } else { + // Add a new asset + self.assets.push(Asset::new(name, amount)); + } + } + + /// Get the total value of all assets in the wallet + pub fn total_value(&self) -> f64 { + self.assets.iter().map(|a| a.amount).sum() + } + + /// Returns a map of index keys for this wallet + pub fn index_keys(&self) -> HashMap { + let mut keys = HashMap::new(); + keys.insert("name".to_string(), self.name.clone()); + keys.insert("blockchain".to_string(), self.blockchain_name.clone()); + keys + } +} + +// Implement Storable trait (provides default dump/load) +impl Storable for Wallet {} + +// Implement SledModel trait +impl SledModel for Wallet { + fn get_id(&self) -> String { + self.id.to_string() + } + + fn db_prefix() -> &'static str { + "wallet" + } +} \ No newline at end of file diff --git a/herodb/src/models/instructions.md b/herodb/src/models/instructions.md new file mode 100644 index 0000000..3a2cfc7 --- /dev/null +++ b/herodb/src/models/instructions.md @@ -0,0 +1,18 @@ +in @src/models/circle/circle.rs + +- member us now new rootobject, check implementation +- a member is linked to one or more contacts id's (from src/models/mcc/contacts.rs) +- a member has one or more wallets + + +in@src/models/biz add a ticket module + +user can have more than 1 ticket which is to ask support from the org + +a ticket has following fields + +- subject +- description +- creation/update date +- assignees (based on memberid see above) +- \ No newline at end of file