Inject some builders in script
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
		@@ -19,7 +19,7 @@ tempfile = "3.8"
 | 
			
		||||
poem = "1.3.55"
 | 
			
		||||
poem-openapi = { version = "2.0.11", features = ["swagger-ui"] }
 | 
			
		||||
tokio = { version = "1", features = ["full"] }
 | 
			
		||||
rhai = "1.15.1"
 | 
			
		||||
rhai = "1.21.0"
 | 
			
		||||
paste = "1.0"
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,12 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use chrono::{DateTime, Duration, Utc};
 | 
			
		||||
use herodb::db::{DB, DBBuilder};
 | 
			
		||||
use herodb::models::biz::{
 | 
			
		||||
    Currency, CurrencyBuilder,
 | 
			
		||||
    Product, ProductBuilder, ProductComponent, ProductComponentBuilder,
 | 
			
		||||
    ProductType, ProductStatus,
 | 
			
		||||
    Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus
 | 
			
		||||
    Currency, CurrencyBuilder, Product, ProductBuilder, ProductComponent, ProductComponentBuilder,
 | 
			
		||||
    ProductStatus, ProductType, Sale, SaleBuilder, SaleItem, SaleItemBuilder, SaleStatus,
 | 
			
		||||
};
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use rhai::{Engine, packages::Package};
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    println!("DB Example 2: Using Builder Pattern and Model-Specific Methods");
 | 
			
		||||
@@ -21,25 +20,170 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    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);
 | 
			
		||||
    "#;
 | 
			
		||||
 | 
			
		||||
    engine.eval::<()>(script)?;
 | 
			
		||||
 | 
			
		||||
    // Create a database instance with our models registered
 | 
			
		||||
    let db = DBBuilder::new(&db_path)
 | 
			
		||||
    let mut db = DBBuilder::new(&db_path)
 | 
			
		||||
        .register_model::<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)?;
 | 
			
		||||
    println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code);
 | 
			
		||||
    // // Create a currency using the builder
 | 
			
		||||
    // let usd = CurrencyBuilder::new()
 | 
			
		||||
    //     .amount(0.0) // Initial amount
 | 
			
		||||
    //     .currency_code("USD")
 | 
			
		||||
    //     .build()?;
 | 
			
		||||
    //
 | 
			
		||||
    // // Insert the currency
 | 
			
		||||
    // db.insert_currency(usd.clone())?;
 | 
			
		||||
    // println!("Currency created: ${:.2} {}", usd.amount, usd.currency_code);
 | 
			
		||||
 | 
			
		||||
    // Create product components using the builder
 | 
			
		||||
    let component1 = ProductComponentBuilder::new()
 | 
			
		||||
