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:
@@ -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
|
@@ -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");
|
||||
}
|
||||
}
|
@@ -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(())
|
||||
}
|
@@ -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(())
|
||||
}
|
48
herodb/src/cmd/dbexample_biz/README.md
Normal file
48
herodb/src/cmd/dbexample_biz/README.md
Normal 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.
|
275
herodb/src/cmd/dbexample_biz/main.rs
Normal file
275
herodb/src/cmd/dbexample_biz/main.rs
Normal 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
|
||||
}
|
10
herodb/src/cmd/dbexample_biz/mod.rs
Normal file
10
herodb/src/cmd/dbexample_biz/mod.rs
Normal 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;
|
@@ -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");
|
2161
herodb/src/cmd/dbexample_governance/Cargo.lock
generated
2161
herodb/src/cmd/dbexample_governance/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
360
herodb/src/cmd/dbexample_prod/main.rs
Normal file
360
herodb/src/cmd/dbexample_prod/main.rs
Normal 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
7
herodb/src/cmd/mod.rs
Normal 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;
|
Reference in New Issue
Block a user