Merge branch 'builders_in_script'

* builders_in_script:
  ....
  ...
  ...
  ...
  ...
  ...
  ...
  ...
  ...
  Inject some builders in script

# Conflicts:
#	herodb/src/cmd/dbexample/examples.rs
#	herodb/src/models/biz/product.rs
#	herodb/src/models/gov/GOVERNANCE_ENHANCEMENT_PLAN.md
#	herodb/src/models/gov/compliance.rs
This commit is contained in:
2025-04-20 05:41:28 +02:00
56 changed files with 3972 additions and 3689 deletions

View File

@@ -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<T>`: 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::<User>()
.register_model::<Company>()
.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::<User>()?;
```
## 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<T>` 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

View File

@@ -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<Utc>,
updated_at: DateTime<Utc>,
}
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");
}
}

View File

@@ -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<P: Into<PathBuf>>(path: P) -> SledDBResult<DB> {
// Using the builder pattern to register all models
DBBuilder::new(path)
.register_model::<User>()
.register_model::<Company>()
.register_model::<Meeting>()
.register_model::<Product>()
.register_model::<Sale>()
.register_model::<Vote>()
.register_model::<Shareholder>()
.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::<User>()?;
db.register::<Company>()?;
db.register::<Meeting>()?;
db.register::<Product>()?;
db.register::<Sale>()?;
db.register::<Vote>()?;
db.register::<Shareholder>()?;
Ok(())
}

View File