@@ -55,16 +199,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .description("24/7 phone and email support")
 | 
			
		||||
        .quantity(1)
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Create products using the builder
 | 
			
		||||
    let product1 = ProductBuilder::new()
 | 
			
		||||
        .id(1)
 | 
			
		||||
        .name("Standard Plan")
 | 
			
		||||
        .description("Our standard service offering")
 | 
			
		||||
        .price(CurrencyBuilder::new()
 | 
			
		||||
            .amount(29.99)
 | 
			
		||||
            .currency_code("USD")
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .price(
 | 
			
		||||
            CurrencyBuilder::new()
 | 
			
		||||
                .amount(29.99)
 | 
			
		||||
                .currency_code("USD")
 | 
			
		||||
                .build()?,
 | 
			
		||||
        )
 | 
			
		||||
        .type_(ProductType::Service)
 | 
			
		||||
        .category("Subscription")
 | 
			
		||||
        .status(ProductStatus::Available)
 | 
			
		||||
@@ -77,10 +222,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .id(2)
 | 
			
		||||
        .name("Premium Plan")
 | 
			
		||||
        .description("Our premium service offering with priority support")
 | 
			
		||||
        .price(CurrencyBuilder::new()
 | 
			
		||||
            .amount(99.99)
 | 
			
		||||
            .currency_code("USD")
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .price(
 | 
			
		||||
            CurrencyBuilder::new()
 | 
			
		||||
                .amount(99.99)
 | 
			
		||||
                .currency_code("USD")
 | 
			
		||||
                .build()?,
 | 
			
		||||
        )
 | 
			
		||||
        .type_(ProductType::Service)
 | 
			
		||||
        .category("Subscription")
 | 
			
		||||
        .status(ProductStatus::Available)
 | 
			
		||||
@@ -90,18 +237,27 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Insert products using model-specific methods
 | 
			
		||||
    db.insert_product(&product1)?;
 | 
			
		||||
    db.insert_product(&product2)?;
 | 
			
		||||
    db.insert_product(product1.clone())?;
 | 
			
		||||
    db.insert_product(product2.clone())?;
 | 
			
		||||
 | 
			
		||||
    println!("Product created: {} (${:.2})", product1.name, product1.price.amount);
 | 
			
		||||
    println!("Product created: {} (${:.2})", product2.name, product2.price.amount);
 | 
			
		||||
    println!(
 | 
			
		||||
        "Product created: {} (${:.2})",
 | 
			
		||||
        product1.name, product1.price.amount
 | 
			
		||||
    );
 | 
			
		||||
    println!(
 | 
			
		||||
        "Product created: {} (${:.2})",
 | 
			
		||||
        product2.name, product2.price.amount
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    println!("\n2. Retrieving Products");
 | 
			
		||||
    println!("--------------------");
 | 
			
		||||
 | 
			
		||||
    // Retrieve products using model-specific methods
 | 
			
		||||
    let retrieved_product1 = db.get_product(1)?;
 | 
			
		||||
    println!("Retrieved: {} (${:.2})", retrieved_product1.name, retrieved_product1.price.amount);
 | 
			
		||||
    println!(
 | 
			
		||||
        "Retrieved: {} (${:.2})",
 | 
			
		||||
        retrieved_product1.name, retrieved_product1.price.amount
 | 
			
		||||
    );
 | 
			
		||||
    println!("Components:");
 | 
			
		||||
    for component in &retrieved_product1.components {
 | 
			
		||||
        println!("  - {} ({})", component.name, component.description);
 | 
			
		||||
@@ -114,10 +270,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    let all_products = db.list_products()?;
 | 
			
		||||
    println!("Found {} products:", all_products.len());
 | 
			
		||||
    for product in all_products {
 | 
			
		||||
        println!("  - {} (${:.2}, {})", 
 | 
			
		||||
            product.name, 
 | 
			
		||||
        println!(
 | 
			
		||||
            "  - {} (${:.2}, {})",
 | 
			
		||||
            product.name,
 | 
			
		||||
            product.price.amount,
 | 
			
		||||
            if product.is_purchasable() { "Available" } else { "Unavailable" }
 | 
			
		||||
            if product.is_purchasable() {
 | 
			
		||||
                "Available"
 | 
			
		||||
            } else {
 | 
			
		||||
                "Unavailable"
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -126,17 +287,19 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    // Create a sale using the builder
 | 
			
		||||
    let now = Utc::now();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    let item1 = SaleItemBuilder::new()
 | 
			
		||||
        .id(201)
 | 
			
		||||
        .sale_id(1)
 | 
			
		||||
        .product_id(1)
 | 
			
		||||
        .name("Standard Plan")
 | 
			
		||||
        .quantity(1)
 | 
			
		||||
        .unit_price(CurrencyBuilder::new()
 | 
			
		||||
            .amount(29.99)
 | 
			
		||||
            .currency_code("USD")
 | 
			
		||||
            .build()?)
 | 
			
		||||
        .unit_price(
 | 
			
		||||
            CurrencyBuilder::new()
 | 
			
		||||
                .amount(29.99)
 | 
			
		||||
                .currency_code("USD")
 | 
			
		||||
                .build()?,
 | 
			
		||||
        )
 | 
			
		||||
        .active_till(now + Duration::days(30))
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
@@ -151,11 +314,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        .build()?;
 | 
			
		||||
 | 
			
		||||
    // Insert the sale using model-specific methods
 | 
			
		||||
    db.insert_sale(&sale)?;
 | 
			
		||||
    println!("Sale created: #{} for {} (${:.2})", 
 | 
			
		||||
        sale.id, 
 | 
			
		||||
        sale.buyer_name,
 | 
			
		||||
        sale.total_amount.amount
 | 
			
		||||
    db.insert_sale(sale.clone())?;
 | 
			
		||||
    println!(
 | 
			
		||||
        "Sale created: #{} for {} (${:.2})",
 | 
			
		||||
        sale.id, sale.buyer_name, sale.total_amount.amount
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    println!("\n5. Updating a Sale");
 | 
			
		||||
@@ -163,12 +325,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    // Retrieve the sale, update it, and save it back
 | 
			
		||||
    let mut retrieved_sale = db.get_sale(1)?;
 | 
			
		||||
    println!("Retrieved sale: #{} with status {:?}", retrieved_sale.id, retrieved_sale.status);
 | 
			
		||||
    
 | 
			
		||||
    println!(
 | 
			
		||||
        "Retrieved sale: #{} with status {:?}",
 | 
			
		||||
        retrieved_sale.id, retrieved_sale.status
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Update the status
 | 
			
		||||
    retrieved_sale.update_status(SaleStatus::Completed);
 | 
			
		||||
    db.insert_sale(&retrieved_sale)?;
 | 
			
		||||
    
 | 
			
		||||
    db.insert_sale(retrieved_sale.clone())?;
 | 
			
		||||
 | 
			
		||||
    println!("Updated sale status to {:?}", retrieved_sale.status);
 | 
			
		||||
 | 
			
		||||
    println!("\n6. Deleting Objects");
 | 
			
		||||
@@ -187,4 +352,4 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    println!("\nExample completed successfully!");
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
use bincode;
 | 
			
		||||
use brotli::{CompressorReader, Decompressor};
 | 
			
		||||
use rhai::CustomType;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use sled;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
@@ -38,15 +39,11 @@ pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
 | 
			
		||||
        let mut compressed = Vec::new();
 | 
			
		||||
        // Default Brotli parameters: quality 5, lgwin 22 (window size)
 | 
			
		||||
        const BROTLI_QUALITY: u32 = 5;
 | 
			
		||||
        const BROTLI_LGWIN: u32 = 22; 
 | 
			
		||||
        const BROTLI_LGWIN: u32 = 22;
 | 
			
		||||
        const BUFFER_SIZE: usize = 4096; // 4KB buffer
 | 
			
		||||
 | 
			
		||||
        let mut compressor = CompressorReader::new(
 | 
			
		||||
            &encoded[..], 
 | 
			
		||||
            BUFFER_SIZE, 
 | 
			
		||||
            BROTLI_QUALITY, 
 | 
			
		||||
            BROTLI_LGWIN
 | 
			
		||||
        );
 | 
			
		||||
        let mut compressor =
 | 
			
		||||
            CompressorReader::new(&encoded[..], BUFFER_SIZE, BROTLI_QUALITY, BROTLI_LGWIN);
 | 
			
		||||
        compressor.read_to_end(&mut compressed)?;
 | 
			
		||||
 | 
			
		||||
        Ok(compressed)
 | 
			
		||||
@@ -56,7 +53,7 @@ pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
 | 
			
		||||
    fn load_from_bytes(data: &[u8]) -> SledDBResult<Self> {
 | 
			
		||||
        let mut decompressed = Vec::new();
 | 
			
		||||
        const BUFFER_SIZE: usize = 4096; // 4KB buffer
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let mut decompressor = Decompressor::new(data, BUFFER_SIZE);
 | 
			
		||||
        decompressor.read_to_end(&mut decompressed)?;
 | 
			
		||||
 | 
			
		||||
@@ -140,8 +137,8 @@ impl<T: SledModel> SledDB<T> {
 | 
			
		||||
        Ok(models)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     /// Provides access to the underlying Sled Db instance for advanced operations.
 | 
			
		||||
     pub fn raw_db(&self) -> &sled::Db {
 | 
			
		||||
    /// Provides access to the underlying Sled Db instance for advanced operations.
 | 
			
		||||
    pub fn raw_db(&self) -> &sled::Db {
 | 
			
		||||
        &self.db
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
use crate::db::base::*;
 | 
			
		||||
use bincode;
 | 
			
		||||
use rhai::{CustomType, EvalAltResult, TypeBuilder};
 | 
			
		||||
use std::any::TypeId;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::sync::{Arc, Mutex, RwLock};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use bincode;
 | 
			
		||||
 | 
			
		||||
/// Represents a single database operation in a transaction
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
@@ -24,7 +25,7 @@ pub trait AnyDbOperations: Send + Sync {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()>;
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>>;
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>; 
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()>;
 | 
			
		||||
    fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -33,17 +34,17 @@ impl<T: SledModel> AnyDbOperations for SledDB<T> {
 | 
			
		||||
    fn delete(&self, id: &str) -> SledDBResult<()> {
 | 
			
		||||
        self.delete(id)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    fn get_any(&self, id: &str) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.get(id)?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    fn list_any(&self) -> SledDBResult<Box<dyn std::any::Any>> {
 | 
			
		||||
        let result = self.list()?;
 | 
			
		||||
        Ok(Box::new(result))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    fn insert_any(&self, model: &dyn std::any::Any) -> SledDBResult<()> {
 | 
			
		||||
        // Downcast to the specific type T
 | 
			
		||||
        match model.downcast_ref::<T>() {
 | 
			
		||||
@@ -51,7 +52,7 @@ impl<T: SledModel> AnyDbOperations for SledDB<T> {
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    fn insert_any_raw(&self, serialized: &[u8]) -> SledDBResult<()> {
 | 
			
		||||
        // Deserialize the bytes into model of type T
 | 
			
		||||
        let model: T = bincode::deserialize(serialized)?;
 | 
			
		||||
@@ -77,23 +78,25 @@ impl TransactionState {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Main DB manager that automatically handles all root models
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct DB {
 | 
			
		||||
    db_path: PathBuf,
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Type map for generic operations
 | 
			
		||||
    type_map: HashMap<TypeId, Box<dyn AnyDbOperations>>,
 | 
			
		||||
    
 | 
			
		||||
    type_map: HashMap<TypeId, Arc<dyn AnyDbOperations>>,
 | 
			
		||||
 | 
			
		||||
    // Locks to ensure thread safety for key areas
 | 
			
		||||
    _write_locks: Arc<Mutex<HashMap<String, bool>>>,
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Transaction state
 | 
			
		||||
    transaction: RwLock<Option<TransactionState>>,
 | 
			
		||||
    transaction: Arc<RwLock<Option<TransactionState>>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for DB that allows registering models
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct DBBuilder {
 | 
			
		||||
    base_path: PathBuf,
 | 
			
		||||
    model_registrations: Vec<Box<dyn ModelRegistration>>,
 | 
			
		||||
    model_registrations: Vec<Arc<dyn ModelRegistration>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trait for model registration
 | 
			
		||||
@@ -129,33 +132,45 @@ impl DBBuilder {
 | 
			
		||||
            model_registrations: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    pub fn with_path<P: Into<PathBuf>>(base_path: P) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            base_path: base_path.into(),
 | 
			
		||||
            model_registrations: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Register a model type with the DB
 | 
			
		||||
    pub fn register_model<T: SledModel>(mut self) -> Self {
 | 
			
		||||
        self.model_registrations.push(Box::new(SledModelRegistration::<T>::new()));
 | 
			
		||||
        self.model_registrations
 | 
			
		||||
            .push(Arc::new(SledModelRegistration::<T>::new()));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Build the DB with the registered models
 | 
			
		||||
    pub fn build(self) -> SledDBResult<DB> {
 | 
			
		||||
    pub fn build(self) -> Result<DB, Box<EvalAltResult>> {
 | 
			
		||||
        let base_path = self.base_path;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Ensure base directory exists
 | 
			
		||||
        if !base_path.exists() {
 | 
			
		||||
            std::fs::create_dir_all(&base_path)?;
 | 
			
		||||
            std::fs::create_dir_all(&base_path).map_err(|e| {
 | 
			
		||||
                EvalAltResult::ErrorSystem("Could not create base dir".to_string(), Box::new(e))
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Register all models
 | 
			
		||||
        let mut type_map: HashMap<TypeId, Box<dyn AnyDbOperations>> = HashMap::new();
 | 
			
		||||
        
 | 
			
		||||
        let mut type_map: HashMap<TypeId, Arc<dyn AnyDbOperations>> = HashMap::new();
 | 
			
		||||
 | 
			
		||||
        for registration in self.model_registrations {
 | 
			
		||||
            let (type_id, db) = registration.register(&base_path)?;
 | 
			
		||||
            type_map.insert(type_id, db);
 | 
			
		||||
            let (type_id, db) = registration.register(&base_path).map_err(|e| {
 | 
			
		||||
                EvalAltResult::ErrorSystem("Could not register type".to_string(), Box::new(e))
 | 
			
		||||
            })?;
 | 
			
		||||
            type_map.insert(type_id, db.into());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let _write_locks = Arc::new(Mutex::new(HashMap::new()));
 | 
			
		||||
        let transaction = RwLock::new(None);
 | 
			
		||||
        
 | 
			
		||||
        let transaction = Arc::new(RwLock::new(None));
 | 
			
		||||
 | 
			
		||||
        Ok(DB {
 | 
			
		||||
            db_path: base_path,
 | 
			
		||||
            type_map,
 | 
			
		||||
@@ -169,15 +184,15 @@ impl DB {
 | 
			
		||||
    /// Create a new empty DB instance without any models
 | 
			
		||||
    pub fn new<P: Into<PathBuf>>(base_path: P) -> SledDBResult<Self> {
 | 
			
		||||
        let base_path = base_path.into();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Ensure base directory exists
 | 
			
		||||
        if !base_path.exists() {
 | 
			
		||||
            std::fs::create_dir_all(&base_path)?;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let _write_locks = Arc::new(Mutex::new(HashMap::new()));
 | 
			
		||||
        let transaction = RwLock::new(None);
 | 
			
		||||
        
 | 
			
		||||
        let transaction = Arc::new(RwLock::new(None));
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            db_path: base_path,
 | 
			
		||||
            type_map: HashMap::new(),
 | 
			
		||||
@@ -185,25 +200,27 @@ impl DB {
 | 
			
		||||
            transaction,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Transaction-related methods
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Begin a new transaction
 | 
			
		||||
    pub fn begin_transaction(&self) -> SledDBResult<()> {
 | 
			
		||||
        let mut tx = self.transaction.write().unwrap();
 | 
			
		||||
        if tx.is_some() {
 | 
			
		||||
            return Err(SledDBError::GeneralError("Transaction already in progress".into()));
 | 
			
		||||
            return Err(SledDBError::GeneralError(
 | 
			
		||||
                "Transaction already in progress".into(),
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        *tx = Some(TransactionState::new());
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Check if a transaction is active
 | 
			
		||||
    pub fn has_active_transaction(&self) -> bool {
 | 
			
		||||
        let tx = self.transaction.read().unwrap();
 | 
			
		||||
        tx.is_some() && tx.as_ref().unwrap().active
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Apply a set operation with the serialized data - bypass transaction check
 | 
			
		||||
    fn apply_set_operation(&self, model_type: TypeId, serialized: &[u8]) -> SledDBResult<()> {
 | 
			
		||||
        // Get the database operations for this model type
 | 
			
		||||
@@ -211,39 +228,47 @@ impl DB {
 | 
			
		||||
            // Just pass the raw serialized data to a special raw insert method
 | 
			
		||||
            return db_ops.insert_any_raw(serialized);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Err(SledDBError::GeneralError(format!("No DB registered for type ID {:?}", model_type)))
 | 
			
		||||
 | 
			
		||||
        Err(SledDBError::GeneralError(format!(
 | 
			
		||||
            "No DB registered for type ID {:?}",
 | 
			
		||||
            model_type
 | 
			
		||||
        )))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Commit the current transaction, applying all operations
 | 
			
		||||
    pub fn commit_transaction(&self) -> SledDBResult<()> {
 | 
			
		||||
        let mut tx_guard = self.transaction.write().unwrap();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(tx_state) = tx_guard.take() {
 | 
			
		||||
            if !tx_state.active {
 | 
			
		||||
                return Err(SledDBError::GeneralError("Transaction not active".into()));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Execute all operations in the transaction
 | 
			
		||||
            for op in tx_state.operations {
 | 
			
		||||
                match op {
 | 
			
		||||
                    DbOperation::Set { model_type, serialized } => {
 | 
			
		||||
                    DbOperation::Set {
 | 
			
		||||
                        model_type,
 | 
			
		||||
                        serialized,
 | 
			
		||||
                    } => {
 | 
			
		||||
                        self.apply_set_operation(model_type, &serialized)?;
 | 
			
		||||
                    },
 | 
			
		||||
                    }
 | 
			
		||||
                    DbOperation::Delete { model_type, id } => {
 | 
			
		||||
                        let db_ops = self.type_map.get(&model_type)
 | 
			
		||||
                        let db_ops = self
 | 
			
		||||
                            .type_map
 | 
			
		||||
                            .get(&model_type)
 | 
			
		||||
                            .ok_or_else(|| SledDBError::TypeError)?;
 | 
			
		||||
                        db_ops.delete(&id)?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(SledDBError::GeneralError("No active transaction".into()))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Rollback the current transaction, discarding all operations
 | 
			
		||||
    pub fn rollback_transaction(&self) -> SledDBResult<()> {
 | 
			
		||||
        let mut tx = self.transaction.write().unwrap();
 | 
			
		||||
@@ -253,79 +278,85 @@ impl DB {
 | 
			
		||||
        *tx = None;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Get the path to the database
 | 
			
		||||
    pub fn path(&self) -> &PathBuf {
 | 
			
		||||
        &self.db_path
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Generic methods that work with any supported model type
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Insert a model instance into its appropriate database based on type
 | 
			
		||||
    pub fn set<T: SledModel>(&self, model: &T) -> SledDBResult<()> {
 | 
			
		||||
        // Try to acquire a write lock on the transaction
 | 
			
		||||
        let mut tx_guard = self.transaction.write().unwrap();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Check if there's an active transaction
 | 
			
		||||
        if let Some(tx_state) = tx_guard.as_mut() {
 | 
			
		||||
            if tx_state.active {
 | 
			
		||||
                // Serialize the model for later use
 | 
			
		||||
                let serialized = bincode::serialize(model)?;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Record a Set operation in the transaction
 | 
			
		||||
                tx_state.operations.push(DbOperation::Set {
 | 
			
		||||
                    model_type: TypeId::of::<T>(),
 | 
			
		||||
                    serialized,
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // If we got here, either there's no transaction or it's not active
 | 
			
		||||
        // Drop the write lock before doing a direct database operation
 | 
			
		||||
        drop(tx_guard);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Execute directly
 | 
			
		||||
        match self.type_map.get(&TypeId::of::<T>()) {
 | 
			
		||||
            Some(db_ops) => db_ops.insert_any(model),
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Check the transaction state for the given type and id
 | 
			
		||||
    fn check_transaction<T: SledModel>(&self, id: &str) -> Option<Result<Option<T>, SledDBError>> {
 | 
			
		||||
        // Try to acquire a read lock on the transaction
 | 
			
		||||
        let tx_guard = self.transaction.read().unwrap();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(tx_state) = tx_guard.as_ref() {
 | 
			
		||||
            if !tx_state.active {
 | 
			
		||||
                return None;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let type_id = TypeId::of::<T>();
 | 
			
		||||
            let id_str = id.to_string();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Process operations in reverse order (last operation wins)
 | 
			
		||||
            for op in tx_state.operations.iter().rev() {
 | 
			
		||||
                match op {
 | 
			
		||||
                    // First check if this ID has been deleted in the transaction
 | 
			
		||||
                    DbOperation::Delete { model_type, id: op_id } => {
 | 
			
		||||
                    DbOperation::Delete {
 | 
			
		||||
                        model_type,
 | 
			
		||||
                        id: op_id,
 | 
			
		||||
                    } => {
 | 
			
		||||
                        if *model_type == type_id && op_id == id {
 | 
			
		||||
                            // Return NotFound error for deleted records
 | 
			
		||||
                            return Some(Err(SledDBError::NotFound(id.to_string())));
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    }
 | 
			
		||||
                    // Then check if it has been set in the transaction
 | 
			
		||||
                    DbOperation::Set { model_type, serialized } => {
 | 
			
		||||
                    DbOperation::Set {
 | 
			
		||||
                        model_type,
 | 
			
		||||
                        serialized,
 | 
			
		||||
                    } => {
 | 
			
		||||
                        if *model_type == type_id {
 | 
			
		||||
                            // Try to deserialize and check the ID
 | 
			
		||||
                            match bincode::deserialize::<T>(serialized) {
 | 
			
		||||
                                Ok(model) => {
 | 
			
		||||
                                if model.get_id() == id_str {
 | 
			
		||||
                                    return Some(Ok(Some(model)));
 | 
			
		||||
                                    if model.get_id() == id_str {
 | 
			
		||||
                                        return Some(Ok(Some(model)));
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                                },
 | 
			
		||||
                                Err(_) => continue, // Skip if deserialization fails
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@@ -333,7 +364,7 @@ impl DB {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Not found in transaction (continue to database)
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
@@ -348,7 +379,7 @@ impl DB {
 | 
			
		||||
                Ok(None) => {} // Should never happen
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // If no pending value, look up from the database
 | 
			
		||||
        match self.type_map.get(&TypeId::of::<T>()) {
 | 
			
		||||
            Some(db_ops) => {
 | 
			
		||||
@@ -358,16 +389,16 @@ impl DB {
 | 
			
		||||
                    Ok(t) => Ok(*t),
 | 
			
		||||
                    Err(_) => Err(SledDBError::TypeError),
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Delete a model instance by its ID and type
 | 
			
		||||
    pub fn delete<T: SledModel>(&self, id: &str) -> SledDBResult<()> {
 | 
			
		||||
        // Try to acquire a write lock on the transaction
 | 
			
		||||
        let mut tx_guard = self.transaction.write().unwrap();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Check if there's an active transaction
 | 
			
		||||
        if let Some(tx_state) = tx_guard.as_mut() {
 | 
			
		||||
            if tx_state.active {
 | 
			
		||||
@@ -376,22 +407,22 @@ impl DB {
 | 
			
		||||
                    model_type: TypeId::of::<T>(),
 | 
			
		||||
                    id: id.to_string(),
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                return Ok(());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // If we got here, either there's no transaction or it's not active
 | 
			
		||||
        // Drop the write lock before doing a direct database operation
 | 
			
		||||
        drop(tx_guard);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Execute directly
 | 
			
		||||
        match self.type_map.get(&TypeId::of::<T>()) {
 | 
			
		||||
            Some(db_ops) => db_ops.delete(id),
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// List all model instances of a specific type
 | 
			
		||||
    pub fn list<T: SledModel>(&self) -> SledDBResult<Vec<T>> {
 | 
			
		||||
        // Look up the correct DB operations for type T in our type map
 | 
			
		||||
@@ -403,24 +434,27 @@ impl DB {
 | 
			
		||||
                    Ok(vec_t) => Ok(*vec_t),
 | 
			
		||||
                    Err(_) => Err(SledDBError::TypeError),
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            None => Err(SledDBError::TypeError),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Register a model type with this DB instance
 | 
			
		||||
    pub fn register<T: SledModel>(&mut self) -> SledDBResult<()> {
 | 
			
		||||
        let db_path = self.db_path.join(T::db_prefix());
 | 
			
		||||
        let db: SledDB<T> = SledDB::open(db_path)?;
 | 
			
		||||
        self.type_map.insert(TypeId::of::<T>(), Box::new(db));
 | 
			
		||||
        self.type_map.insert(TypeId::of::<T>(), Arc::new(db));
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Get a typed handle to a registered model DB
 | 
			
		||||
    pub fn db_for<T: SledModel>(&self) -> SledDBResult<&dyn AnyDbOperations> {
 | 
			
		||||
        match self.type_map.get(&TypeId::of::<T>()) {
 | 
			
		||||
            Some(db) => Ok(&**db),
 | 
			
		||||
            None => Err(SledDBError::GeneralError(format!("No DB registered for type {}", std::any::type_name::<T>()))),
 | 
			
		||||
            None => Err(SledDBError::GeneralError(format!(
 | 
			
		||||
                "No DB registered for type {}",
 | 
			
		||||
                std::any::type_name::<T>()
 | 
			
		||||
            ))),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,25 +5,27 @@ macro_rules! impl_model_methods {
 | 
			
		||||
        impl DB {
 | 
			
		||||
            paste::paste! {
 | 
			
		||||
                /// Insert a model instance into the database
 | 
			
		||||
                pub fn [<insert_ $singular>](&self, item: &$model) -> SledDBResult<()> {
 | 
			
		||||
                    self.set(item)
 | 
			
		||||
                pub fn [<insert_ $singular>](&mut self, item: $model) -> Result<(), Box<rhai::EvalAltResult>> {
 | 
			
		||||
                    Ok(self.set(&item).map_err(|e| {
 | 
			
		||||
                        rhai::EvalAltResult::ErrorSystem("could not insert $singular".to_string(), Box::new(e))
 | 
			
		||||
                    })?)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// Get a model instance by its ID
 | 
			
		||||
                pub fn [<get_ $singular>](&self, id: u32) -> SledDBResult<$model> {
 | 
			
		||||
                pub fn [<get_ $singular>](&mut self, id: i64) -> SledDBResult<$model> {
 | 
			
		||||
                    self.get::<$model>(&id.to_string())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// Delete a model instance by its ID
 | 
			
		||||
                pub fn [<delete_ $singular>](&self, id: u32) -> SledDBResult<()> {
 | 
			
		||||
                pub fn [<delete_ $singular>](&mut self, id: i64) -> SledDBResult<()> {
 | 
			
		||||
                    self.delete::<$model>(&id.to_string())
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /// List all model instances
 | 
			
		||||
                pub fn [<list_ $plural>](&self) -> SledDBResult<Vec<$model>> {
 | 
			
		||||
                pub fn [<list_ $plural>](&mut self) -> SledDBResult<Vec<$model>> {
 | 
			
		||||
                    self.list::<$model>()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
 | 
			
		||||
use crate::db::base::{SledModel, Storable};
 | 
			
		||||
use chrono::{DateTime, Duration, Utc};
 | 
			
		||||
use rhai::{CustomType, EvalAltResult, TypeBuilder};
 | 
			
		||||
use serde::{Deserialize, Serialize}; // Import Sled traits from db module
 | 
			
		||||
 | 
			
		||||
/// Currency represents a monetary value with amount and currency code
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
 | 
			
		||||
pub struct Currency {
 | 
			
		||||
    pub amount: f64,
 | 
			
		||||
    pub currency_code: String,
 | 
			
		||||
@@ -17,9 +18,14 @@ impl Currency {
 | 
			
		||||
            currency_code,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn amount(&mut self) -> f64 {
 | 
			
		||||
        self.amount
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Currency
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct CurrencyBuilder {
 | 
			
		||||
    amount: Option<f64>,
 | 
			
		||||
    currency_code: Option<String>,
 | 
			
		||||
@@ -47,7 +53,7 @@ impl CurrencyBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the Currency object
 | 
			
		||||
    pub fn build(self) -> Result<Currency, &'static str> {
 | 
			
		||||
    pub fn build(self) -> Result<Currency, Box<EvalAltResult>> {
 | 
			
		||||
        Ok(Currency {
 | 
			
		||||
            amount: self.amount.ok_or("amount is required")?,
 | 
			
		||||
            currency_code: self.currency_code.ok_or("currency_code is required")?,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use chrono::{DateTime, Utc, Duration};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
 | 
			
		||||
 | 
			
		||||
use crate::db::base::{SledModel, Storable};
 | 
			
		||||
use chrono::{DateTime, Duration, Utc};
 | 
			
		||||
use rhai::{CustomType, EvalAltResult, TypeBuilder, export_module};
 | 
			
		||||
use serde::{Deserialize, Serialize}; // Import Sled traits from db module
 | 
			
		||||
 | 
			
		||||
/// ProductType represents the type of a product
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 | 
			
		||||
@@ -20,17 +20,17 @@ pub enum ProductStatus {
 | 
			
		||||
/// ProductComponent represents a component of a product
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct ProductComponent {
 | 
			
		||||
    pub id: u32,
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub quantity: i32,
 | 
			
		||||
    pub quantity: i64,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ProductComponent {
 | 
			
		||||
    /// Create a new product component with default timestamps
 | 
			
		||||
    pub fn new(id: u32, name: String, description: String, quantity: i32) -> Self {
 | 
			
		||||
    pub fn new(id: i64, name: String, description: String, quantity: i64) -> Self {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Self {
 | 
			
		||||
            id,
 | 
			
		||||
@@ -44,11 +44,12 @@ impl ProductComponent {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for ProductComponent
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct ProductComponentBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    id: Option<i64>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    quantity: Option<i32>,
 | 
			
		||||
    quantity: Option<i64>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
@@ -67,7 +68,7 @@ impl ProductComponentBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
    pub fn id(mut self, id: i64) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
@@ -85,7 +86,7 @@ impl ProductComponentBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the quantity
 | 
			
		||||
    pub fn quantity(mut self, quantity: i32) -> Self {
 | 
			
		||||
    pub fn quantity(mut self, quantity: i64) -> Self {
 | 
			
		||||
        self.quantity = Some(quantity);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
@@ -103,7 +104,7 @@ impl ProductComponentBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the ProductComponent object
 | 
			
		||||
    pub fn build(self) -> Result<ProductComponent, &'static str> {
 | 
			
		||||
    pub fn build(self) -> Result<ProductComponent, Box<EvalAltResult>> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Ok(ProductComponent {
 | 
			
		||||
            id: self.id.ok_or("id is required")?,
 | 
			
		||||
@@ -117,9 +118,9 @@ impl ProductComponentBuilder {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Product represents a product or service offered by the Freezone
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
 | 
			
		||||
pub struct Product {
 | 
			
		||||
    pub id: u32,
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub price: Currency,
 | 
			
		||||
@@ -128,7 +129,7 @@ pub struct Product {
 | 
			
		||||
    pub status: ProductStatus,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    pub max_amount: u16, // means allows us to define how many max of this there are
 | 
			
		||||
    pub max_amount: i64, // means allows us to define how many max of this there are
 | 
			
		||||
    pub purchase_till: DateTime<Utc>,
 | 
			
		||||
    pub active_till: DateTime<Utc>, // after this product no longer active if e.g. a service
 | 
			
		||||
    pub components: Vec<ProductComponent>,
 | 
			
		||||
@@ -139,14 +140,14 @@ pub struct Product {
 | 
			
		||||
impl Product {
 | 
			
		||||
    /// Create a new product with default timestamps
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        id: u32,
 | 
			
		||||
        id: i64,
 | 
			
		||||
        name: String,
 | 
			
		||||
        description: String,
 | 
			
		||||
        price: Currency,
 | 
			
		||||
        type_: ProductType,
 | 
			
		||||
        category: String,
 | 
			
		||||
        status: ProductStatus,
 | 
			
		||||
        max_amount: u16,
 | 
			
		||||
        max_amount: i64,
 | 
			
		||||
        validity_days: i64, // How many days the product is valid after purchase
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
@@ -167,30 +168,30 @@ impl Product {
 | 
			
		||||
            components: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Add a component to this product
 | 
			
		||||
    pub fn add_component(&mut self, component: ProductComponent) {
 | 
			
		||||
        self.components.push(component);
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Update the purchase availability timeframe
 | 
			
		||||
    pub fn set_purchase_period(&mut self, purchase_till: DateTime<Utc>) {
 | 
			
		||||
        self.purchase_till = purchase_till;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Update the active timeframe
 | 
			
		||||
    pub fn set_active_period(&mut self, active_till: DateTime<Utc>) {
 | 
			
		||||
        self.active_till = active_till;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Check if the product is available for purchase
 | 
			
		||||
    pub fn is_purchasable(&self) -> bool {
 | 
			
		||||
        self.status == ProductStatus::Available && Utc::now() <= self.purchase_till
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Check if the product is still active (for services)
 | 
			
		||||
    pub fn is_active(&self) -> bool {
 | 
			
		||||
        Utc::now() <= self.active_till
 | 
			
		||||
@@ -198,8 +199,9 @@ impl Product {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Product
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct ProductBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    id: Option<i64>,
 | 
			
		||||
    name: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    price: Option<Currency>,
 | 
			
		||||
@@ -208,7 +210,7 @@ pub struct ProductBuilder {
 | 
			
		||||
    status: Option<ProductStatus>,
 | 
			
		||||
    created_at: Option<DateTime<Utc>>,
 | 
			
		||||
    updated_at: Option<DateTime<Utc>>,
 | 
			
		||||
    max_amount: Option<u16>,
 | 
			
		||||
    max_amount: Option<i64>,
 | 
			
		||||
    purchase_till: Option<DateTime<Utc>>,
 | 
			
		||||
    active_till: Option<DateTime<Utc>>,
 | 
			
		||||
    components: Vec<ProductComponent>,
 | 
			
		||||
@@ -237,7 +239,7 @@ impl ProductBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the id
 | 
			
		||||
    pub fn id(mut self, id: u32) -> Self {
 | 
			
		||||
    pub fn id(mut self, id: i64) -> Self {
 | 
			
		||||
        self.id = Some(id);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
@@ -279,7 +281,7 @@ impl ProductBuilder {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the max amount
 | 
			
		||||
    pub fn max_amount(mut self, max_amount: u16) -> Self {
 | 
			
		||||
    pub fn max_amount(mut self, max_amount: i64) -> Self {
 | 
			
		||||
        self.max_amount = Some(max_amount);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
@@ -313,13 +315,15 @@ impl ProductBuilder {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let created_at = self.created_at.unwrap_or(now);
 | 
			
		||||
        let updated_at = self.updated_at.unwrap_or(now);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Calculate purchase_till and active_till based on validity_days if not set directly
 | 
			
		||||
        let purchase_till = self.purchase_till.unwrap_or(now + Duration::days(365));
 | 
			
		||||
        let active_till = if let Some(validity_days) = self.validity_days {
 | 
			
		||||
            self.active_till.unwrap_or(now + Duration::days(validity_days))
 | 
			
		||||
            self.active_till
 | 
			
		||||
                .unwrap_or(now + Duration::days(validity_days))
 | 
			
		||||
        } else {
 | 
			
		||||
            self.active_till.ok_or("Either active_till or validity_days must be provided")?
 | 
			
		||||
            self.active_till
 | 
			
		||||
                .ok_or("Either active_till or validity_days must be provided")?
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(Product {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
use crate::models::biz::Currency; // Use crate:: for importing from the module
 | 
			
		||||
use crate::db::base::{SledModel, Storable}; // Import Sled traits from db module
 | 
			
		||||
use crate::db::base::{SledModel, Storable};
 | 
			
		||||
use crate::models::biz::Currency; // Use crate:: for importing from the module // Import Sled traits from db module
 | 
			
		||||
// use super::db::Model; // Removed old Model trait import
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use rhai::{CustomType, TypeBuilder};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
// use std::collections::HashMap; // Removed unused import
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +44,7 @@ impl SaleItem {
 | 
			
		||||
            amount,
 | 
			
		||||
            currency_code: unit_price.currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            id,
 | 
			
		||||
            sale_id,
 | 
			
		||||
@@ -58,6 +59,7 @@ impl SaleItem {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for SaleItem
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct SaleItemBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    sale_id: Option<u32>,
 | 
			
		||||
@@ -130,7 +132,7 @@ impl SaleItemBuilder {
 | 
			
		||||
    pub fn build(self) -> Result<SaleItem, &'static str> {
 | 
			
		||||
        let unit_price = self.unit_price.ok_or("unit_price is required")?;
 | 
			
		||||
        let quantity = self.quantity.ok_or("quantity is required")?;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Calculate subtotal
 | 
			
		||||
        let amount = unit_price.amount * quantity as f64;
 | 
			
		||||
        let subtotal = Currency {
 | 
			
		||||
@@ -152,7 +154,7 @@ impl SaleItemBuilder {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sale represents a sale of products or services
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
 | 
			
		||||
pub struct Sale {
 | 
			
		||||
    pub id: u32,
 | 
			
		||||
    pub company_id: u32,
 | 
			
		||||
@@ -184,7 +186,10 @@ impl Sale {
 | 
			
		||||
            company_id,
 | 
			
		||||
            buyer_name,
 | 
			
		||||
            buyer_email,
 | 
			
		||||
            total_amount: Currency { amount: 0.0, currency_code },
 | 
			
		||||
            total_amount: Currency {
 | 
			
		||||
                amount: 0.0,
 | 
			
		||||
                currency_code,
 | 
			
		||||
            },
 | 
			
		||||
            status,
 | 
			
		||||
            sale_date: now,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
@@ -192,12 +197,12 @@ impl Sale {
 | 
			
		||||
            items: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Add an item to the sale and update the total amount
 | 
			
		||||
    pub fn add_item(&mut self, item: SaleItem) {
 | 
			
		||||
        // Make sure the item's sale_id matches this sale
 | 
			
		||||
        assert_eq!(self.id, item.sale_id, "Item sale_id must match sale id");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Update the total amount
 | 
			
		||||
        if self.items.is_empty() {
 | 
			
		||||
            // First item, initialize the total amount with the same currency
 | 
			
		||||
@@ -210,14 +215,14 @@ impl Sale {
 | 
			
		||||
            // (Assumes all items have the same currency)
 | 
			
		||||
            self.total_amount.amount += item.subtotal.amount;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add the item to the list
 | 
			
		||||
        self.items.push(item);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Update the sale timestamp
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /// Update the status of the sale
 | 
			
		||||
    pub fn update_status(&mut self, status: SaleStatus) {
 | 
			
		||||
        self.status = status;
 | 
			
		||||
@@ -226,6 +231,7 @@ impl Sale {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Builder for Sale
 | 
			
		||||
#[derive(Clone, CustomType)]
 | 
			
		||||
pub struct SaleBuilder {
 | 
			
		||||
    id: Option<u32>,
 | 
			
		||||
    company_id: Option<u32>,
 | 
			
		||||
@@ -311,20 +317,20 @@ impl SaleBuilder {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let id = self.id.ok_or("id is required")?;
 | 
			
		||||
        let currency_code = self.currency_code.ok_or("currency_code is required")?;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Initialize with empty total amount
 | 
			
		||||
        let mut total_amount = Currency {
 | 
			
		||||
            amount: 0.0,
 | 
			
		||||
            currency_code: currency_code.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Calculate total amount from items
 | 
			
		||||
        for item in &self.items {
 | 
			
		||||
            // Make sure the item's sale_id matches this sale
 | 
			
		||||
            if item.sale_id != id {
 | 
			
		||||
                return Err("Item sale_id must match sale id");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if total_amount.amount == 0.0 {
 | 
			
		||||
                // First item, initialize the total amount with the same currency
 | 
			
		||||
                total_amount = Currency {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user