Inject some builders in script

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet 2025-04-04 15:59:30 +02:00
parent 04233e6f1a
commit 53d8d184c4
Signed by untrusted user who does not match committer: lee
GPG Key ID: 72CBFB5FDA7FE025
8 changed files with 404 additions and 190 deletions

View File

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

View File

@ -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}, {})",
println!(
" - {} (${:.2}, {})",
product.name,
product.price.amount,
if product.is_purchasable() { "Available" } else { "Unavailable" }
if product.is_purchasable() {
"Available"
} else {
"Unavailable"
}
);
}
@ -133,10 +294,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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,11 +325,14 @@ 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);

View File

@ -1,5 +1,6 @@
use bincode;
use brotli::{CompressorReader, Decompressor};
use rhai::CustomType;
use serde::{Deserialize, Serialize};
use sled;
use std::fmt::Debug;
@ -41,12 +42,8 @@ pub trait Storable: Serialize + for<'de> Deserialize<'de> + Sized {
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)
@ -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
}
}

View File

@ -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)]
@ -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
@ -130,31 +133,43 @@ impl DBBuilder {
}
}
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,
@ -176,7 +191,7 @@ impl DB {
}
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,
@ -192,7 +207,9 @@ impl DB {
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(())
@ -212,7 +229,10 @@ impl DB {
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
@ -227,11 +247,16 @@ impl DB {
// 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)?;
}
@ -310,22 +335,28 @@ impl DB {
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
}
}
@ -358,7 +389,7 @@ impl DB {
Ok(t) => Ok(*t),
Err(_) => Err(SledDBError::TypeError),
}
},
}
None => Err(SledDBError::TypeError),
}
}
@ -403,7 +434,7 @@ impl DB {
Ok(vec_t) => Ok(*vec_t),
Err(_) => Err(SledDBError::TypeError),
}
},
}
None => Err(SledDBError::TypeError),
}
}
@ -412,7 +443,7 @@ impl DB {
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(())
}
@ -420,7 +451,10 @@ impl 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>()
))),
}
}
}

View File

@ -5,22 +5,24 @@ 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>()
}
}

View File

@ -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")?,

View File

@ -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();
@ -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
}
@ -317,9 +319,11 @@ impl ProductBuilder {
// 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 {

View File

@ -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
@ -58,6 +59,7 @@ impl SaleItem {
}
/// Builder for SaleItem
#[derive(Clone, CustomType)]
pub struct SaleItemBuilder {
id: Option<u32>,
sale_id: Option<u32>,
@ -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,
@ -226,6 +231,7 @@ impl Sale {
}
/// Builder for Sale
#[derive(Clone, CustomType)]
pub struct SaleBuilder {
id: Option<u32>,
company_id: Option<u32>,