@@ -1,464 +0,0 @@
use chrono::{Utc, Duration};
use herodb::db::DBBuilder;
use herodb::models::biz::{
Currency, CurrencyBuilder,
Product, ProductBuilder, ProductComponentBuilder,
ProductType, ProductStatus,
Sale, SaleBuilder, SaleItemBuilder, SaleStatus,
ExchangeRate, ExchangeRateBuilder, EXCHANGE_RATE_SERVICE,
Service, ServiceBuilder, ServiceItemBuilder, ServiceStatus, BillingFrequency,
Customer, CustomerBuilder,
Contract, ContractBuilder, ContractStatus,
Invoice, InvoiceBuilder, InvoiceItemBuilder, InvoiceStatus, PaymentStatus, Payment
};
use std::path::PathBuf;
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("DB Example 2: Using Builder Pattern and Model-Specific Methods");
println!("============================================================");
// Create a temporary directory for the database
let db_path = PathBuf::from("/tmp/dbexample2");
if db_path.exists() {
fs::remove_dir_all(&db_path)?;
}
fs::create_dir_all(&db_path)?;
println!("Database path: {:?}", db_path);
// Create a database instance with our models registered
let db = DBBuilder::new(&db_path)
.register_model::<Product>()
.register_model::<Currency>()
.register_model::<Sale>()
.register_model::<ExchangeRate>()
.register_model::<Service>()
.register_model::<Customer>()
.register_model::<Contract>()
.register_model::<Invoice>()
.build()?;
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: ${} {}", usd.amount, usd.currency_code);
// Create product components using the builder with energy usage and cost
let component1 = ProductComponentBuilder::new()
.id(101)
.name("Basic Support")
.description("24/7 email support")
.quantity(1)
.energy_usage(5.0) // 5 watts
.cost(CurrencyBuilder::new()
.amount(5.0)
.currency_code("USD")
.build()?)
.build()?;
let component2 = ProductComponentBuilder::new()
.id(102)
.name("Premium Support")
.description("24/7 phone and email support")
.quantity(1)
.energy_usage(10.0) // 10 watts
.cost(CurrencyBuilder::new()
.amount(15.0)
.currency_code("USD")
.build()?)
.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()?)
.type_(ProductType::Service)
.category("Subscription")
.status(ProductStatus::Active)
.max_amount(1000)
.validity_days(30)
.add_component(component1)
.build()?;
let product2 = ProductBuilder::new()
.id(2)
.name("Premium Plan")
.description("Our premium service offering with priority support")
.price(CurrencyBuilder::new()
.amount(99.99)
.currency_code("USD")
.build()?)
.type_(ProductType::Service)
.category("Subscription")
.status(ProductStatus::Active)
.max_amount(500)
.validity_days(30)
.add_component(component2)
.build()?;
// Insert products using model-specific methods
db.insert_product(&product1)?;
db.insert_product(&product2)?;
println!("Product created: {} (${}) USD", product1.name, product1.price.amount);
println!("Product created: {} (${}) USD", 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: {} (${}) USD", retrieved_product1.name, retrieved_product1.price.amount);
println!("Components:");
for component in &retrieved_product1.components {
println!(" - {} ({}, Energy: {}W, Cost: ${} USD)",
component.name,
component.description,
component.energy_usage,
component.cost.amount
);
}
// Calculate total energy usage
let total_energy = retrieved_product1.total_energy_usage();
println!("Total energy usage: {}W", total_energy);
// Calculate components cost
if let Some(components_cost) = retrieved_product1.components_cost_in_usd() {
println!("Total components cost: ${} USD", components_cost.amount);
}
println!("\n3. Listing All Products");
println!("----------------------");
// List all products using model-specific methods
let all_products = db.list_products()?;
println!("Found {} products:", all_products.len());
for product in all_products {
println!(" - {} (${} USD, {})",
product.name,
product.price.amount,
if product.is_purchasable() { "Available" } else { "Unavailable" }
);
}
println!("\n4. Creating a Sale");
println!("-----------------");
// 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()?)
.active_till(now + Duration::days(30))
.build()?;
let sale = SaleBuilder::new()
.id(1)
.company_id(101)
.buyer_name("John Doe")
.buyer_email("john.doe@example.com")
.currency_code("USD")
.status(SaleStatus::Pending)
.add_item(item1)
.build()?;
// Insert the sale using model-specific methods
db.insert_sale(&sale)?;
println!("Sale created: #{} for {} (${} USD)",
sale.id,
sale.buyer_name,
sale.total_amount.amount
);
println!("\n5. Updating a Sale");
println!("-----------------");
// 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);
// Update the status
retrieved_sale.update_status(SaleStatus::Completed);
db.insert_sale(&retrieved_sale)?;
println!("Updated sale status to {:?}", retrieved_sale.status);
println!("\n6. Working with Exchange Rates");
println!("----------------------------");
// Create and set exchange rates using the builder
let eur_rate = ExchangeRateBuilder::new()
.base_currency("EUR")
.target_currency("USD")
.rate(1.18)
.build()?;
let gbp_rate = ExchangeRateBuilder::new()
.base_currency("GBP")
.target_currency("USD")
.rate(1.38)
.build()?;
// Insert exchange rates into the database
db.insert_exchange_rate(&eur_rate)?;
db.insert_exchange_rate(&gbp_rate)?;
// Set the exchange rates in the service
EXCHANGE_RATE_SERVICE.set_rate(eur_rate.clone());
EXCHANGE_RATE_SERVICE.set_rate(gbp_rate.clone());
println!("Exchange rates set:");
println!(" - 1 EUR = {} USD", eur_rate.rate);
println!(" - 1 GBP = {} USD", gbp_rate.rate);
// Create currencies in different denominations
let eur_price = CurrencyBuilder::new()
.amount(100.0)
.currency_code("EUR")
.build()?;
let gbp_price = CurrencyBuilder::new()
.amount(85.0)
.currency_code("GBP")
.build()?;
// Convert to USD
if let Some(eur_in_usd) = eur_price.to_usd() {
println!("{} EUR = {} USD", eur_price.amount, eur_in_usd.amount);
} else {
println!("Could not convert EUR to USD");
}
if let Some(gbp_in_usd) = gbp_price.to_usd() {
println!("{} GBP = {} USD", gbp_price.amount, gbp_in_usd.amount);
} else {
println!("Could not convert GBP to USD");
}
// Convert between currencies
if let Some(eur_in_gbp) = eur_price.to_currency("GBP") {
println!("{} EUR = {} GBP", eur_price.amount, eur_in_gbp.amount);
} else {
println!("Could not convert EUR to GBP");
}
// Test product price conversion
let retrieved_product2 = db.get_product(2)?;
if let Some(price_in_eur) = retrieved_product2.cost_in_currency("EUR") {
println!("Product '{}' price: ${} USD = {} EUR",
retrieved_product2.name,
retrieved_product2.price.amount,
price_in_eur.amount
);
}
println!("\n7. Deleting Objects");
println!("------------------");
// Delete a product
db.delete_product(2)?;
println!("Deleted product #2");
// List remaining products
let remaining_products = db.list_products()?;
println!("Remaining products: {}", remaining_products.len());
for product in remaining_products {
println!(" - {}", product.name);
}
println!("\n8. Creating a Customer");
println!("--------------------");
// Create a customer using the builder
let customer = CustomerBuilder::new()
.id(1001)
.name("Jane Smith")
.description("Enterprise customer")
.pubkey("abc123def456")
.add_contact(5001)
.add_contact(5002)
.build()?;
// Insert the customer
db.insert_customer(&customer)?;
println!("Customer created: {} (ID: {})", customer.name, customer.id);
println!("Contacts: {:?}", customer.contact_ids);
println!("\n9. Creating a Service");
println!("-------------------");
// Create service items using the builder
let service_item1 = ServiceItemBuilder::new()
.id(301)
.service_id(2001)
.product_id(1)
.name("Standard Plan - Monthly")
.quantity(1)
.unit_price(CurrencyBuilder::new()
.amount(29.99)
.currency_code("USD")
.build()?)
.tax_rate(0.07) // 7% tax
.is_taxable(true)
.active_till(now + Duration::days(30))
.build()?;
// Create a service using the builder
let service = ServiceBuilder::new()
.id(2001)
.customer_id(1001)
.currency_code("USD")
.status(ServiceStatus::Active)
.billing_frequency(BillingFrequency::Monthly)
.add_item(service_item1)
.build()?;
// Insert the service
db.insert_service(&service)?;
println!("Service created: #{} for customer #{}", service.id, service.customer_id);
println!("Total amount: ${} USD (including tax)", service.total_amount.amount);
println!("Billing frequency: {:?}", service.billing_frequency);
println!("\n10. Creating a Contract");
println!("---------------------");
// Create a contract using the builder
let contract = ContractBuilder::new()
.id(3001)
.customer_id(1001)
.service_id(2001)
.terms("Monthly service contract with auto-renewal")
.start_date(now)
.end_date(now + Duration::days(365))
.auto_renewal(true)
.renewal_terms("Renews automatically for 1 year unless cancelled 30 days prior")
.status(ContractStatus::Active)
.build()?;
// Insert the contract
db.insert_contract(&contract)?;
println!("Contract created: #{} for customer #{}", contract.id, contract.customer_id);
println!("Contract period: {} to {}",
contract.start_date.format("%Y-%m-%d"),
contract.end_date.format("%Y-%m-%d")
);
println!("Auto-renewal: {}", if contract.auto_renewal { "Yes" } else { "No" });
println!("\n11. Creating an Invoice");
println!("---------------------");
// Create invoice items using the builder
let invoice_item1 = InvoiceItemBuilder::new()
.id(401)
.invoice_id(4001)
.description("Monthly service fee - Standard Plan")
.amount(CurrencyBuilder::new()
.amount(32.09) // Price with tax
.currency_code("USD")
.build()?)
.service_id(2001)
.build()?;
// Create an invoice using the builder
let invoice = InvoiceBuilder::new()
.id(4001)
.customer_id(1001)
.currency_code("USD")
.issue_date(now)
.due_date(now + Duration::days(15))
.add_item(invoice_item1)
.build()?;
// Insert the invoice
db.insert_invoice(&invoice)?;
println!("Invoice created: #{} for customer #{}", invoice.id, invoice.customer_id);
println!("Total amount: ${} USD", invoice.total_amount.amount);
println!("Balance due: ${} USD", invoice.balance_due.amount);
println!("Status: {:?}, Payment status: {:?}", invoice.status, invoice.payment_status);
println!("\n12. Processing a Payment");
println!("----------------------");
// Retrieve the invoice, add a payment, and save it back
let mut retrieved_invoice = db.get_invoice(4001)?;
// Create a payment
let payment = Payment::new(
CurrencyBuilder::new()
.amount(32.09)
.currency_code("USD")
.build()?,
"Credit Card".to_string()
);
// Add the payment to the invoice
retrieved_invoice.add_payment(payment);
// Save the updated invoice
db.insert_invoice(&retrieved_invoice)?;
println!("Payment processed for invoice #{}", retrieved_invoice.id);
println!("New balance due: ${} USD", retrieved_invoice.balance_due.amount);
println!("New status: {:?}, Payment status: {:?}",
retrieved_invoice.status,
retrieved_invoice.payment_status
);
println!("\n13. Retrieving Related Objects");
println!("----------------------------");
// Retrieve customer and related objects
let retrieved_customer = db.get_customer(1001)?;
println!("Customer: {} (ID: {})", retrieved_customer.name, retrieved_customer.id);
// Retrieve service for this customer
let retrieved_service = db.get_service(2001)?;
println!("Service: #{} with {} items",
retrieved_service.id,
retrieved_service.items.len()
);
// Retrieve contract for this customer
let retrieved_contract = db.get_contract(3001)?;
println!("Contract: #{} ({})",
retrieved_contract.id,
if retrieved_contract.is_active() { "Active" } else { "Inactive" }
);
// Retrieve invoice for this customer
let retrieved_invoice = db.get_invoice(4001)?;
println!("Invoice: #{} ({})",
retrieved_invoice.id,
match retrieved_invoice.payment_status {
PaymentStatus::Paid => "Paid",
PaymentStatus::PartiallyPaid => "Partially Paid",
PaymentStatus::Unpaid => "Unpaid",
}
);
println!("\nExample completed successfully!");
Ok(())
}

View File

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

View File

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

View File

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

View File

@@ -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<dyn std::error::Error>> {
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)?;
}
@@ -58,7 +58,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// 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.as_str());
@@ -94,9 +94,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// 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);
@@ -137,9 +137,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// 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);
@@ -150,7 +150,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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);
@@ -198,7 +198,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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());
@@ -209,7 +209,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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();
@@ -242,19 +242,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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: {}",
@@ -263,7 +263,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Approve the resolution
resolution.approve();
db.insert(&resolution)?;
db.set(&resolution)?;
println!("Resolution approved on {}",
resolution.approved_at.unwrap().format("%Y-%m-%d"));
@@ -287,7 +287,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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"),
@@ -297,7 +297,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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:");
@@ -318,7 +318,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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");

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,360 @@
use chrono::{DateTime, Duration, Utc};
use herodb::db::{DB, DBBuilder};
use herodb::models::biz::{
Currency, CurrencyBuilder, Product, ProductBuilder, ProductComponent, ProductComponentBuilder,
ProductStatus, ProductType, Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus,
};
use rhai::{Engine, packages::Package};
use std::fs;
use std::path::PathBuf;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("DB Example 2: Using Builder Pattern and Model-Specific Methods");
println!("============================================================");
// Create a temporary directory for the database
let db_path = PathBuf::from("/tmp/dbexample_prod");
if db_path.exists() {
fs::remove_dir_all(&db_path)?;
}
fs::create_dir_all(&db_path)?;
println!("Database path: {:?}", db_path);
let mut engine = Engine::new();
engine
.build_type::<Product>()
.build_type::<ProductBuilder>()
.build_type::<ProductComponentBuilder>()
.build_type::<Currency>()
.build_type::<CurrencyBuilder>()
.build_type::<Sale>()
.build_type::<SaleBuilder>()
.build_type::<DBBuilder>()
.build_type::<DB>();
// 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::<String>);
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::<String>);
engine.register_fn(
"description",
ProductComponentBuilder::description::<String>,
);
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::<String>);
engine.register_fn("description", ProductBuilder::description::<String>);
engine.register_fn("price", ProductBuilder::price);
engine.register_fn("type", ProductBuilder::type_);
engine.register_fn("category", ProductBuilder::category::<String>);
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::<String>);
engine.register_fn("register_currency", DBBuilder::register_model::<Currency>);
engine.register_fn("register_product", DBBuilder::register_model::<Product>);
engine.register_fn("register_sale", DBBuilder::register_model::<Sale>);
engine.register_fn("currency_code", CurrencyBuilder::currency_code::<String>);
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);
"#;
println!("\n0. Executing Script");
println!("----------------------------------------");
engine.eval::<()>(script)?;
// Create a database instance with our models registered
let mut db = DBBuilder::new(&db_path)
.register_model::<Product>()
.register_model::<Currency>()
.register_model::<Sale>()
.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.clone())?;
// println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code);
// Create product components using the builder
let component1 = ProductComponentBuilder::new()
.id(101)
.name("Basic Support")
.description("24/7 email support")
.quantity(1)
.build()?;
let component2 = ProductComponentBuilder::new()
.id(102)
.name("Premium Support")
.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()?,
)
.type_(ProductType::Service)
.category("Subscription")
.status(ProductStatus::Available)
.max_amount(1000)
.validity_days(30)
.add_component(component1)
.build()?;
let product2 = ProductBuilder::new()
.id(2)
.name("Premium Plan")
.description("Our premium service offering with priority support")
.price(
CurrencyBuilder::new()
.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.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!("\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!("Components:");
for component in &retrieved_product1.components {
println!(" - {} ({})", component.name, component.description);
}
println!("\n3. Listing All Products");
println!("----------------------");
// List all products using model-specific methods
let all_products = db.list_products()?;
println!("Found {} products:", all_products.len());
for product in all_products {
println!(
" - {} (${:.2}, {})",
product.name,
product.price.amount,
if product.is_purchasable() {
"Available"
} else {
"Unavailable"
}
);
}
println!("\n4. Creating a Sale");
println!("-----------------");
// 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()?,
)
.active_till(now + Duration::days(30))
.build()?;
let sale = SaleBuilder::new()
.id(1)
.company_id(101)
.buyer_name("John Doe")
.buyer_email("john.doe@example.com")
.currency_code("USD")
.status(SaleStatus::Pending)
.add_item(item1)
.build()?;
// Insert the sale using model-specific methods
db.insert_sale(sale.clone())?;
println!(
"Sale created: #{} for {} (${:.2})",
sale.id, sale.buyer_name, sale.total_amount.amount
);
println!("\n5. Updating a Sale");
println!("-----------------");
// 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
);
// Update the status
retrieved_sale.update_status(SaleStatus::Completed);
db.insert_sale(retrieved_sale.clone())?;
println!("Updated sale status to {:?}", retrieved_sale.status);
println!("\n6. Deleting Objects");
println!("------------------");
// Delete a product
db.delete_product(2)?;
println!("Deleted product #2");
// List remaining products
let remaining_products = db.list_products()?;
println!("Remaining products: {}", remaining_products.len());
for product in remaining_products {
println!(" - {}", product.name);
}
println!("\nExample completed successfully!");
Ok(())
}

7
herodb/src/cmd/mod.rs Normal file
View File

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