From abbed9a1a1dc056dff96f10f828ec02b605c9616 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Tue, 3 Jun 2025 21:50:08 +0300 Subject: [PATCH 1/9] final db models wip --- heromodels/.DS_Store | Bin 0 -> 8196 bytes heromodels/Cargo.lock | 60 +- heromodels/Cargo.toml | 4 +- heromodels/data/0.db | Bin 9670 -> 19320 bytes heromodels/data/lookup/data | Bin 4000000 -> 4000000 bytes .../examples/biz_rhai/{biz.rs => biz.rhai} | 0 .../examples/calendar_rhai/calendar.rhai | 2 +- heromodels/examples/calendar_rhai/example.rs | 4 + heromodels/index/0.db | Bin 5041 -> 5629 bytes heromodels/index/lookup/data | Bin 4000000 -> 4000000 bytes heromodels/src/herodb/model_methods.rs | 9 + heromodels/src/lib.rs | 3 + heromodels/src/models/biz/company.rs | 103 +- heromodels/src/models/biz/product.rs | 66 +- heromodels/src/models/biz/rhai.rs | 928 +++++++++----- heromodels/src/models/biz/sale.rs | 101 +- heromodels/src/models/biz/shareholder.rs | 37 +- heromodels/src/models/calendar/calendar.rs | 85 +- heromodels/src/models/calendar/mod.rs | 2 +- heromodels/src/models/calendar/rhai.rs | 422 ++++-- heromodels/src/models/core/comment.rs | 25 +- heromodels/src/models/finance/account.rs | 113 +- heromodels/src/models/finance/asset.rs | 76 +- heromodels/src/models/finance/marketplace.rs | 248 ++-- heromodels/src/models/finance/rhai.rs | 1142 +++++++++++------ heromodels/src/models/flow/flow.rs | 6 +- heromodels/src/models/flow/flow_step.rs | 12 + heromodels/src/models/flow/rhai.rs | 494 +++++-- .../src/models/flow/signature_requirement.rs | 3 +- .../src/models/governance/attached_file.rs | 27 + heromodels/src/models/governance/mod.rs | 5 +- heromodels/src/models/governance/proposal.rs | 64 +- heromodels/src/models/legal/contract.rs | 6 +- heromodels/src/models/mod.rs | 3 +- heromodels/src/models/projects/epic.rs | 83 ++ heromodels/src/models/projects/mod.rs | 10 + heromodels/src/models/projects/sprint.rs | 81 ++ .../src/models/projects/sprint_enums.rs | 16 + heromodels/src/models/projects/task.rs | 93 ++ heromodels/src/models/projects/task_enums.rs | 32 + heromodels/src/models/userexample/user.rs | 2 +- heromodels/tmp/event_test_db/index/0.db | Bin 0 -> 35 bytes .../tmp/event_test_db/index/lookup/.inc | 1 + 43 files changed, 2958 insertions(+), 1410 deletions(-) create mode 100644 heromodels/.DS_Store rename heromodels/examples/biz_rhai/{biz.rs => biz.rhai} (100%) create mode 100644 heromodels/src/models/governance/attached_file.rs create mode 100644 heromodels/src/models/projects/epic.rs create mode 100644 heromodels/src/models/projects/sprint.rs create mode 100644 heromodels/src/models/projects/sprint_enums.rs create mode 100644 heromodels/src/models/projects/task.rs create mode 100644 heromodels/src/models/projects/task_enums.rs create mode 100644 heromodels/tmp/event_test_db/index/0.db create mode 100644 heromodels/tmp/event_test_db/index/lookup/.inc diff --git a/heromodels/.DS_Store b/heromodels/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6862beaf904ed17fb069f61a7c1587e866913d7b GIT binary patch literal 8196 zcmeHMJ#Q015S_)wG6ErEB$NW7Lkd+IOao1%OJq8#q@xM`!ii*amK_KQ61ty2l!6M0 z2AWXtQ6VHs{s0A{MEn2{T{_&;% X<$5?+1(Z%@?^*5$W>*KoxzzC#ZYxlaS?;d&(Fm(G4ZNVAc7JU##B zhRq{!uYNSiXPTYNtF#buUMFdT?tv3zCo`hOOD|ll%;nm*r7$Lp9KS## z8dA%NT{zg9Xk^^Z#X}CPpNyH*I`K*Y$18E|5wQ!zn=da2+_eHOQvSjdrS6^7zPnKVE)2eqR0&<iSPziyZMJy4gG(d^-ciR~7iW zK=%N38=`Uz^cJqOYM0ATVthRRZd|{dzLPazR$m9FspA=qbA)kf^{Tw4X4*)pZKNA-JL<{o|Iz0~jj Ly0U&a&J6qp>Pn%; literal 0 HcmV?d00001 diff --git a/heromodels/Cargo.lock b/heromodels/Cargo.lock index d8cd394..4d7c0f9 100644 --- a/heromodels/Cargo.lock +++ b/heromodels/Cargo.lock @@ -2,14 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adapter_macros" -version = "0.1.0" -dependencies = [ - "chrono", - "rhai", -] - [[package]] name = "ahash" version = "0.8.12" @@ -177,12 +169,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -193,21 +179,19 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" name = "heromodels" version = "0.1.0" dependencies = [ - "adapter_macros", "bincode", "chrono", "heromodels-derive", "heromodels_core", "ourdb", "rhai", - "rhai_autobind_macros", "rhai_client_macros", - "rhai_wrapper", "serde", "serde_json", "strum", "strum_macros", "tst", + "uuid", ] [[package]] @@ -419,16 +403,6 @@ dependencies = [ "thin-vec", ] -[[package]] -name = "rhai_autobind_macros" -version = "0.1.0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rhai_client_macros" version = "0.1.0" @@ -450,25 +424,6 @@ dependencies = [ "syn", ] -[[package]] -name = "rhai_macros_derive" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "rhai_wrapper" -version = "0.1.0" -dependencies = [ - "chrono", - "rhai", - "rhai_macros_derive", - "serde", -] - [[package]] name = "rust_decimal" version = "1.37.1" @@ -570,7 +525,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", @@ -643,6 +598,17 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 67c5be5..392a73d 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -14,13 +14,11 @@ ourdb = { path = "../ourdb" } tst = { path = "../tst" } heromodels-derive = { path = "../heromodels-derive" } heromodels_core = { path = "../heromodels_core" } -rhai_autobind_macros = { path = "../../rhaj/rhai_autobind_macros" } -rhai_wrapper = { path = "../../rhaj/rhai_wrapper" } rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] } # Added "decimal" feature, sync for Arc> -adapter_macros = { path = "../adapter_macros" } rhai_client_macros = { path = "../rhai_client_macros" } strum = "0.26" strum_macros = "0.26" +uuid = { version = "1.17.0", features = ["v4"] } [features] default = [] diff --git a/heromodels/data/0.db b/heromodels/data/0.db index c68be08143e4d952764ee80d660d001b0f0788cf..531af475d5bea8321d9e512611467eebeb3ba30e 100644 GIT binary patch delta 1654 zcmai!Sx8i26vw|A*V3EYcwMnEW?a*u)NxcyZPXM~5;P)QN~@<)8bNp0PVyxAgqb&)N;tWj3Q(>6yB0*D zZBlEbA&;J$gVs_XPSMnRHk#Ftv43YZh{6?`#mxyTC8WAr{M4{@<@g8`HBg3rU_kJ5w>{hy zQIgDK6IByN6a`k4CK(y0*;0tg%U7D{WQ%y<`L~{l3AgE&!n!(p?EE&)0_Dtm3p&OB6a5$ zR&|QbL7GA qK}1kiK3T!PpWxqroqvg4$2bljmIbqcFq4Qf=A zB5GEPTGgh%ZM!nNu`w5tmZVY Z1uZJ0B`qtf73GxIstPJTSAx>X&oA2WY}5b% delta 267 zcmW;DOIASv0Dxhn2)RW_BocW)q8CmJY0_4lW6(GYXm~f!z{X6P_0@mQH~iPzeRNGS z@?Y!y`}6fH*o47H7(|J2K%7I4NRZ^16lpS?kmc_^<%}G8&M8oz8sZiyL8rRfm oa6^+@TC};NLzf Result<(), Box> { // Initialize Rhai engine @@ -14,6 +17,7 @@ fn main() -> Result<(), Box> { // Register the Calendar type with Rhai // This function is generated by the #[rhai_model_export] attribute Calendar::register_rhai_bindings_for_calendar(&mut engine, db.clone()); + register_rhai_engine_functions(&mut engine, db.clone()); // Register a function to get the database instance engine.register_fn("get_db", move || db.clone()); diff --git a/heromodels/index/0.db b/heromodels/index/0.db index 029921efab168c495440e06265e667251f1187d2..7f8214cf410cd3b98f1a06b9284ebec81a9060ac 100644 GIT binary patch delta 600 zcmZXQJxc>Y5Qb;GO*UqmgS#BaU6M<6f)Ee_`UC7l{HQEN8w;(qyHZ633rQ3qAXtmn zSfsQSLD3@ESXtQFScRxFyRLBh7CSRL&%QgmSu&is%Dgm$#pCt>Nab^EQosT&BRF_- zc+)C`&yC9|uDpG0Q7{-$Xc3MP99_)4aOP#ktw_1UnBZNj2;99+pQ;qRzVXu_ia+;O zVfFofl8<>`#!yl&B+r*~tin!reT+drF@_o>Op4ryH8`2sJk{VlHd9KlL`y*^ZORR+ z8ay1dZ@BWeji)A+x!zJ7>oDxR#+>;-zc@;H5ecOUTy9v^VI@4{F8H_D()VkYLE>|4 z3~S@PY0Z9^*O#e~iqJr~$S?d=3`>W1&s6@R@}5$c!6qx@Fd!+veSI*F6jFTeReXWe<%#vf{BPRz3C1nsv`@*tBKa m3opI0W7nS7_Pz1eI|tsI_~4^MM~;2+*%x1(__mmaW%eIB%{`+4 delta 267 zcmWl|SvEoe007YQo)VR4ph+1L4OFjzk`GJk=);GfvICuV&}J;e0=n)wck%rfEL?5E z &'static str { - "company" - } - - fn get_id(&self) -> u32 { - self.base_data.id - } - - fn base_data_mut(&mut self) -> &mut BaseModelData { - &mut self.base_data - } - - // Override db_keys to provide custom indexes if needed - fn db_keys(&self) -> Vec { - vec![ - IndexKeyBuilder::new("name").value(self.name.clone()).build(), - IndexKeyBuilder::new("registration_number").value(self.registration_number.clone()).build(), - // Add other relevant keys, e.g., by status or type if frequently queried - ] - } -} - // --- Index Implementations (Example) --- pub struct CompanyNameIndex; @@ -98,13 +76,19 @@ impl Index for CompanyRegistrationNumberIndex { // --- Builder Pattern --- +impl BaseModelDataOps for Company { + fn get_base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } +} + impl Company { - pub fn new(name: String, registration_number: String, incorporation_date: i64) -> Self { // incorporation_date to i64 + pub fn new() -> Self { Self { base_data: BaseModelData::new(), - name, - registration_number, - incorporation_date, // This is i64 now + name: String::new(), + registration_number: String::new(), + incorporation_date: 0, fiscal_year_end: String::new(), email: String::new(), phone: String::new(), @@ -116,29 +100,44 @@ impl Company { status: CompanyStatus::default(), } } - - pub fn fiscal_year_end(mut self, fiscal_year_end: String) -> Self { - self.fiscal_year_end = fiscal_year_end; + + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + pub fn registration_number(mut self, registration_number: impl ToString) -> Self { + self.registration_number = registration_number.to_string(); + self + } + + pub fn incorporation_date(mut self, incorporation_date: i64) -> Self { + self.incorporation_date = incorporation_date; self } - pub fn email(mut self, email: String) -> Self { - self.email = email; + pub fn fiscal_year_end(mut self, fiscal_year_end: impl ToString) -> Self { + self.fiscal_year_end = fiscal_year_end.to_string(); self } - pub fn phone(mut self, phone: String) -> Self { - self.phone = phone; + pub fn email(mut self, email: impl ToString) -> Self { + self.email = email.to_string(); self } - pub fn website(mut self, website: String) -> Self { - self.website = website; + pub fn phone(mut self, phone: impl ToString) -> Self { + self.phone = phone.to_string(); self } - pub fn address(mut self, address: String) -> Self { - self.address = address; + pub fn website(mut self, website: impl ToString) -> Self { + self.website = website.to_string(); + self + } + + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); self } @@ -147,13 +146,13 @@ impl Company { self } - pub fn industry(mut self, industry: String) -> Self { - self.industry = industry; + pub fn industry(mut self, industry: impl ToString) -> Self { + self.industry = industry.to_string(); self } - pub fn description(mut self, description: String) -> Self { - self.description = description; + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); self } @@ -162,17 +161,5 @@ impl Company { self } - // Setter for base_data fields if needed directly, though usually handled by Model trait or new() - // Builder methods for created_at and modified_at can be more specific if needed, - // but Model::build() updates modified_at, and BaseModelData::new() sets created_at. - // If direct setting is required for tests or specific scenarios: - pub fn set_base_created_at(mut self, created_at: i64) -> Self { - self.base_data.created_at = created_at; - self - } - - pub fn set_base_modified_at(mut self, modified_at: i64) -> Self { - self.base_data.modified_at = modified_at; - self - } + // Base data operations are now handled by BaseModelDataOps trait } diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index 031a593..a87cb2d 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,6 +1,8 @@ use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; use heromodels_core::Model; +use heromodels_core::BaseModelDataOps; +use heromodels_derive::model; // ProductType represents the type of a product #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -27,18 +29,18 @@ pub struct ProductComponent { } impl ProductComponent { - // Minimal constructor - pub fn new(name: String) -> Self { + // Minimal constructor with no parameters + pub fn new() -> Self { Self { - name, + name: String::new(), description: String::new(), quantity: 1, // Default quantity to 1 } } // Builder methods - pub fn description(mut self, description: String) -> Self { - self.description = description; + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); self } @@ -47,14 +49,15 @@ impl ProductComponent { self } - pub fn name(mut self, name: String) -> Self { - self.name = name; + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); self } } // Product represents a product or service offered -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +#[model] pub struct Product { pub base_data: BaseModelData, pub name: String, @@ -69,20 +72,6 @@ pub struct Product { pub components: Vec, } -impl Model for Product { - fn get_id(&self) -> u32 { - self.base_data.id - } - - fn db_prefix() -> &'static str { - "prod" - } - - fn base_data_mut(&mut self) -> &mut BaseModelData { - &mut self.base_data - } -} - impl Product { pub fn new() -> Self { Self { @@ -101,13 +90,13 @@ impl Product { } // Builder methods - pub fn name(mut self, name: String) -> Self { - self.name = name; + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); self } - pub fn description(mut self, description: String) -> Self { - self.description = description; + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); self } @@ -121,8 +110,8 @@ impl Product { self } - pub fn category(mut self, category: String) -> Self { - self.category = category; + pub fn category(mut self, category: impl ToString) -> Self { + self.category = category.to_string(); self } @@ -156,24 +145,5 @@ impl Product { self } - // BaseModelData field setters - pub fn set_base_created_at(mut self, time: i64) -> Self { - self.base_data.created_at = time; - self - } - - pub fn set_base_modified_at(mut self, time: i64) -> Self { - self.base_data.modified_at = time; - self - } - - pub fn add_base_comment_id(mut self, comment_id: u32) -> Self { - self.base_data.comments.push(comment_id); - self - } - - pub fn set_base_comment_ids(mut self, comment_ids: Vec) -> Self { - self.base_data.comments = comment_ids; - self - } + // BaseModelData field operations are now handled by BaseModelDataOps trait } diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs index 7a8c2ec..6fe6081 100644 --- a/heromodels/src/models/biz/rhai.rs +++ b/heromodels/src/models/biz/rhai.rs @@ -1,332 +1,628 @@ -use rhai::{Engine, Module, Dynamic, EvalAltResult, Position}; +use rhai::plugin::*; +use rhai::{Engine, Module, Dynamic, EvalAltResult, Position, INT}; use std::sync::Arc; +use std::mem; use crate::db::Collection; // For db.set and db.get_by_id use crate::db::hero::OurDB; +use crate::db::Db; + use super::company::{Company, CompanyStatus, BusinessType}; use crate::models::biz::shareholder::{Shareholder, ShareholderType}; use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent}; use crate::models::biz::sale::{Sale, SaleItem, SaleStatus}; use heromodels_core::Model; -// Helper function to convert i64 to u32, returning a Rhai error if conversion fails -fn id_from_i64(id_val: i64) -> Result> { - u32::try_from(id_val).map_err(|_| { +type RhaiCompany = Company; +type RhaiShareholder = Shareholder; +type RhaiProduct = Product; +type RhaiProductComponent = ProductComponent; +type RhaiSale = Sale; +type RhaiSaleItem = SaleItem; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert i64 '{}' to u32 for ID", id_val), - Position::NONE, + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE )) - }) + ) +} + +#[export_module] +mod rhai_biz_module { + // --- Company Functions --- + #[rhai_fn(name = "new_company")] + pub fn new_company() -> RhaiCompany { + Company::new() + } + + // Company builder methods + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn company_name(company: &mut RhaiCompany, name: String) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.name(name); + Ok(company.clone()) + } + + #[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)] + pub fn company_fiscal_year_end(company: &mut RhaiCompany, fiscal_year_end: String) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.fiscal_year_end(fiscal_year_end); + Ok(company.clone()) + } + + #[rhai_fn(name = "registration_number", return_raw, global, pure)] + pub fn company_registration_number(company: &mut RhaiCompany, reg_num: String) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.registration_number(reg_num); + Ok(company.clone()) + } + + #[rhai_fn(name = "incorporation_date", return_raw, global, pure)] + pub fn company_incorporation_date(company: &mut RhaiCompany, date: i64) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.incorporation_date(date); + Ok(company.clone()) + } + + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn company_status(company: &mut RhaiCompany, status: CompanyStatus) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.status(status); + Ok(company.clone()) + } + + #[rhai_fn(name = "business_type", return_raw, global, pure)] + pub fn company_business_type(company: &mut RhaiCompany, business_type: BusinessType) -> Result> { + let owned_company = mem::take(company); + *company = owned_company.business_type(business_type); + Ok(company.clone()) + } + + // Company getters + #[rhai_fn(name = "get_company_id")] + pub fn get_company_id(company: &mut RhaiCompany) -> i64 { + company.get_id() as i64 + } + + #[rhai_fn(name = "get_company_name")] + pub fn get_company_name(company: &mut RhaiCompany) -> String { + company.name.clone() + } + + #[rhai_fn(name = "get_company_created_at")] + pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 { + company.base_data.created_at + } + + #[rhai_fn(name = "get_company_modified_at")] + pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 { + company.base_data.modified_at + } + + #[rhai_fn(name = "get_company_registration_number")] + pub fn get_company_registration_number(company: &mut RhaiCompany) -> String { + company.registration_number.clone() + } + + #[rhai_fn(name = "get_company_fiscal_year_end")] + pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String { + company.fiscal_year_end.clone() + } + + #[rhai_fn(name = "get_company_incorporation_date")] + pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 { + company.incorporation_date + } + + #[rhai_fn(name = "get_company_status")] + pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus { + company.status.clone() + } + + #[rhai_fn(name = "get_company_business_type")] + pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType { + company.business_type.clone() + } + + // --- Shareholder Functions --- + #[rhai_fn(name = "new_shareholder")] + pub fn new_shareholder() -> RhaiShareholder { + Shareholder::new() + } + + // Shareholder builder methods + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn shareholder_name(shareholder: &mut RhaiShareholder, name: String) -> Result> { + let owned_shareholder = mem::take(shareholder); + *shareholder = owned_shareholder.name(name); + Ok(shareholder.clone()) + } + + #[rhai_fn(name = "company_id", return_raw, global, pure)] + pub fn shareholder_company_id(shareholder: &mut RhaiShareholder, company_id: i64) -> Result> { + let company_id_u32 = id_from_i64_to_u32(company_id)?; + let owned_shareholder = mem::take(shareholder); + *shareholder = owned_shareholder.company_id(company_id_u32); + Ok(shareholder.clone()) + } + + #[rhai_fn(name = "share_count", return_raw, global, pure)] + pub fn shareholder_share_count(shareholder: &mut RhaiShareholder, share_count: f64) -> Result> { + let owned_shareholder = mem::take(shareholder); + shareholder.shares = share_count; + Ok(shareholder.clone()) + } + + #[rhai_fn(name = "type_", return_raw, global, pure)] + pub fn shareholder_type(shareholder: &mut RhaiShareholder, type_: ShareholderType) -> Result> { + let owned_shareholder = mem::take(shareholder); + *shareholder = owned_shareholder.type_(type_); + Ok(shareholder.clone()) + } + + // Shareholder getters + #[rhai_fn(name = "get_shareholder_id")] + pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 { + shareholder.get_id() as i64 + } + + #[rhai_fn(name = "get_shareholder_name")] + pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String { + shareholder.name.clone() + } + + #[rhai_fn(name = "get_shareholder_company_id")] + pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 { + shareholder.company_id as i64 + } + + #[rhai_fn(name = "get_shareholder_share_count")] + pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 { + shareholder.shares as i64 + } + + #[rhai_fn(name = "get_shareholder_type")] + pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType { + shareholder.type_.clone() + } + + // --- ProductComponent Functions --- + #[rhai_fn(name = "new_product_component")] + pub fn new_product_component() -> RhaiProductComponent { + ProductComponent::new() + } + + // ProductComponent builder methods + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn product_component_name(component: &mut RhaiProductComponent, name: String) -> Result> { + let owned_component = mem::take(component); + *component = owned_component.name(name); + Ok(component.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn product_component_description(component: &mut RhaiProductComponent, description: String) -> Result> { + let owned_component = mem::take(component); + *component = owned_component.description(description); + Ok(component.clone()) + } + + #[rhai_fn(name = "quantity", return_raw, global, pure)] + pub fn product_component_quantity(component: &mut RhaiProductComponent, quantity: i64) -> Result> { + let owned_component = mem::take(component); + *component = owned_component.quantity(quantity as u32); + Ok(component.clone()) + } + + // ProductComponent getters + #[rhai_fn(name = "get_product_component_name")] + pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String { + component.name.clone() + } + + #[rhai_fn(name = "get_product_component_description")] + pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String { + component.description.clone() + } + + #[rhai_fn(name = "get_product_component_quantity")] + pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 { + component.quantity as i64 + } + + // --- Product Functions --- + #[rhai_fn(name = "new_product")] + pub fn new_product() -> RhaiProduct { + Product::new() + } + + // Product builder methods + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn product_name(product: &mut RhaiProduct, name: String) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.name(name); + Ok(product.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn product_description(product: &mut RhaiProduct, description: String) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.description(description); + Ok(product.clone()) + } + + #[rhai_fn(name = "price", return_raw, global, pure)] + pub fn product_price(product: &mut RhaiProduct, price: f64) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.price(price); + Ok(product.clone()) + } + + #[rhai_fn(name = "type_", return_raw, global, pure)] + pub fn product_type(product: &mut RhaiProduct, type_: ProductType) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.type_(type_); + Ok(product.clone()) + } + + #[rhai_fn(name = "category", return_raw, global, pure)] + pub fn product_category(product: &mut RhaiProduct, category: String) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.category(category); + Ok(product.clone()) + } + + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn product_status(product: &mut RhaiProduct, status: ProductStatus) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.status(status); + Ok(product.clone()) + } + + #[rhai_fn(name = "max_amount", return_raw, global, pure)] + pub fn product_max_amount(product: &mut RhaiProduct, max_amount: i64) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.max_amount(max_amount as u16); + Ok(product.clone()) + } + + #[rhai_fn(name = "purchase_till", return_raw, global, pure)] + pub fn product_purchase_till(product: &mut RhaiProduct, purchase_till: i64) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.purchase_till(purchase_till); + Ok(product.clone()) + } + + #[rhai_fn(name = "active_till", return_raw, global, pure)] + pub fn product_active_till(product: &mut RhaiProduct, active_till: i64) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.active_till(active_till); + Ok(product.clone()) + } + + #[rhai_fn(name = "add_component", return_raw, global, pure)] + pub fn product_add_component(product: &mut RhaiProduct, component: RhaiProductComponent) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.add_component(component); + Ok(product.clone()) + } + + #[rhai_fn(name = "components", return_raw, global, pure)] + pub fn product_components(product: &mut RhaiProduct, components: Vec) -> Result> { + let owned_product = mem::take(product); + *product = owned_product.components(components); + Ok(product.clone()) + } + + // Product getters + #[rhai_fn(name = "get_product_id")] + pub fn get_product_id(product: &mut RhaiProduct) -> i64 { + product.get_id() as i64 + } + + #[rhai_fn(name = "get_product_name")] + pub fn get_product_name(product: &mut RhaiProduct) -> String { + product.name.clone() + } + + #[rhai_fn(name = "get_product_description")] + pub fn get_product_description(product: &mut RhaiProduct) -> String { + product.description.clone() + } + + #[rhai_fn(name = "get_product_price")] + pub fn get_product_price(product: &mut RhaiProduct) -> f64 { + product.price + } + + #[rhai_fn(name = "get_product_type")] + pub fn get_product_type(product: &mut RhaiProduct) -> ProductType { + product.type_.clone() + } + + #[rhai_fn(name = "get_product_category")] + pub fn get_product_category(product: &mut RhaiProduct) -> String { + product.category.clone() + } + + #[rhai_fn(name = "get_product_status")] + pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus { + product.status.clone() + } + + #[rhai_fn(name = "get_product_max_amount")] + pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 { + product.max_amount as i64 + } + + #[rhai_fn(name = "get_product_purchase_till")] + pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 { + product.purchase_till + } + + #[rhai_fn(name = "get_product_active_till")] + pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 { + product.active_till + } + + #[rhai_fn(name = "get_product_components")] + pub fn get_product_components(product: &mut RhaiProduct) -> Vec { + product.components.clone() + } + + #[rhai_fn(name = "get_product_created_at")] + pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 { + product.base_data.created_at + } + + #[rhai_fn(name = "get_product_modified_at")] + pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 { + product.base_data.modified_at + } + + #[rhai_fn(name = "get_product_comments")] + pub fn get_product_comments(product: &mut RhaiProduct) -> Vec { + product.base_data.comments.iter().map(|&id| id as i64).collect() + } + + // --- SaleItem Functions --- + #[rhai_fn(name = "new_sale_item")] + pub fn new_sale_item() -> RhaiSaleItem { + SaleItem::new() + } + + // SaleItem builder methods + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn sale_item_name(item: &mut RhaiSaleItem, name: String) -> Result> { + let owned_item = mem::take(item); + *item = owned_item.name(name); + Ok(item.clone()) + } + + #[rhai_fn(name = "price", return_raw, global, pure)] + pub fn sale_item_price(item: &mut RhaiSaleItem, price: f64) -> Result> { + let owned_item = mem::take(item); + item.unit_price = price; + Ok(item.clone()) + } + + #[rhai_fn(name = "quantity", return_raw, global, pure)] + pub fn sale_item_quantity(item: &mut RhaiSaleItem, quantity: i64) -> Result> { + let owned_item = mem::take(item); + *item = owned_item.quantity(quantity.try_into().unwrap()); + Ok(item.clone()) + } + + #[rhai_fn(name = "product_id", return_raw, global, pure)] + pub fn sale_item_product_id(item: &mut RhaiSaleItem, product_id: i64) -> Result> { + let product_id_u32 = id_from_i64_to_u32(product_id)?; + let owned_item = mem::take(item); + *item = owned_item.product_id(product_id_u32); + Ok(item.clone()) + } + + // SaleItem getters + #[rhai_fn(name = "get_sale_item_name")] + pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String { + item.name.clone() + } + + #[rhai_fn(name = "get_sale_item_price")] + pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 { + item.unit_price + } + + #[rhai_fn(name = "get_sale_item_quantity")] + pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 { + item.quantity as i64 + } + + #[rhai_fn(name = "get_sale_item_product_id")] + pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 { + item.product_id as i64 + } + + // --- Sale Functions --- + #[rhai_fn(name = "new_sale")] + pub fn new_sale() -> RhaiSale { + Sale::new() + } + + #[rhai_fn(name = "transaction_id", return_raw, global, pure)] + pub fn sale_transaction_id(sale: &mut RhaiSale, transaction_id: u32) -> Result> { + let owned_sale = mem::take(sale); + sale.transaction_id = transaction_id; + Ok(sale.clone()) + } + + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn sale_status(sale: &mut RhaiSale, status: SaleStatus) -> Result> { + let owned_sale = mem::take(sale); + *sale = owned_sale.status(status); + Ok(sale.clone()) + } + + #[rhai_fn(name = "add_item", return_raw, global, pure)] + pub fn sale_add_item(sale: &mut RhaiSale, item: RhaiSaleItem) -> Result> { + let owned_sale = mem::take(sale); + *sale = owned_sale.add_item(item); + Ok(sale.clone()) + } + + #[rhai_fn(name = "items", return_raw, global, pure)] + pub fn sale_items(sale: &mut RhaiSale, items: Vec) -> Result> { + let owned_sale = mem::take(sale); + *sale = owned_sale.items(items); + Ok(sale.clone()) + } + + // Sale getters + #[rhai_fn(name = "get_sale_id")] + pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { + sale.get_id() as i64 + } + + #[rhai_fn(name = "get_sale_transaction_id")] + pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 { + sale.transaction_id + } + + #[rhai_fn(name = "get_sale_status")] + pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus { + sale.status.clone() + } + + #[rhai_fn(name = "get_sale_items")] + pub fn get_sale_items(sale: &mut RhaiSale) -> Vec { + sale.items.clone() + } + + #[rhai_fn(name = "get_sale_created_at")] + pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 { + sale.base_data.created_at + } + + #[rhai_fn(name = "get_sale_modified_at")] + pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 { + sale.base_data.modified_at + } + + #[rhai_fn(name = "get_sale_comments")] + pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec { + sale.base_data.comments.iter().map(|&id| id as i64).collect() + } } pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { - let module = Module::new(); - - // --- Enum Constants: CompanyStatus --- - let mut status_constants_module = Module::new(); - status_constants_module.set_var("Active", Dynamic::from(CompanyStatus::Active.clone())); - status_constants_module.set_var("Inactive", Dynamic::from(CompanyStatus::Inactive.clone())); - status_constants_module.set_var("Suspended", Dynamic::from(CompanyStatus::Suspended.clone())); - engine.register_static_module("CompanyStatusConstants", status_constants_module.into()); - engine.register_type_with_name::("CompanyStatus"); - - // --- Enum Constants: BusinessType --- - let mut business_type_constants_module = Module::new(); - business_type_constants_module.set_var("Coop", Dynamic::from(BusinessType::Coop.clone())); - business_type_constants_module.set_var("Single", Dynamic::from(BusinessType::Single.clone())); - business_type_constants_module.set_var("Twin", Dynamic::from(BusinessType::Twin.clone())); - business_type_constants_module.set_var("Starter", Dynamic::from(BusinessType::Starter.clone())); - business_type_constants_module.set_var("Global", Dynamic::from(BusinessType::Global.clone())); - engine.register_static_module("BusinessTypeConstants", business_type_constants_module.into()); - engine.register_type_with_name::("BusinessType"); - - // --- Company --- - engine.register_type_with_name::("Company"); - - // Constructor - engine.register_fn("new_company", |name: String, registration_number: String, incorporation_date: i64| -> Result> { Ok(Company::new(name, registration_number, incorporation_date)) }); - - // Getters for Company - engine.register_get("id", |company: &mut Company| -> Result> { Ok(company.get_id() as i64) }); - engine.register_get("created_at", |company: &mut Company| -> Result> { Ok(company.base_data.created_at) }); - engine.register_get("modified_at", |company: &mut Company| -> Result> { Ok(company.base_data.modified_at) }); - engine.register_get("name", |company: &mut Company| -> Result> { Ok(company.name.clone()) }); - engine.register_get("registration_number", |company: &mut Company| -> Result> { Ok(company.registration_number.clone()) }); - engine.register_get("incorporation_date", |company: &mut Company| -> Result> { Ok(company.incorporation_date as i64) }); - engine.register_get("fiscal_year_end", |company: &mut Company| -> Result> { Ok(company.fiscal_year_end.clone()) }); - engine.register_get("email", |company: &mut Company| -> Result> { Ok(company.email.clone()) }); - engine.register_get("phone", |company: &mut Company| -> Result> { Ok(company.phone.clone()) }); - engine.register_get("website", |company: &mut Company| -> Result> { Ok(company.website.clone()) }); - engine.register_get("address", |company: &mut Company| -> Result> { Ok(company.address.clone()) }); - engine.register_get("business_type", |company: &mut Company| -> Result> { Ok(company.business_type.clone()) }); - engine.register_get("industry", |company: &mut Company| -> Result> { Ok(company.industry.clone()) }); - engine.register_get("description", |company: &mut Company| -> Result> { Ok(company.description.clone()) }); - engine.register_get("status", |company: &mut Company| -> Result> { Ok(company.status.clone()) }); - // Builder methods for Company - engine.register_fn("fiscal_year_end", |company: Company, fiscal_year_end: String| -> Result> { Ok(company.fiscal_year_end(fiscal_year_end)) }); - engine.register_fn("email", |company: Company, email: String| -> Result> { Ok(company.email(email)) }); - engine.register_fn("phone", |company: Company, phone: String| -> Result> { Ok(company.phone(phone)) }); - engine.register_fn("website", |company: Company, website: String| -> Result> { Ok(company.website(website)) }); - engine.register_fn("address", |company: Company, address: String| -> Result> { Ok(company.address(address)) }); - engine.register_fn("business_type", |company: Company, business_type: BusinessType| -> Result> { Ok(company.business_type(business_type)) }); - engine.register_fn("industry", |company: Company, industry: String| -> Result> { Ok(company.industry(industry)) }); - engine.register_fn("description", |company: Company, description: String| -> Result> { Ok(company.description(description)) }); - engine.register_fn("status", |company: Company, status: CompanyStatus| -> Result> { Ok(company.status(status)) }); - engine.register_fn("set_base_created_at", |company: Company, created_at: i64| -> Result> { Ok(company.set_base_created_at(created_at)) }); - engine.register_fn("set_base_modified_at", |company: Company, modified_at: i64| -> Result> { Ok(company.set_base_modified_at(modified_at)) }); - - // --- Enum Constants: ShareholderType --- - let mut shareholder_type_constants_module = Module::new(); - shareholder_type_constants_module.set_var("Individual", Dynamic::from(ShareholderType::Individual.clone())); - shareholder_type_constants_module.set_var("Corporate", Dynamic::from(ShareholderType::Corporate.clone())); - engine.register_static_module("ShareholderTypeConstants", shareholder_type_constants_module.into()); - engine.register_type_with_name::("ShareholderType"); - - // --- Shareholder --- - engine.register_type_with_name::("Shareholder"); - - // Constructor for Shareholder (minimal, takes only ID) - engine.register_fn("new_shareholder", || -> Result> { Ok(Shareholder::new()) }); - - // Getters for Shareholder - engine.register_get("id", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.get_id() as i64) }); - engine.register_get("created_at", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.base_data.created_at) }); - engine.register_get("modified_at", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.base_data.modified_at) }); - engine.register_get("company_id", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.company_id as i64) }); - engine.register_get("user_id", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.user_id as i64) }); - engine.register_get("name", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.name.clone()) }); - engine.register_get("shares", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.shares) }); - engine.register_get("percentage", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.percentage) }); - engine.register_get("type_", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.type_.clone()) }); - engine.register_get("since", |shareholder: &mut Shareholder| -> Result> { Ok(shareholder.since) }); - - // Builder methods for Shareholder - engine.register_fn("company_id", |shareholder: Shareholder, company_id: i64| -> Result> { Ok(shareholder.company_id(company_id as u32)) }); - engine.register_fn("user_id", |shareholder: Shareholder, user_id: i64| -> Result> { Ok(shareholder.user_id(user_id as u32)) }); - engine.register_fn("name", |shareholder: Shareholder, name: String| -> Result> { Ok(shareholder.name(name)) }); - engine.register_fn("shares", |shareholder: Shareholder, shares: f64| -> Result> { Ok(shareholder.shares(shares)) }); - engine.register_fn("percentage", |shareholder: Shareholder, percentage: f64| -> Result> { Ok(shareholder.percentage(percentage)) }); - engine.register_fn("type_", |shareholder: Shareholder, type_: ShareholderType| -> Result> { Ok(shareholder.type_(type_)) }); - engine.register_fn("since", |shareholder: Shareholder, since: i64| -> Result> { Ok(shareholder.since(since)) }); - engine.register_fn("set_base_created_at", |shareholder: Shareholder, created_at: i64| -> Result> { Ok(shareholder.set_base_created_at(created_at)) }); - engine.register_fn("set_base_modified_at", |shareholder: Shareholder, modified_at: i64| -> Result> { Ok(shareholder.set_base_modified_at(modified_at)) }); - - - // --- Enum Constants: ProductType --- - let mut product_type_constants_module = Module::new(); - product_type_constants_module.set_var("Product", Dynamic::from(ProductType::Product.clone())); - product_type_constants_module.set_var("Service", Dynamic::from(ProductType::Service.clone())); - engine.register_static_module("ProductTypeConstants", product_type_constants_module.into()); - engine.register_type_with_name::("ProductType"); - - // --- Enum Constants: ProductStatus --- - let mut product_status_constants_module = Module::new(); - product_status_constants_module.set_var("Available", Dynamic::from(ProductStatus::Available.clone())); - product_status_constants_module.set_var("Unavailable", Dynamic::from(ProductStatus::Unavailable.clone())); - engine.register_static_module("ProductStatusConstants", product_status_constants_module.into()); - engine.register_type_with_name::("ProductStatus"); - - // --- Enum Constants: SaleStatus --- - let mut sale_status_module = Module::new(); - sale_status_module.set_var("Pending", Dynamic::from(SaleStatus::Pending.clone())); - sale_status_module.set_var("Completed", Dynamic::from(SaleStatus::Completed.clone())); - sale_status_module.set_var("Cancelled", Dynamic::from(SaleStatus::Cancelled.clone())); - engine.register_static_module("SaleStatusConstants", sale_status_module.into()); - engine.register_type_with_name::("SaleStatus"); - - // --- ProductComponent --- - engine.register_type_with_name::("ProductComponent") - .register_fn("new_product_component", |name: String| -> Result> { Ok(ProductComponent::new(name)) }) - .register_get("name", |pc: &mut ProductComponent| -> Result> { Ok(pc.name.clone()) }) - .register_fn("name", |pc: ProductComponent, name: String| -> Result> { Ok(pc.name(name)) }) - .register_get("description", |pc: &mut ProductComponent| -> Result> { Ok(pc.description.clone()) }) - .register_fn("description", |pc: ProductComponent, description: String| -> Result> { Ok(pc.description(description)) }) - .register_get("quantity", |pc: &mut ProductComponent| -> Result> { Ok(pc.quantity as i64) }) - .register_fn("quantity", |pc: ProductComponent, quantity: i64| -> Result> { Ok(pc.quantity(quantity as u32)) }); - - // --- Product --- - engine.register_type_with_name::("Product") - .register_fn("new_product", || -> Result> { Ok(Product::new()) }) - // Getters for Product - .register_get("id", |p: &mut Product| -> Result> { Ok(p.base_data.id as i64) }) - .register_get("name", |p: &mut Product| -> Result> { Ok(p.name.clone()) }) - .register_get("description", |p: &mut Product| -> Result> { Ok(p.description.clone()) }) - .register_get("price", |p: &mut Product| -> Result> { Ok(p.price) }) - .register_get("type_", |p: &mut Product| -> Result> { Ok(p.type_.clone()) }) - .register_get("category", |p: &mut Product| -> Result> { Ok(p.category.clone()) }) - .register_get("status", |p: &mut Product| -> Result> { Ok(p.status.clone()) }) - .register_get("max_amount", |p: &mut Product| -> Result> { Ok(p.max_amount as i64) }) - .register_get("purchase_till", |p: &mut Product| -> Result> { Ok(p.purchase_till) }) - .register_get("active_till", |p: &mut Product| -> Result> { Ok(p.active_till) }) - .register_get("components", |p: &mut Product| -> Result> { - let rhai_array = p.components.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }) - // Getters for BaseModelData fields - .register_get("created_at", |p: &mut Product| -> Result> { Ok(p.base_data.created_at) }) - .register_get("modified_at", |p: &mut Product| -> Result> { Ok(p.base_data.modified_at) }) - .register_get("comments", |p: &mut Product| -> Result, Box> { Ok(p.base_data.comments.iter().map(|&id| id as i64).collect()) }) - // Builder methods for Product - .register_fn("name", |p: Product, name: String| -> Result> { Ok(p.name(name)) }) - .register_fn("description", |p: Product, description: String| -> Result> { Ok(p.description(description)) }) - .register_fn("price", |p: Product, price: f64| -> Result> { Ok(p.price(price)) }) - .register_fn("type_", |p: Product, type_: ProductType| -> Result> { Ok(p.type_(type_)) }) - .register_fn("category", |p: Product, category: String| -> Result> { Ok(p.category(category)) }) - .register_fn("status", |p: Product, status: ProductStatus| -> Result> { Ok(p.status(status)) }) - .register_fn("max_amount", |p: Product, max_amount: i64| -> Result> { Ok(p.max_amount(max_amount as u16)) }) - .register_fn("purchase_till", |p: Product, purchase_till: i64| -> Result> { Ok(p.purchase_till(purchase_till)) }) - .register_fn("active_till", |p: Product, active_till: i64| -> Result> { Ok(p.active_till(active_till)) }) - .register_fn("add_component", |p: Product, component: ProductComponent| -> Result> { Ok(p.add_component(component)) }) - .register_fn("components", |p: Product, components: Vec| -> Result> { Ok(p.components(components)) }) - .register_fn("set_base_created_at", |p: Product, time: i64| -> Result> { Ok(p.set_base_created_at(time)) }) - .register_fn("set_base_modified_at", |p: Product, time: i64| -> Result> { Ok(p.set_base_modified_at(time)) }) - .register_fn("add_base_comment_id", |p: Product, comment_id: i64| -> Result> { Ok(p.add_base_comment_id(id_from_i64(comment_id)?)) }) - .register_fn("set_base_comment_ids", |p: Product, comment_ids: Vec| -> Result> { - let u32_ids = comment_ids.into_iter().map(id_from_i64).collect::, _>>()?; - Ok(p.set_base_comment_ids(u32_ids)) - }); - - // --- SaleItem --- - engine.register_type_with_name::("SaleItem"); - engine.register_fn("new_sale_item", |product_id_i64: i64, name: String, quantity_i64: i64, unit_price: f64, subtotal: f64| -> Result> { - Ok(SaleItem::new(id_from_i64(product_id_i64)?, name, quantity_i64 as i32, unit_price, subtotal)) - }); - - // Getters for SaleItem - engine.register_get("product_id", |si: &mut SaleItem| -> Result> { Ok(si.product_id as i64) }); - engine.register_get("name", |si: &mut SaleItem| -> Result> { Ok(si.name.clone()) }); - engine.register_get("quantity", |si: &mut SaleItem| -> Result> { Ok(si.quantity as i64) }); - engine.register_get("unit_price", |si: &mut SaleItem| -> Result> { Ok(si.unit_price) }); - engine.register_get("subtotal", |si: &mut SaleItem| -> Result> { Ok(si.subtotal) }); - engine.register_get("service_active_until", |si: &mut SaleItem| -> Result, Box> { Ok(si.service_active_until) }); - - // Builder-style methods for SaleItem - engine.register_type_with_name::("SaleItem") - .register_fn("product_id", |item: SaleItem, product_id_i64: i64| -> Result> { Ok(item.product_id(id_from_i64(product_id_i64)?)) }) - .register_fn("name", |item: SaleItem, name: String| -> Result> { Ok(item.name(name)) }) - .register_fn("quantity", |item: SaleItem, quantity_i64: i64| -> Result> { Ok(item.quantity(quantity_i64 as i32)) }) - .register_fn("unit_price", |item: SaleItem, unit_price: f64| -> Result> { Ok(item.unit_price(unit_price)) }) - .register_fn("subtotal", |item: SaleItem, subtotal: f64| -> Result> { Ok(item.subtotal(subtotal)) }) - .register_fn("service_active_until", |item: SaleItem, until: Option| -> Result> { Ok(item.service_active_until(until)) }); - - // --- Sale --- - engine.register_type_with_name::("Sale"); - engine.register_fn("new_sale", |company_id_i64: i64, buyer_name: String, buyer_email: String, total_amount: f64, status: SaleStatus, sale_date: i64| -> Result> { Ok(Sale::new(id_from_i64(company_id_i64)?, buyer_name, buyer_email, total_amount, status, sale_date)) }); - - // Getters for Sale - engine.register_get("id", |s: &mut Sale| -> Result> { Ok(s.get_id() as i64) }); - engine.register_get("customer_id", |s: &mut Sale| -> Result> { Ok(s.company_id as i64) }); - engine.register_get("buyer_name", |s: &mut Sale| -> Result> { Ok(s.buyer_name.clone()) }); - engine.register_get("buyer_email", |s: &mut Sale| -> Result> { Ok(s.buyer_email.clone()) }); - engine.register_get("total_amount", |s: &mut Sale| -> Result> { Ok(s.total_amount) }); - engine.register_get("status", |s: &mut Sale| -> Result> { Ok(s.status.clone()) }); - engine.register_get("sale_date", |s: &mut Sale| -> Result> { Ok(s.sale_date) }); - engine.register_get("items", |s: &mut Sale| -> Result> { - Ok(s.items.iter().cloned().map(Dynamic::from).collect::()) - }); - engine.register_get("notes", |s: &mut Sale| -> Result> { Ok(s.notes.clone()) }); - engine.register_get("created_at", |s: &mut Sale| -> Result> { Ok(s.base_data.created_at) }); - engine.register_get("modified_at", |s: &mut Sale| -> Result> { Ok(s.base_data.modified_at) }); - // engine.register_get("uuid", |s: &mut Sale| -> Result, Box> { Ok(s.base_data().uuid.clone()) }); // UUID not in BaseModelData - engine.register_get("comments", |s: &mut Sale| -> Result> { - Ok(s.base_data.comments.iter().map(|&id| Dynamic::from(id as i64)).collect::()) - }); - - // Builder-style methods for Sale - engine.register_type_with_name::("Sale") - .register_fn("customer_id", |s: Sale, customer_id_i64: i64| -> Result> { Ok(s.company_id(id_from_i64(customer_id_i64)?)) }) - .register_fn("buyer_name", |s: Sale, buyer_name: String| -> Result> { Ok(s.buyer_name(buyer_name)) }) - .register_fn("buyer_email", |s: Sale, buyer_email: String| -> Result> { Ok(s.buyer_email(buyer_email)) }) - .register_fn("total_amount", |s: Sale, total_amount: f64| -> Result> { Ok(s.total_amount(total_amount)) }) - .register_fn("status", |s: Sale, status: SaleStatus| -> Result> { Ok(s.status(status)) }) - .register_fn("sale_date", |s: Sale, sale_date: i64| -> Result> { Ok(s.sale_date(sale_date)) }) - .register_fn("add_item", |s: Sale, item: SaleItem| -> Result> { Ok(s.add_item(item)) }) - .register_fn("items", |s: Sale, items: Vec| -> Result> { Ok(s.items(items)) }) - .register_fn("notes", |s: Sale, notes: String| -> Result> { Ok(s.notes(notes)) }) - .register_fn("set_base_id", |s: Sale, id_i64: i64| -> Result> { Ok(s.set_base_id(id_from_i64(id_i64)?)) }) - // .register_fn("set_base_uuid", |s: Sale, uuid: Option| -> Result> { Ok(s.set_base_uuid(uuid)) }) // UUID not in BaseModelData - .register_fn("set_base_created_at", |s: Sale, time: i64| -> Result> { Ok(s.set_base_created_at(time)) }) - .register_fn("set_base_modified_at", |s: Sale, time: i64| -> Result> { Ok(s.set_base_modified_at(time)) }) - .register_fn("add_base_comment", |s: Sale, comment_id_i64: i64| -> Result> { Ok(s.add_base_comment(id_from_i64(comment_id_i64)?)) }) - .register_fn("set_base_comments", |s: Sale, comment_ids: Vec| -> Result> { - let u32_ids = comment_ids.into_iter().map(id_from_i64).collect::, _>>()?; - Ok(s.set_base_comments(u32_ids)) - }); - - // DB functions for Product - let captured_db_for_set_product = Arc::clone(&db); - engine.register_fn("set_product", move |product: Product| -> Result> { - let original_id_for_error = product.get_id(); - captured_db_for_set_product.set(&product) - .map(|(_id, updated_product)| updated_product) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Product (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) - }) - }); - - let captured_db_for_get_prod = Arc::clone(&db); - engine.register_fn("get_product_by_id", move |id_i64: i64| -> Result> { - let id_u32 = id_i64 as u32; - captured_db_for_get_prod.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Product (ID: {}): {}", id_u32, e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Product with ID {} not found", id_u32).into(), Position::NONE))) - }); - - // DB functions for Sale - let captured_db_for_set_sale = Arc::clone(&db); - engine.register_fn("set_sale", move |sale: Sale| -> Result> { - let original_id_for_error = sale.get_id(); - captured_db_for_set_sale.set(&sale) - .map(|(_id, updated_sale)| updated_sale) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Sale (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) - }) - }); - - let captured_db_for_get_sale = Arc::clone(&db); - engine.register_fn("get_sale_by_id", move |id_i64: i64| -> Result> { - let id_u32 = id_from_i64(id_i64)?; - captured_db_for_get_sale.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Sale (ID: {}): {}", id_u32, e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Sale with ID {} not found", id_u32).into(), Position::NONE))) - }); - - // Mock DB functions for Shareholder - let captured_db_for_set_shareholder = Arc::clone(&db); - engine.register_fn("set_shareholder", move |shareholder: Shareholder| -> Result> { - let original_id_for_error = shareholder.get_id(); - captured_db_for_set_shareholder.set(&shareholder) - .map(|(_id, updated_shareholder)| updated_shareholder) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Shareholder (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) - }) - }); - - let captured_db_for_get_sh = Arc::clone(&db); - engine.register_fn("get_shareholder_by_id", move |id_i64: i64| -> Result> { - let id_u32 = id_i64 as u32; - captured_db_for_get_sh.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Shareholder (ID: {}): {}", id_u32, e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Shareholder with ID {} not found", id_u32).into(), Position::NONE))) - }); - - // Mock DB functions for Company - let captured_db_for_set_company = Arc::clone(&db); - engine.register_fn("set_company", move |company: Company| -> Result> { - let original_id_for_error = company.get_id(); // Capture ID before it's potentially changed by DB - captured_db_for_set_company.set(&company) - .map(|(_id, updated_company)| updated_company) // Use the model returned by db.set() - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Company (Original ID: {}): {}", original_id_for_error, e).into(), Position::NONE)) - }) - }); - - let captured_db_for_get = Arc::clone(&db); - engine.register_fn("get_company_by_id", move |id_i64: i64| -> Result> { - let id_u32 = id_i64 as u32; // Assuming direct conversion is fine, or use a helper like in flow - captured_db_for_get.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Company (ID: {}): {}", id_u32, e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Company with ID {} not found", id_u32).into(), Position::NONE))) - }); - + // Register the exported module globally + let module = exported_module!(rhai_biz_module); engine.register_global_module(module.into()); + + // Create a new module for database operations + let mut db_module = Module::new(); + + // Database operations will obtain fresh collection handles directly. + + // Add database functions for Company + let db_for_set_company = Arc::clone(&db); + db_module.set_native_fn("set_company", move |company: Company| -> Result> { + let company_collection_set = db_for_set_company.collection::().expect("Failed to get company collection for set in closure"); + company_collection_set.set(&company) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save company: {:?}", e).into(), + Position::NONE + ))) + }); + + let db_for_get_company = Arc::clone(&db); + db_module.set_native_fn("get_company_by_id", move |id: INT| -> Result> { + let company_collection_get = db_for_get_company.collection::().expect("Failed to get company collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + company_collection_get.get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get company with id {}: {:?}", id, e).into(), + Position::NONE + ))) + }); + + // Add database functions for Shareholder + let db_for_set_shareholder = Arc::clone(&db); + db_module.set_native_fn("set_shareholder", move |shareholder: Shareholder| -> Result> { + let shareholder_collection_set = db_for_set_shareholder.collection::().expect("Failed to get shareholder collection for set in closure"); + shareholder_collection_set.set(&shareholder) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save shareholder: {:?}", e).into(), + Position::NONE + ))) + }); + + let db_for_get_shareholder = Arc::clone(&db); + db_module.set_native_fn("get_shareholder_by_id", move |id: INT| -> Result> { + let shareholder_collection_get = db_for_get_shareholder.collection::().expect("Failed to get shareholder collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + shareholder_collection_get.get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get shareholder with id {}: {:?}", id, e).into(), + Position::NONE + ))) + }); + + // Add database functions for Product + let db_for_set_product = Arc::clone(&db); + db_module.set_native_fn("set_product", move |product: Product| -> Result> { + let product_collection_set = db_for_set_product.collection::().expect("Failed to get product collection for set in closure"); + product_collection_set.set(&product) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save product: {:?}", e).into(), + Position::NONE + ))) + }); + + let db_for_get_product = Arc::clone(&db); + db_module.set_native_fn("get_product_by_id", move |id: INT| -> Result> { + let product_collection_get = db_for_get_product.collection::().expect("Failed to get product collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + product_collection_get.get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get product with id {}: {:?}", id, e).into(), + Position::NONE + ))) + }); + + // Add database functions for Sale + let db_for_set_sale = Arc::clone(&db); + db_module.set_native_fn("set_sale", move |sale: Sale| -> Result> { + let sale_collection_set = db_for_set_sale.collection::().expect("Failed to get sale collection for set in closure"); + sale_collection_set.set(&sale) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save sale: {:?}", e).into(), + Position::NONE + ))) + }); + + let db_for_get_sale = Arc::clone(&db); + db_module.set_native_fn("get_sale_by_id", move |id: INT| -> Result> { + let sale_collection_get = db_for_get_sale.collection::().expect("Failed to get sale collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + sale_collection_get.get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get sale with id {}: {:?}", id, e).into(), + Position::NONE + ))) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered biz Rhai module using export_module approach."); } diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index 2d6d39f..a699329 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model}; +use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; /// Represents the status of a sale. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -16,7 +16,7 @@ impl Default for SaleStatus { } /// Represents an individual item within a Sale. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct SaleItem { pub product_id: u32, pub name: String, // Denormalized product name at time of sale @@ -27,14 +27,14 @@ pub struct SaleItem { } impl SaleItem { - /// Creates a new `SaleItem`. - pub fn new(product_id: u32, name: String, quantity: i32, unit_price: f64, subtotal: f64) -> Self { + /// Creates a new `SaleItem` with default values. + pub fn new() -> Self { SaleItem { - product_id, - name, - quantity, - unit_price, - subtotal, + product_id: 0, + name: String::new(), + quantity: 0, + unit_price: 0.0, + subtotal: 0.0, service_active_until: None, } } @@ -45,8 +45,8 @@ impl SaleItem { self } - pub fn name(mut self, name: String) -> Self { - self.name = name; + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); self } @@ -72,12 +72,12 @@ impl SaleItem { } /// Represents a sale of products or services. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] pub struct Sale { pub base_data: BaseModelData, pub company_id: u32, - pub buyer_name: String, - pub buyer_email: String, + pub buyer_id: u32, + pub transaction_id: u32, pub total_amount: f64, pub status: SaleStatus, pub sale_date: i64, @@ -99,24 +99,23 @@ impl Model for Sale { } } +impl BaseModelDataOps for Sale { + fn get_base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } +} + impl Sale { - /// Creates a new `Sale`. - pub fn new( - company_id: u32, - buyer_name: String, - buyer_email: String, - total_amount: f64, - status: SaleStatus, - sale_date: i64, - ) -> Self { + /// Creates a new `Sale` with default values. + pub fn new() -> Self { Sale { base_data: BaseModelData::new(), - company_id, - buyer_name, - buyer_email, - total_amount, - status, - sale_date, + company_id: 0, + buyer_id: 0, + transaction_id: 0, + total_amount: 0.0, + status: SaleStatus::default(), + sale_date: 0, items: Vec::new(), notes: String::new(), } @@ -128,13 +127,13 @@ impl Sale { self } - pub fn buyer_name(mut self, buyer_name: String) -> Self { - self.buyer_name = buyer_name; + pub fn buyer_id(mut self, buyer_id: u32) -> Self { + self.buyer_id = buyer_id; self } - pub fn buyer_email(mut self, buyer_email: String) -> Self { - self.buyer_email = buyer_email; + pub fn transaction_id(mut self, transaction_id: u32) -> Self { + self.transaction_id = transaction_id; self } @@ -163,40 +162,10 @@ impl Sale { self } - pub fn notes(mut self, notes: String) -> Self { - self.notes = notes; + pub fn notes(mut self, notes: impl ToString) -> Self { + self.notes = notes.to_string(); self } - // Builder methods for BaseModelData fields, prefixed with base_ - pub fn set_base_id(mut self, id: u32) -> Self { - self.base_data.id = id; - self - } - - // UUID is not part of BaseModelData directly in heromodels_core - // pub fn set_base_uuid(mut self, uuid: Option) -> Self { - // self.base_data.uuid = uuid; // Assuming uuid field exists if needed elsewhere - // self - // } - - pub fn set_base_created_at(mut self, created_at: i64) -> Self { - self.base_data.created_at = created_at; - self - } - - pub fn set_base_modified_at(mut self, modified_at: i64) -> Self { - self.base_data.modified_at = modified_at; - self - } - - pub fn add_base_comment(mut self, comment_id: u32) -> Self { - self.base_data.comments.push(comment_id); - self - } - - pub fn set_base_comments(mut self, comments: Vec) -> Self { - self.base_data.comments = comments; - self - } + // BaseModelData operations are now handled by BaseModelDataOps trait } diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index e449301..ffc8f53 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model}; +use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; +use heromodels_derive::model; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ShareholderType { @@ -13,7 +14,8 @@ impl Default for ShareholderType { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[model] pub struct Shareholder { pub base_data: BaseModelData, pub company_id: u32, @@ -50,8 +52,8 @@ impl Shareholder { self } - pub fn name(mut self, name: String) -> Self { - self.name = name; + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); self } @@ -75,28 +77,5 @@ impl Shareholder { self } - // Base data setters if needed for Rhai or specific scenarios - pub fn set_base_created_at(mut self, created_at: i64) -> Self { - self.base_data.created_at = created_at; - self - } - - pub fn set_base_modified_at(mut self, modified_at: i64) -> Self { - self.base_data.modified_at = modified_at; - self - } -} - -impl Model for Shareholder { - fn db_prefix() -> &'static str { - "shareholder" - } - - fn get_id(&self) -> u32 { - self.base_data.id - } - - fn base_data_mut(&mut self) -> &mut BaseModelData { - &mut self.base_data - } -} + // Base data operations are now handled by BaseModelDataOps trait +} \ No newline at end of file diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 2ca849c..2eca950 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -1,21 +1,46 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use rhai_autobind_macros::rhai_model_export; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; /// Represents the status of an attendee for an event -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub enum AttendanceStatus { Accepted, Declined, Tentative, + #[default] NoResponse, } +impl AttendanceStatus { + /// Convert a string to an AttendanceStatus + pub fn from_string(s: &str) -> Result { + match s { + "Accepted" => Ok(AttendanceStatus::Accepted), + "Declined" => Ok(AttendanceStatus::Declined), + "Tentative" => Ok(AttendanceStatus::Tentative), + "NoResponse" => Ok(AttendanceStatus::NoResponse), + _ => Err(format!("Invalid attendance status: '{}'", s)), + } + } + + /// Convert an AttendanceStatus to a string + pub fn to_string(&self) -> String { + match self { + AttendanceStatus::Accepted => "Accepted".to_string(), + AttendanceStatus::Declined => "Declined".to_string(), + AttendanceStatus::Tentative => "Tentative".to_string(), + AttendanceStatus::NoResponse => "NoResponse".to_string(), + } + } +} + /// Represents an attendee of an event -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Attendee { /// ID of the user attending // Assuming user_id might be queryable @@ -39,34 +64,51 @@ impl Attendee { } /// Represents an event in a calendar +#[model] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] pub struct Event { /// Base model data - #[serde(flatten)] pub base_data: BaseModelData, /// Title of the event + #[index] pub title: String, /// Optional description of the event pub description: Option, - /// Start time of the event - pub start_time: DateTime, - /// End time of the event - pub end_time: DateTime, + /// Start time of the event (Unix timestamp) + pub start_time: i64, + /// End time of the event (Unix timestamp) + pub end_time: i64, /// List of attendees for the event pub attendees: Vec, /// Optional location of the event pub location: Option, } -impl Event { - /// Creates a new event - pub fn new(title: impl ToString, start_time: DateTime, end_time: DateTime) -> Self { +impl Default for Event { + fn default() -> Self { + let now = chrono::Utc::now().timestamp(); Self { base_data: BaseModelData::new(), - title: title.to_string(), + title: String::new(), description: None, - start_time, - end_time, + start_time: now, + end_time: now + 3600, // Add 1 hour in seconds + attendees: Vec::new(), + location: None, + } + } +} + +impl Event { + /// Creates a new event + pub fn new() -> Self { + let now = chrono::Utc::now().timestamp(); + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + start_time: now, + end_time: now + 3600, // Add 1 hour in seconds attendees: Vec::new(), location: None, } @@ -116,8 +158,8 @@ impl Event { /// Reschedules the event to new start and end times pub fn reschedule( mut self, - new_start_time: DateTime, - new_end_time: DateTime, + new_start_time: i64, + new_end_time: i64, ) -> Self { // Basic validation: end_time should be after start_time if new_end_time > new_start_time { @@ -130,17 +172,18 @@ impl Event { } /// Represents a calendar with events -#[rhai_model_export( - db_type = "std::sync::Arc", -)] +// Temporarily removed rhai_model_export macro to fix compilation issues +// #[rhai_model_export( +// db_type = "std::sync::Arc", +// )] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] pub struct Calendar { /// Base model data - #[serde(flatten)] pub base_data: BaseModelData, /// Name of the calendar + #[index] pub name: String, /// Optional description of the calendar diff --git a/heromodels/src/models/calendar/mod.rs b/heromodels/src/models/calendar/mod.rs index 35fae98..fb0216a 100644 --- a/heromodels/src/models/calendar/mod.rs +++ b/heromodels/src/models/calendar/mod.rs @@ -4,4 +4,4 @@ pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs pub use self::calendar::{Calendar, Event, Attendee, AttendanceStatus}; -pub use rhai::register_rhai_engine_functions as register_calendar_rhai_module; +pub use rhai::register_calendar_rhai_module; diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index d4e1f12..07de7c8 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -1,90 +1,362 @@ -use rhai::{Engine, EvalAltResult, NativeCallContext, ImmutableString}; +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; +use std::mem; +use chrono::{DateTime, Utc}; +use crate::db::Db; -use heromodels_core::BaseModelData; +use super::calendar::{Event, Attendee, Calendar, AttendanceStatus}; +type RhaiEvent = Event; +type RhaiAttendee = Attendee; +type RhaiCalendar = Calendar; use crate::db::hero::OurDB; -use super::calendar::{Calendar, Event, Attendee, AttendanceStatus}; -use adapter_macros::{adapt_rhai_i64_input_fn, adapt_rhai_i64_input_method}; -use adapter_macros::rhai_timestamp_helpers; +use crate::db::Collection; -// Helper function for get_all_calendars registration - - -fn new_calendar_rhai(name: String) -> Result> { - Ok(Calendar::new(None, name)) +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) } -fn new_event_rhai( - context: NativeCallContext, - title_rhai: ImmutableString, - start_time_ts: i64, - end_time_ts: i64, -) -> Result> { - let start_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(start_time_ts) - .map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to convert start_time for Event: {}", e_str).into(), - context.position(), - )))?; - - let end_time = rhai_timestamp_helpers::rhai_timestamp_to_datetime(end_time_ts) - .map_err(|e_str| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to convert end_time for Event: {}", e_str).into(), - context.position(), - )))?; - - Ok(Event::new(title_rhai.to_string(), start_time, end_time)) +// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers +fn val_from_i64_to_u64(val_i64: i64) -> Result> { + u64::try_from(val_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert value '{}' to u64", val_i64).into(), + Position::NONE + )) + ) } -pub fn register_rhai_engine_functions(engine: &mut Engine, db: Arc) { - engine.register_fn("name", move |calendar: Calendar, name: String| Calendar::name(calendar, name)); - engine.register_fn("description", move |calendar: Calendar, description: String| Calendar::description(calendar, description)); - engine.register_fn("add_event", Calendar::add_event); - // Note: Event IDs are i64 in Calendar.events, but Event model's base_data.id is u32. - // This might require adjustment if events are fetched by ID from the DB via Calendar.events. +#[export_module] +mod rhai_calendar_module { + // --- Event Functions --- + #[rhai_fn(name = "new_event")] + pub fn new_event() -> RhaiEvent { + Event::new() + } - engine.register_fn("new_event", |context: NativeCallContext, title_rhai: ImmutableString, start_time_ts: i64, end_time_ts: i64| -> Result> { - new_event_rhai(context, title_rhai, start_time_ts, end_time_ts) - }); - engine.register_fn("title", move |event: Event, title: String| Event::title(event, title)); - engine.register_fn("description", move |event: Event, description: String| Event::description(event, description)); - engine.register_fn("add_attendee", move |event: Event, attendee: Attendee| Event::add_attendee(event, attendee)); - engine.register_fn("remove_attendee", adapt_rhai_i64_input_method!(Event, remove_attendee, u32)); - engine.register_fn("update_attendee_status", move |context: NativeCallContext, event: Event, contact_id_i64: i64, status: AttendanceStatus| -> Result> { - let contact_id_u32: u32 = contact_id_i64.try_into().map_err(|_e| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Conversion error for contact_id in Event::update_attendee_status from i64 to u32"), - context.position(), - )) - })?; - Ok(event.update_attendee_status(contact_id_u32, status)) - }); - - engine.register_fn("new_attendee", adapt_rhai_i64_input_fn!(Attendee::new, u32)); - - engine.register_fn("new_calendar", |name: String| -> Result> { new_calendar_rhai(name) }); - - // Register a function to get the database instance - engine.register_fn("get_db", move || db.clone()); + /// Sets the event title + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn event_title(event: &mut RhaiEvent, title: String) -> Result> { + let owned_event = mem::take(event); + *event = owned_event.title(title); + Ok(event.clone()) + } - // Register getters for Calendar - engine.register_get("id", |c: &mut Calendar| -> Result> { Ok(c.base_data.id as i64) }); - engine.register_get("name", |c: &mut Calendar| -> Result> { - // println!("Rhai attempting to get Calendar.name: {}", c.name); // Debug print - Ok(c.name.clone()) - }); - engine.register_get("description", |c: &mut Calendar| -> Result, Box> { Ok(c.description.clone()) }); + /// Sets the event description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn event_description(event: &mut RhaiEvent, description: String) -> Result> { + let owned_event = mem::take(event); + *event = owned_event.description(description); + Ok(event.clone()) + } + + /// Sets the event location + #[rhai_fn(name = "location", return_raw, global, pure)] + pub fn event_location(event: &mut RhaiEvent, location: String) -> Result> { + let owned_event = mem::take(event); + *event = owned_event.location(location); + Ok(event.clone()) + } - // Register getter for Calendar.base_data - engine.register_get("base_data", |c: &mut Calendar| -> Result> { Ok(c.base_data.clone()) }); + /// Adds an attendee to the event + #[rhai_fn(name = "add_attendee", return_raw, global, pure)] + pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result> { + // Use take to get ownership of the event + let owned_event = mem::take(event); + *event = owned_event.add_attendee(attendee); + Ok(event.clone()) + } - // Register getters for BaseModelData - engine.register_get("id", |bmd: &mut BaseModelData| -> Result> { Ok(bmd.id.into()) }); + /// Reschedules the event with new start and end times + #[rhai_fn(name = "reschedule", return_raw, global, pure)] + pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result> { + // Validate timestamps + if new_end_time <= new_start_time { + return Err(Box::new(EvalAltResult::ErrorRuntime( + "End time must be after start time".into(), + Position::NONE + ))); + } + + // Use take to get ownership of the event + let owned_event = mem::take(event); + *event = owned_event.reschedule(new_start_time, new_end_time); + Ok(event.clone()) + } + + /// Updates an attendee's status in the event + #[rhai_fn(name = "update_attendee_status", return_raw, global, pure)] + pub fn event_update_attendee_status(event: &mut RhaiEvent, contact_id: i64, status_str: String) -> Result> { + let status_enum = AttendanceStatus::from_string(&status_str) + .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(), + Position::NONE + )))?; + + // Use take to get ownership of the event + let owned_event = mem::take(event); + *event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum); + Ok(event.clone()) + } - // Database interaction functions for Calendar are expected to be provided by #[rhai_model_export(..)] on the Calendar struct. - // Ensure that `db.rs` or similar correctly wires up the `OurDB` methods for these. + // Event Getters + #[rhai_fn(get = "id", pure)] + pub fn get_event_id(event: &mut RhaiEvent) -> i64 { event.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { event.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { event.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_event_title(event: &mut RhaiEvent) -> String { event.title.clone() } + #[rhai_fn(get = "description", pure)] + pub fn get_event_description(event: &mut RhaiEvent) -> Option { event.description.clone() } + #[rhai_fn(get = "start_time", pure)] + pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { event.start_time } + #[rhai_fn(get = "end_time", pure)] + pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { event.end_time } + #[rhai_fn(get = "location", pure)] + pub fn get_event_location(event: &mut RhaiEvent) -> Option { event.location.clone() } + #[rhai_fn(get = "attendees", pure)] + pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { event.attendees.clone() } + + // --- Attendee Functions --- + #[rhai_fn(name = "new_attendee")] + pub fn new_attendee() -> RhaiAttendee { + Attendee::new(0) // Default contact_id, will be set via builder + } + + /// Sets the contact ID for an attendee + #[rhai_fn(name = "with_contact_id", return_raw, global, pure)] + pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result> { + let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0); + let owned_attendee = mem::replace(attendee, Attendee::new(0)); + *attendee = Attendee::new(new_contact_id); + attendee.status = owned_attendee.status; + Ok(attendee.clone()) + } + + /// Sets the status for an attendee + #[rhai_fn(name = "with_status", return_raw, global, pure)] + pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result> { + let status_enum = AttendanceStatus::from_string(&status_str) + .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(), + Position::NONE + )))?; + + let owned_attendee = mem::replace(attendee, Attendee::new(0)); + *attendee = owned_attendee.status(status_enum); + Ok(attendee.clone()) + } + + // We now use with_status instead of update_attendee_status for consistency + + // Attendee Getters + #[rhai_fn(get = "contact_id", pure)] + pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { attendee.contact_id as i64 } + #[rhai_fn(get = "status", pure)] + pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { attendee.status.to_string() } + + // --- Calendar Functions --- + #[rhai_fn(name = "new_calendar", return_raw)] + pub fn new_calendar() -> Result> { + let calendar = Calendar::new(None, ""); + Ok(calendar) + } + + /// Sets the calendar name + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn calendar_name(calendar: &mut RhaiCalendar, name: String) -> Result> { + // Create a default Calendar to replace the taken one + let default_calendar = Calendar::new(None, ""); + + // Take ownership of the calendar, apply the builder method, then put it back + let owned_calendar = std::mem::replace(calendar, default_calendar); + *calendar = Calendar::new(Some(owned_calendar.base_data.id), name); + Ok(calendar.clone()) + } + + /// Sets the calendar description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn calendar_description(calendar: &mut RhaiCalendar, description: String) -> Result> { + // Create a default Calendar to replace the taken one + let default_calendar = Calendar::new(None, ""); + + // Take ownership of the calendar, apply the builder method, then put it back + let owned_calendar = std::mem::replace(calendar, default_calendar); + let updated_calendar = owned_calendar.description(description); + *calendar = updated_calendar.clone(); + Ok(updated_calendar) + } + + #[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)] + pub fn calendar_add_event(calendar: &mut RhaiCalendar, event: RhaiEvent) -> Result> { + // Create a default Calendar to replace the taken one + let default_calendar = Calendar::new(None, ""); + + // Take ownership of the calendar, apply the builder method, then put it back + let owned_calendar = std::mem::replace(calendar, default_calendar); + *calendar = owned_calendar.add_event(event.base_data.id as i64); + Ok(calendar.clone()) + } + + #[rhai_fn(name = "remove_event_from_calendar", return_raw)] + pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box> { + // Create a default Calendar to replace the taken one + let default_calendar = Calendar::new(None, ""); + + // Take ownership of the calendar, apply the builder method, then put it back + let owned_calendar = std::mem::replace(calendar, default_calendar); + *calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64); + Ok(()) + } + + // Calendar Getters + #[rhai_fn(get = "id", pure)] + pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.id as i64 } + + #[rhai_fn(get = "name", pure)] + pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { calendar.name.clone() } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.modified_at } + + #[rhai_fn(get = "events", pure)] + pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { calendar.events.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { calendar.description.clone() } + + // Calendar doesn't have an owner_id field in the current implementation + // pub fn get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 } - // Getters for Event - engine.register_get("id", |e: &mut Event| -> Result> { Ok(e.base_data.id as i64) }); - engine.register_get("title", |e: &mut Event| -> Result> { Ok(e.title.clone()) }); - // Add more getters for Event fields as needed +} + +pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc) { + // Register the exported module globally + let module = exported_module!(rhai_calendar_module); + engine.register_global_module(module.into()); + + // Create a module for database functions + let mut db_module = Module::new(); + + // Manually register database functions as they need to capture 'db' + let db_clone_set_event = db.clone(); + db_module.set_native_fn("save_event", move |event: Event| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_event.set(&event) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?; + + // Return the updated event with the correct ID + Ok(result.1) + }); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_event = db.clone(); + db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_event.collection::() + .expect("can open event collection") + .delete_by_id(event.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }); + + let db_clone_get_event = db.clone(); + db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_event.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) + }); + + let db_clone_set_calendar = db.clone(); + db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_calendar.set(&calendar) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?; + + // Return the updated calendar with the correct ID + Ok(result.1) + }); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_calendar = db.clone(); + db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_calendar.collection::() + .expect("can open calendar collection") + .delete_by_id(calendar.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }); + + let db_clone_get_calendar = db.clone(); + db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_calendar.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE))) + }); + + // Add list_calendars function to get all calendars + let db_clone_list_calendars = db.clone(); + db_module.set_native_fn("list_calendars", move || -> Result> { + let collection = db_clone_list_calendars.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get calendar collection: {:?}", e).into(), + Position::NONE + )))?; + let calendars = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all calendars: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for calendar in calendars { + array.push(Dynamic::from(calendar)); + } + Ok(Dynamic::from(array)) + }); + + // Add list_events function to get all events + let db_clone_list_events = db.clone(); + db_module.set_native_fn("list_events", move || -> Result> { + let collection = db_clone_list_events.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get event collection: {:?}", e).into(), + Position::NONE + )))?; + let events = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all events: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for event in events { + array.push(Dynamic::from(event)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered calendar Rhai module using export_module approach."); } diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index 28bcefd..0122b02 100644 --- a/heromodels/src/models/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -1,36 +1,43 @@ +// heromodels/src/models/core/comment.rs use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; -/// Represents a comment on a model -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] // Added PartialEq #[model] pub struct Comment { - pub base_data: BaseModelData, + pub base_data: BaseModelData, // Provides id, created_at, updated_at #[index] - pub user_id: u32, - pub content: String, + pub user_id: u32, // Maps to commenter_id + pub content: String, // Maps to text + pub parent_comment_id: Option, // For threaded comments } impl Comment { - /// Create a new comment with auto-generated ID pub fn new() -> Self { Self { base_data: BaseModelData::new(), - user_id: 0, + user_id: 0, // Default, should be set via builder or method content: String::new(), + parent_comment_id: None, } } - /// Set the user ID + // Builder method for user_id pub fn user_id(mut self, id: u32) -> Self { self.user_id = id; self } - /// Set the content + // Builder method for content pub fn content(mut self, content: impl ToString) -> Self { self.content = content.to_string(); self } + + // Builder method for parent_comment_id + pub fn parent_comment_id(mut self, parent_id: Option) -> Self { + self.parent_comment_id = parent_id; + self + } } diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index d6f9648..f225fc4 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -8,7 +8,7 @@ use heromodels_core::BaseModelData; use super::asset::Asset; /// Account represents a financial account owned by a user -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] #[model] // Has base.Base in V spec pub struct Account { pub base_data: BaseModelData, @@ -18,82 +18,75 @@ pub struct Account { pub ledger: String, // describes the ledger/blockchain where the account is located pub address: String, // address of the account on the blockchain pub pubkey: String, // public key - pub assets: Vec, // list of assets in this account + pub assets: Vec, // list of assets in this account } impl Account { - /// Create a new account with auto-generated ID - /// - /// # Arguments - /// * `id` - Optional ID for the account (use None for auto-generated ID) - /// * `name` - Name of the account - /// * `user_id` - ID of the user who owns the account - /// * `description` - Description of the account - /// * `ledger` - Ledger/blockchain where the account is located - /// * `address` - Address of the account on the blockchain - /// * `pubkey` - Public key - pub fn new( - id: Option, - name: impl ToString, - user_id: u32, - description: impl ToString, - ledger: impl ToString, - address: impl ToString, - pubkey: impl ToString, - ) -> Self { - let mut base_data = BaseModelData::new(); - if let Some(id) = id { - base_data.update_id(id); - } - + /// Create a new account with default values + pub fn new() -> Self { Self { - base_data, - name: name.to_string(), - user_id, - description: description.to_string(), - ledger: ledger.to_string(), - address: address.to_string(), - pubkey: pubkey.to_string(), + base_data: BaseModelData::new(), + name: String::new(), + user_id: 0, + description: String::new(), + ledger: String::new(), + address: String::new(), + pubkey: String::new(), assets: Vec::new(), } } + /// Set the name of the account + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the user ID of the account owner + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the description of the account + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the ledger/blockchain where the account is located + pub fn ledger(mut self, ledger: impl ToString) -> Self { + self.ledger = ledger.to_string(); + self + } + + /// Set the address of the account on the blockchain + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + /// Set the public key of the account + pub fn pubkey(mut self, pubkey: impl ToString) -> Self { + self.pubkey = pubkey.to_string(); + self + } + /// Add an asset to the account - pub fn add_asset(mut self, asset: Asset) -> Self { - self.assets.push(asset); + pub fn add_asset(mut self, asset_id: u32) -> Self { + self.assets.push(asset_id); self } /// Get the total value of all assets in the account pub fn total_value(&self) -> f64 { - self.assets.iter().map(|asset| asset.amount).sum() + /// TODO: implement + 0.0 } /// Find an asset by name pub fn find_asset_by_name(&self, name: &str) -> Option<&Asset> { - self.assets.iter().find(|asset| asset.name == name) - } - - /// Update the account details - pub fn update_details( - mut self, - name: Option, - description: Option, - address: Option, - pubkey: Option, - ) -> Self { - if let Some(name) = name { - self.name = name.to_string(); - } - if let Some(description) = description { - self.description = description.to_string(); - } - if let Some(address) = address { - self.address = address.to_string(); - } - if let Some(pubkey) = pubkey { - self.pubkey = pubkey.to_string(); - } - self + /// TODO: implement + return None } } diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 40a5acf..20dbff1 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -21,7 +21,7 @@ impl Default for AssetType { } /// Asset represents a digital asset or token -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] #[model] // Has base.Base in V spec pub struct Asset { pub base_data: BaseModelData, @@ -34,47 +34,55 @@ pub struct Asset { } impl Asset { - /// Create a new asset with auto-generated ID - /// - /// # Arguments - /// * `id` - Optional ID for the asset (use None for auto-generated ID) - /// * `name` - Name of the asset - /// * `description` - Description of the asset - /// * `amount` - Amount of the asset - /// * `address` - Address of the asset on the blockchain or bank - /// * `asset_type` - Type of the asset - /// * `decimals` - Number of decimals of the asset - pub fn new( - id: Option, - name: impl ToString, - description: impl ToString, - amount: f64, - address: impl ToString, - asset_type: AssetType, - decimals: u8, - ) -> Self { - let mut base_data = BaseModelData::new(); - if let Some(id) = id { - base_data.update_id(id); - } - + /// Create a new asset with default values + pub fn new() -> Self { Self { - base_data, - name: name.to_string(), - description: description.to_string(), - amount, - address: address.to_string(), - asset_type, - decimals, + base_data: BaseModelData::new(), + name: String::new(), + description: String::new(), + amount: 0.0, + address: String::new(), + asset_type: AssetType::default(), + decimals: 18, // Default for most tokens } } - /// Update the asset amount - pub fn update_amount(mut self, amount: f64) -> Self { + /// Set the name of the asset + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the description of the asset + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the amount of the asset + pub fn amount(mut self, amount: f64) -> Self { self.amount = amount; self } + /// Set the address of the asset on the blockchain + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + /// Set the type of the asset + pub fn asset_type(mut self, asset_type: AssetType) -> Self { + self.asset_type = asset_type; + self + } + + /// Set the number of decimals of the asset + pub fn decimals(mut self, decimals: u8) -> Self { + self.decimals = decimals; + self + } + /// Get the formatted amount with proper decimal places pub fn formatted_amount(&self) -> String { let factor = 10_f64.powi(self.decimals as i32); diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 701e1cd..f82bd90 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -54,6 +54,7 @@ impl Default for BidStatus { /// Bid represents a bid on an auction listing #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[derive(Default)] pub struct Bid { pub listing_id: String, // ID of the listing this bid belongs to pub bidder_id: u32, // ID of the user who placed the bid @@ -64,32 +65,44 @@ pub struct Bid { } impl Bid { - /// Create a new bid - pub fn new( - listing_id: impl ToString, - bidder_id: u32, - amount: f64, - currency: impl ToString, - ) -> Self { - Self { - listing_id: listing_id.to_string(), - bidder_id, - amount, - currency: currency.to_string(), - status: BidStatus::default(), - created_at: Utc::now(), - } + /// Create a new bid with default values + pub fn new() -> Self { + Self::default() } - /// Update the status of the bid - pub fn update_status(mut self, status: BidStatus) -> Self { + /// Set the listing ID for the bid + pub fn listing_id(mut self, listing_id: impl ToString) -> Self { + self.listing_id = listing_id.to_string(); + self + } + + /// Set the bidder ID for the bid + pub fn bidder_id(mut self, bidder_id: u32) -> Self { + self.bidder_id = bidder_id; + self + } + + /// Set the amount for the bid + pub fn amount(mut self, amount: f64) -> Self { + self.amount = amount; + self + } + + /// Set the currency for the bid + pub fn currency(mut self, currency: impl ToString) -> Self { + self.currency = currency.to_string(); + self + } + + /// Set the status of the bid + pub fn status(mut self, status: BidStatus) -> Self { self.status = status; self } } /// Listing represents a marketplace listing for an asset -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] #[model] // Has base.Base in V spec pub struct Listing { pub base_data: BaseModelData, @@ -112,66 +125,82 @@ pub struct Listing { } impl Listing { - /// Create a new listing with auto-generated ID - /// - /// # Arguments - /// * `id` - Optional ID for the listing (use None for auto-generated ID) - /// * `title` - Title of the listing - /// * `description` - Description of the listing - /// * `asset_id` - ID of the asset being listed - /// * `asset_type` - Type of the asset - /// * `seller_id` - ID of the seller - /// * `price` - Initial price for fixed price, or starting price for auction - /// * `currency` - Currency of the price - /// * `listing_type` - Type of the listing - /// * `expires_at` - Optional expiration date - /// * `tags` - Tags for the listing - /// * `image_url` - Optional image URL - pub fn new( - id: Option, - title: impl ToString, - description: impl ToString, - asset_id: impl ToString, - asset_type: AssetType, - seller_id: impl ToString, - price: f64, - currency: impl ToString, - listing_type: ListingType, - expires_at: Option>, - tags: Vec, - image_url: Option, - ) -> Self { - let mut base_data = BaseModelData::new(); - if let Some(id) = id { - base_data.update_id(id); - } + /// Create a new listing with default values + pub fn new() -> Self { + Self::default() + } - Self { - base_data, - title: title.to_string(), - description: description.to_string(), - asset_id: asset_id.to_string(), - asset_type, - seller_id: seller_id.to_string(), - price, - currency: currency.to_string(), - listing_type, - status: ListingStatus::default(), - expires_at, - sold_at: None, - buyer_id: None, - sale_price: None, - bids: Vec::new(), - tags, - image_url: image_url.map(|url| url.to_string()), - } + /// Set the title of the listing + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Set the description of the listing + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the asset ID of the listing + pub fn asset_id(mut self, asset_id: impl ToString) -> Self { + self.asset_id = asset_id.to_string(); + self + } + + /// Set the asset type of the listing + pub fn asset_type(mut self, asset_type: AssetType) -> Self { + self.asset_type = asset_type; + self + } + + /// Set the seller ID of the listing + pub fn seller_id(mut self, seller_id: impl ToString) -> Self { + self.seller_id = seller_id.to_string(); + self + } + + /// Set the price of the listing + pub fn price(mut self, price: f64) -> Self { + self.price = price; + self + } + + /// Set the currency of the listing + pub fn currency(mut self, currency: impl ToString) -> Self { + self.currency = currency.to_string(); + self + } + + /// Set the listing type + pub fn listing_type(mut self, listing_type: ListingType) -> Self { + self.listing_type = listing_type; + self + } + + /// Set the status of the listing + pub fn status(mut self, status: ListingStatus) -> Self { + self.status = status; + self + } + + /// Set the expiration date of the listing + pub fn expires_at(mut self, expires_at: Option>) -> Self { + self.expires_at = expires_at; + self + } + + /// Set the image URL of the listing + pub fn image_url(mut self, image_url: Option) -> Self { + self.image_url = image_url.map(|url| url.to_string()); + self } /// Add a bid to an auction listing pub fn add_bid(mut self, bid: Bid) -> Result { // Check if listing is an auction if self.listing_type != ListingType::Auction { - return Err("Bids can only be placed on auction listings"); + return Err("Cannot add bid to non-auction listing"); } // Check if listing is active @@ -210,27 +239,51 @@ impl Listing { .max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap()) } + /// Set the buyer ID for completing a sale + pub fn buyer_id(mut self, buyer_id: impl ToString) -> Self { + self.buyer_id = Some(buyer_id.to_string()); + self + } + + /// Set the sale price for completing a sale + pub fn sale_price(mut self, sale_price: f64) -> Self { + self.sale_price = Some(sale_price); + self + } + + /// Set the sold date for completing a sale + pub fn sold_at(mut self, sold_at: Option>) -> Self { + self.sold_at = sold_at; + self + } + /// Complete a sale (fixed price or auction) - pub fn complete_sale( - mut self, - buyer_id: impl ToString, - sale_price: f64, - ) -> Result { + pub fn complete_sale(mut self) -> Result { if self.status != ListingStatus::Active { return Err("Cannot complete sale for inactive listing"); } + if self.buyer_id.is_none() { + return Err("Buyer ID must be set before completing sale"); + } + + if self.sale_price.is_none() { + return Err("Sale price must be set before completing sale"); + } + self.status = ListingStatus::Sold; - self.buyer_id = Some(buyer_id.to_string()); - self.sale_price = Some(sale_price); - self.sold_at = Some(Utc::now()); + + if self.sold_at.is_none() { + self.sold_at = Some(Utc::now()); + } // If this was an auction, accept the winning bid and reject others if self.listing_type == ListingType::Auction { + let buyer_id_str = self.buyer_id.as_ref().unwrap().to_string(); + let sale_price_val = self.sale_price.unwrap(); + for bid in &mut self.bids { - if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string() - && bid.amount == sale_price - { + if bid.bidder_id.to_string() == buyer_id_str && bid.amount == sale_price_val { bid.status = BidStatus::Accepted; } else { bid.status = BidStatus::Rejected; @@ -279,34 +332,11 @@ impl Listing { self } - /// Add tags to the listing - pub fn add_tags(mut self, tags: Vec) -> Self { - for tag in tags { - self.tags.push(tag.to_string()); - } + /// Add a single tag to the listing + pub fn add_tag(mut self, tag: impl ToString) -> Self { + self.tags.push(tag.to_string()); self } - /// Update the listing details - pub fn update_details( - mut self, - title: Option, - description: Option, - price: Option, - image_url: Option, - ) -> Self { - if let Some(title) = title { - self.title = title.to_string(); - } - if let Some(description) = description { - self.description = description.to_string(); - } - if let Some(price) = price { - self.price = price; - } - if let Some(image_url) = image_url { - self.image_url = Some(image_url.to_string()); - } - self - } + // update_details method removed as we now have individual setter methods for each field } diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index 01cb4f1..d58db70 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -1,346 +1,722 @@ -use rhai::{Engine, Array, Dynamic, ImmutableString, INT, EvalAltResult, NativeCallContext}; -use std::sync::{Arc, Mutex}; +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; +use std::sync::Arc; +use std::mem; +use chrono::{DateTime, Utc}; + +use super::account::Account; +use super::asset::{Asset, AssetType}; +use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; + +use crate::db::hero::OurDB; +use crate::db::{Collection, Db}; +use heromodels_core::Model; + +type RhaiAccount = Account; +type RhaiAsset = Asset; +type RhaiListing = Listing; +type RhaiBid = Bid; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) +} + +// Helper functions for enum conversions + +// AssetType conversions +fn asset_type_to_string(asset_type: &AssetType) -> String { + match asset_type { + AssetType::Native => "Native".to_string(), + AssetType::Erc20 => "Erc20".to_string(), + AssetType::Erc721 => "Erc721".to_string(), + AssetType::Erc1155 => "Erc1155".to_string(), + } +} + +fn string_to_asset_type(s: &str) -> Result> { + match s { + "Native" => Ok(AssetType::Native), + "Erc20" => Ok(AssetType::Erc20), + "Erc721" => Ok(AssetType::Erc721), + "Erc1155" => Ok(AssetType::Erc1155), + _ => Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", s).into(), + Position::NONE + ))), + } +} + +// ListingStatus conversions +fn listing_status_to_string(status: &ListingStatus) -> String { + match status { + ListingStatus::Active => "Active".to_string(), + ListingStatus::Sold => "Sold".to_string(), + ListingStatus::Cancelled => "Cancelled".to_string(), + ListingStatus::Expired => "Expired".to_string(), + } +} + +fn string_to_listing_status(s: &str) -> Result> { + match s { + "Active" => Ok(ListingStatus::Active), + "Sold" => Ok(ListingStatus::Sold), + "Cancelled" => Ok(ListingStatus::Cancelled), + "Expired" => Ok(ListingStatus::Expired), + _ => Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", s).into(), + Position::NONE + ))), + } +} + +// ListingType conversions +fn listing_type_to_string(lt: &ListingType) -> String { + match lt { + ListingType::FixedPrice => "FixedPrice".to_string(), + ListingType::Auction => "Auction".to_string(), + ListingType::Exchange => "Exchange".to_string(), + } +} + +fn string_to_listing_type(s: &str) -> Result> { + match s { + "FixedPrice" => Ok(ListingType::FixedPrice), + "Auction" => Ok(ListingType::Auction), + "Exchange" => Ok(ListingType::Exchange), + _ => Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", s).into(), + Position::NONE + ))), + } +} + +// BidStatus conversions +fn bid_status_to_string(status: &BidStatus) -> String { + match status { + BidStatus::Active => "Active".to_string(), + BidStatus::Accepted => "Accepted".to_string(), + BidStatus::Rejected => "Rejected".to_string(), + BidStatus::Cancelled => "Cancelled".to_string(), + } +} + +fn string_to_bid_status(s: &str) -> Result> { + match s { + "Active" => Ok(BidStatus::Active), + "Accepted" => Ok(BidStatus::Accepted), + "Rejected" => Ok(BidStatus::Rejected), + "Cancelled" => Ok(BidStatus::Cancelled), + _ => Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", s).into(), + Position::NONE + ))), + } +} + +// Account module +#[export_module] +mod account_module { + use super::*; + + // Constructor + #[rhai_fn(return_raw, global)] + pub fn new_account() -> Result> { + Ok(Account::new()) + } + + // Getters + #[rhai_fn(global, pure)] + pub fn get_id(account: &mut RhaiAccount) -> INT { + account.base_data.id as INT + } + + #[rhai_fn(global, pure)] + pub fn get_created_at(account: &mut RhaiAccount) -> INT { + account.base_data.created_at + } + + #[rhai_fn(global, pure)] + pub fn get_name(account: &mut RhaiAccount) -> String { + account.name.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_user_id(account: &mut RhaiAccount) -> INT { + account.user_id as INT + } + + #[rhai_fn(global, pure)] + pub fn get_description(account: &mut RhaiAccount) -> String { + account.description.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_ledger(account: &mut RhaiAccount) -> String { + account.ledger.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_address(account: &mut RhaiAccount) -> String { + account.address.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_pubkey(account: &mut RhaiAccount) -> String { + account.pubkey.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_assets(account: &mut RhaiAccount) -> Array { + let mut assets_array = Array::new(); + for asset_id in &account.assets { + assets_array.push(Dynamic::from(*asset_id as INT)); + } + assets_array + } + + // Setters using builder pattern with proper mutability handling + #[rhai_fn(return_raw, global)] + pub fn name(account: &mut RhaiAccount, name: String) -> Result> { + let mut acc = mem::take(account); + acc = acc.name(name); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn user_id(account: &mut RhaiAccount, user_id: INT) -> Result> { + let user_id = id_from_i64_to_u32(user_id)?; + let mut acc = mem::take(account); + acc = acc.user_id(user_id); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn description(account: &mut RhaiAccount, description: String) -> Result> { + let mut acc = mem::take(account); + acc = acc.description(description); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn ledger(account: &mut RhaiAccount, ledger: String) -> Result> { + let mut acc = mem::take(account); + acc = acc.ledger(ledger); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn address(account: &mut RhaiAccount, address: String) -> Result> { + let mut acc = mem::take(account); + acc = acc.address(address); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn pubkey(account: &mut RhaiAccount, pubkey: String) -> Result> { + let mut acc = mem::take(account); + acc = acc.pubkey(pubkey); + *account = acc; + Ok(account.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn add_asset(account: &mut RhaiAccount, asset_id: INT) -> Result> { + let asset_id = id_from_i64_to_u32(asset_id)?; + let mut acc = mem::take(account); + acc = acc.add_asset(asset_id); + *account = acc; + Ok(account.clone()) + } +} + +// Asset module +#[export_module] +mod asset_module { + use super::*; + + // Constructor + #[rhai_fn(return_raw, global)] + pub fn new_asset() -> Result> { + Ok(Asset::new()) + } + + // Getters + #[rhai_fn(global, pure)] + pub fn get_id(asset: &mut RhaiAsset) -> INT { + asset.base_data.id as INT + } + + #[rhai_fn(global, pure)] + pub fn get_created_at(asset: &mut RhaiAsset) -> INT { + asset.base_data.created_at + } + + #[rhai_fn(global, pure)] + pub fn get_name(asset: &mut RhaiAsset) -> String { + asset.name.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_description(asset: &mut RhaiAsset) -> String { + asset.description.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_amount(asset: &mut RhaiAsset) -> f64 { + asset.amount + } + + #[rhai_fn(global, pure)] + pub fn get_address(asset: &mut RhaiAsset) -> String { + asset.address.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_asset_type(asset: &mut RhaiAsset) -> String { + asset_type_to_string(&asset.asset_type) + } + + #[rhai_fn(global, pure)] + pub fn get_decimals(asset: &mut RhaiAsset) -> INT { + asset.decimals as INT + } + + // Setters using builder pattern with proper mutability handling + #[rhai_fn(return_raw, global)] + pub fn name(asset: &mut RhaiAsset, name: String) -> Result> { + let mut ast = mem::take(asset); + ast = ast.name(name); + *asset = ast; + Ok(asset.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn description(asset: &mut RhaiAsset, description: String) -> Result> { + let mut ast = mem::take(asset); + ast = ast.description(description); + *asset = ast; + Ok(asset.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn amount(asset: &mut RhaiAsset, amount: f64) -> Result> { + let mut ast = mem::take(asset); + ast = ast.amount(amount); + *asset = ast; + Ok(asset.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn address(asset: &mut RhaiAsset, address: String) -> Result> { + let mut ast = mem::take(asset); + ast = ast.address(address); + *asset = ast; + Ok(asset.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn asset_type(asset: &mut RhaiAsset, asset_type_str: String) -> Result> { + let asset_type_enum = string_to_asset_type(&asset_type_str)?; + let mut ast = mem::take(asset); + ast = ast.asset_type(asset_type_enum); + *asset = ast; + Ok(asset.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn decimals(asset: &mut RhaiAsset, decimals: INT) -> Result> { + if decimals < 0 || decimals > 255 { + return Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Decimals value must be between 0 and 255, got {}", decimals).into(), + Position::NONE + ))); + } + let mut ast = mem::take(asset); + ast = ast.decimals(decimals as u8); + *asset = ast; + Ok(asset.clone()) + } +} +// Listing module +#[export_module] +mod listing_module { + use super::*; + + // Constructor + #[rhai_fn(return_raw, global)] + pub fn new_listing() -> Result> { + Ok(Listing::new()) + } + + // Getters + #[rhai_fn(global, pure)] + pub fn get_id(listing: &mut RhaiListing) -> INT { + listing.base_data.id as INT + } + + #[rhai_fn(global, pure)] + pub fn get_created_at(listing: &mut RhaiListing) -> INT { + listing.base_data.created_at + } + + #[rhai_fn(global, pure)] + pub fn get_seller_id(listing: &mut RhaiListing) -> INT { + // Parse the seller_id string to u32, then to INT + listing.seller_id.parse::().unwrap_or(0) as INT + } + + #[rhai_fn(global, pure)] + pub fn get_asset_id(listing: &mut RhaiListing) -> INT { + // Parse the asset_id string to u32, then to INT + listing.asset_id.parse::().unwrap_or(0) as INT + } + + #[rhai_fn(global, pure)] + pub fn get_price(listing: &mut RhaiListing) -> f64 { + listing.price + } + + #[rhai_fn(global, pure)] + pub fn get_currency(listing: &mut RhaiListing) -> String { + listing.currency.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_listing_type(listing: &mut RhaiListing) -> String { + listing_type_to_string(&listing.listing_type) + } + + #[rhai_fn(global, pure)] + pub fn get_status(listing: &mut RhaiListing) -> String { + listing_status_to_string(&listing.status) + } + + #[rhai_fn(global, pure)] + pub fn get_title(listing: &mut RhaiListing) -> String { + listing.title.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_description(listing: &mut RhaiListing) -> String { + listing.description.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_image_url(listing: &mut RhaiListing) -> String { + listing.image_url.clone().unwrap_or_else(|| "".to_string()) + } + + #[rhai_fn(global, pure)] + pub fn get_end_date(listing: &mut RhaiListing) -> INT { + listing.expires_at.map_or(0, |d| d.timestamp()) + } + + #[rhai_fn(global, pure)] + pub fn get_tags(listing: &mut RhaiListing) -> Array { + let mut tags_array = Array::new(); + for tag in &listing.tags { + tags_array.push(Dynamic::from(tag.clone())); + } + tags_array + } + + #[rhai_fn(global, pure)] + pub fn get_bids(listing: &mut RhaiListing) -> Array { + let mut bids_array = Array::new(); + for bid in &listing.bids { + bids_array.push(Dynamic::from(bid.clone())); + } + bids_array + } + + // Setters using builder pattern with proper mutability handling + #[rhai_fn(return_raw, global)] + pub fn seller_id(listing: &mut RhaiListing, seller_id: INT) -> Result> { + let seller_id = id_from_i64_to_u32(seller_id)?; + let mut lst = mem::take(listing); + lst = lst.seller_id(seller_id); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn asset_id(listing: &mut RhaiListing, asset_id: INT) -> Result> { + let asset_id = id_from_i64_to_u32(asset_id)?; + let mut lst = mem::take(listing); + lst = lst.asset_id(asset_id); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn price(listing: &mut RhaiListing, price: f64) -> Result> { + let mut lst = mem::take(listing); + lst = lst.price(price); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn currency(listing: &mut RhaiListing, currency: String) -> Result> { + let mut lst = mem::take(listing); + lst = lst.currency(currency); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn listing_type(listing: &mut RhaiListing, listing_type_str: String) -> Result> { + let listing_type_enum = string_to_listing_type(&listing_type_str)?; + let mut lst = mem::take(listing); + lst = lst.listing_type(listing_type_enum); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn title(listing: &mut RhaiListing, title: String) -> Result> { + let mut lst = mem::take(listing); + lst = lst.title(title); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn description(listing: &mut RhaiListing, description: String) -> Result> { + let mut lst = mem::take(listing); + lst = lst.description(description); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn image_url(listing: &mut RhaiListing, image_url: String) -> Result> { + let mut lst = mem::take(listing); + lst = lst.image_url(Some(image_url)); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn expires_at(listing: &mut RhaiListing, end_date_ts: INT) -> Result> { + use chrono::TimeZone; + let end_date = chrono::Utc.timestamp_opt(end_date_ts, 0) + .single() + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid timestamp: {}", end_date_ts).into(), + Position::NONE + )))?; + + let mut lst = mem::take(listing); + lst = lst.expires_at(Some(end_date)); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn add_tag(listing: &mut RhaiListing, tag: String) -> Result> { + let mut lst = mem::take(listing); + lst = lst.add_tag(tag); + *listing = lst; + Ok(listing.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn add_bid(listing: &mut RhaiListing, bid: RhaiBid) -> Result> { + let lst = mem::take(listing); + match lst.clone().add_bid(bid) { + Ok(updated_lst) => { + *listing = updated_lst; + Ok(listing.clone()) + }, + Err(err) => { + // Put back the original listing since the add_bid failed + *listing = lst; + Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to add bid: {}", err).into(), + Position::NONE + ))) + } + } + } + + #[rhai_fn(return_raw, global)] + pub fn complete_sale(listing: &mut RhaiListing, buyer_id: INT) -> Result> { + let buyer_id = id_from_i64_to_u32(buyer_id)?; + let lst = mem::take(listing); + + // First set the buyer_id and sale_price + let lst_with_buyer = lst.clone().buyer_id(buyer_id); + + // Now complete the sale + match lst_with_buyer.complete_sale() { + Ok(updated_lst) => { + *listing = updated_lst; + Ok(listing.clone()) + }, + Err(err) => { + // Put back the original listing since the complete_sale failed + *listing = lst; + Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to complete sale: {}", err).into(), + Position::NONE + ))) + } + } + } + + #[rhai_fn(return_raw, global)] + pub fn cancel(listing: &mut RhaiListing) -> Result> { + let lst = mem::take(listing); + match lst.clone().cancel() { + Ok(updated_lst) => { + *listing = updated_lst; + Ok(listing.clone()) + }, + Err(err) => { + // Put back the original listing since the cancel failed + *listing = lst; + Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to cancel listing: {}", err).into(), + Position::NONE + ))) + } + } + } + + #[rhai_fn(return_raw, global)] + pub fn check_expiration(listing: &mut RhaiListing) -> Result> { + let mut lst = mem::take(listing); + lst = lst.check_expiration(); + *listing = lst; + Ok(listing.clone()) + } +} + +// Bid module +#[export_module] +mod bid_module { + use super::*; + + // Constructor + #[rhai_fn(return_raw, global)] + pub fn new_bid() -> Result> { + Ok(Bid::new()) + } + + // Getters + #[rhai_fn(global, pure)] + pub fn get_listing_id(bid: &mut RhaiBid) -> String { + bid.listing_id.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_bidder_id(bid: &mut RhaiBid) -> INT { + bid.bidder_id as INT + } + + #[rhai_fn(global, pure)] + pub fn get_amount(bid: &mut RhaiBid) -> f64 { + bid.amount + } + + #[rhai_fn(global, pure)] + pub fn get_currency(bid: &mut RhaiBid) -> String { + bid.currency.clone() + } + + #[rhai_fn(global, pure)] + pub fn get_status(bid: &mut RhaiBid) -> String { + bid_status_to_string(&bid.status) + } + + #[rhai_fn(global, pure)] + pub fn get_created_at(bid: &mut RhaiBid) -> INT { + bid.created_at.timestamp() + } + + // Setters using builder pattern with proper mutability handling + #[rhai_fn(return_raw, global)] + pub fn listing_id(bid: &mut RhaiBid, listing_id: String) -> Result> { + let mut b = mem::take(bid); + b = b.listing_id(listing_id); + *bid = b; + Ok(bid.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn bidder_id(bid: &mut RhaiBid, bidder_id: INT) -> Result> { + let bidder_id = id_from_i64_to_u32(bidder_id)?; + let mut b = mem::take(bid); + b = b.bidder_id(bidder_id); + *bid = b; + Ok(bid.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn amount(bid: &mut RhaiBid, amount: f64) -> Result> { + let mut b = mem::take(bid); + b = b.amount(amount); + *bid = b; + Ok(bid.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn currency(bid: &mut RhaiBid, currency: String) -> Result> { + let mut b = mem::take(bid); + b = b.currency(currency); + *bid = b; + Ok(bid.clone()) + } + + #[rhai_fn(return_raw, global)] + pub fn update_status(bid: &mut RhaiBid, status_str: String) -> Result> { + let status = string_to_bid_status(&status_str)?; + let mut b = mem::take(bid); + b = b.status(status); + *bid = b; + Ok(bid.clone()) + } +} use std::collections::HashMap; -use std::error::Error as StdError; // For Box +use std::sync::Mutex; +use rhai::ImmutableString; -// Custom error type for Rhai that wraps a String -#[derive(Debug)] +// Custom error type for Rhai struct RhaiStringError(String); - -impl std::fmt::Display for RhaiStringError { +impl std::fmt::Debug for RhaiStringError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } -impl StdError for RhaiStringError {} +// Register all Rhai functions for the finance module +pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { + // --- Register model-specific modules with the engine --- + let account_module = exported_module!(account_module); + engine.register_global_module(account_module.into()); + let asset_module = exported_module!(asset_module); + engine.register_global_module(asset_module.into()); -use chrono::{DateTime, Utc}; + let listing_module = exported_module!(listing_module); + engine.register_global_module(listing_module.into()); -use crate::models::finance::account::Account; -use crate::models::finance::asset::{Asset, AssetType}; -use crate::models::finance::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; + let bid_module = exported_module!(bid_module); + engine.register_global_module(bid_module.into()); -// --- Enum to String & String to Enum Helper Functions (domain-specific) --- -// These remain here as they are specific to the finance models' enums. - -fn asset_type_to_string(asset_type: &AssetType) -> ImmutableString { - format!("{:?}", asset_type).into() -} -fn string_to_asset_type(s: &str) -> Result> { - match s { - "Erc20" => Ok(AssetType::Erc20), - "Erc721" => Ok(AssetType::Erc721), - "Erc1155" => Ok(AssetType::Erc1155), - "Native" => Ok(AssetType::Native), - _ => Err(format!("Invalid AssetType string: {}", s).into()), - } -} - -fn listing_status_to_string(status: &ListingStatus) -> ImmutableString { - format!("{:?}", status).into() -} -fn string_to_listing_status(s: &str) -> Result> { - match s.to_lowercase().as_str() { - "active" => Ok(ListingStatus::Active), - "sold" => Ok(ListingStatus::Sold), - "cancelled" => Ok(ListingStatus::Cancelled), - "expired" => Ok(ListingStatus::Expired), - _ => Err(format!("Invalid ListingStatus string: {}", s).into()), - } -} - -fn listing_type_to_string(lt: &ListingType) -> ImmutableString { - format!("{:?}", lt).into() -} -fn string_to_listing_type(s: &str) -> Result> { - match s.to_lowercase().as_str() { - "fixedprice" => Ok(ListingType::FixedPrice), - "auction" => Ok(ListingType::Auction), - "exchange" => Ok(ListingType::Exchange), - _ => Err(format!("Invalid ListingType string: {}", s).into()), - } -} - -fn bid_status_to_string(status: &BidStatus) -> ImmutableString { - format!("{:?}", status).into() -} -fn string_to_bid_status(s: &str) -> Result> { - match s.to_lowercase().as_str() { - "active" => Ok(BidStatus::Active), - "accepted" => Ok(BidStatus::Accepted), - "rejected" => Ok(BidStatus::Rejected), - "cancelled" => Ok(BidStatus::Cancelled), - _ => Err(format!("Invalid BidStatus string: {}", s).into()), - } -} - -pub fn register_rhai_engine_functions( - engine: &mut Engine, - db_accounts: Arc>>, - db_assets: Arc>>, - db_listings: Arc>>, -) { - // --- Account model --- - engine.register_type_with_name::("Account"); - engine.register_fn("new_account", || -> Account { - Account::new(None, "", 0, "", "", "", "") - }); - // Getters - engine.register_get("id", |acc: &mut Account| acc.base_data.id as INT); - engine.register_get("created_at_ts", |acc: &mut Account| acc.base_data.created_at); - engine.register_get("name", |acc: &mut Account| -> ImmutableString { acc.name.clone().into() }); - engine.register_get("user_id", |acc: &mut Account| acc.user_id as INT); - engine.register_get("description", |acc: &mut Account| -> ImmutableString { acc.description.clone().into() }); - engine.register_get("ledger", |acc: &mut Account| -> ImmutableString { acc.ledger.clone().into() }); - engine.register_get("address", |acc: &mut Account| -> ImmutableString { acc.address.clone().into() }); - engine.register_get("pubkey", |acc: &mut Account| -> ImmutableString { acc.pubkey.clone().into() }); - engine.register_get("assets_list", |acc: &mut Account| -> Result> { - Ok(acc.assets.iter().cloned().map(rhai::Dynamic::from).collect()) - }); - engine.register_get("modified_at_ts", |acc: &mut Account| acc.base_data.modified_at); - - // Setters (Builder Pattern) - engine.register_fn("set_name", |mut acc: Account, name: ImmutableString| -> Result> { - acc.name = name.to_string(); Ok(acc) - }); - engine.register_fn("set_user_id", |mut acc: Account, user_id: INT| -> Result> { - acc.user_id = user_id as u32; Ok(acc) - }); - engine.register_fn("set_description", |mut acc: Account, description: ImmutableString| -> Result> { - acc.description = description.to_string(); Ok(acc) - }); - engine.register_fn("set_ledger", |mut acc: Account, ledger: ImmutableString| -> Result> { - acc.ledger = ledger.to_string(); Ok(acc) - }); - engine.register_fn("set_address", |mut acc: Account, address: ImmutableString| -> Result> { - acc.address = address.to_string(); Ok(acc) - }); - engine.register_fn("set_pubkey", |mut acc: Account, pubkey: ImmutableString| -> Result> { - acc.pubkey = pubkey.to_string(); Ok(acc) - }); - // Action: Add an Asset object to the account's asset list - engine.register_fn("add_asset", |mut acc: Account, asset: Asset| -> Result> { - acc.assets.push(asset); - Ok(acc) - }); - - // --- Asset model --- - engine.register_type_with_name::("Asset"); - engine.register_fn("new_asset", || -> Asset { - Asset::new(None, "", "", 0.0, "", AssetType::Native, 0) - }); - // Getters - engine.register_get("id", |asset: &mut Asset| asset.base_data.id as INT); - engine.register_get("created_at_ts", |asset: &mut Asset| asset.base_data.created_at); - engine.register_get("name", |asset: &mut Asset| -> ImmutableString { asset.name.clone().into() }); - engine.register_get("description", |asset: &mut Asset| -> ImmutableString { asset.description.clone().into() }); - engine.register_get("amount", |asset: &mut Asset| asset.amount); - engine.register_get("address", |asset: &mut Asset| -> ImmutableString { asset.address.clone().into() }); - engine.register_get("asset_type_str", |asset: &mut Asset| -> ImmutableString { self::asset_type_to_string(&asset.asset_type) }); - engine.register_get("decimals", |asset: &mut Asset| asset.decimals as INT); - engine.register_get("modified_at_ts", |asset: &mut Asset| asset.base_data.modified_at); - - // Setters (Builder Pattern) - engine.register_fn("set_name", |mut asset: Asset, name: ImmutableString| -> Result> { - asset.name = name.to_string(); Ok(asset) - }); - engine.register_fn("set_description", |mut asset: Asset, description: ImmutableString| -> Result> { - asset.description = description.to_string(); Ok(asset) - }); - engine.register_fn("set_amount", |mut asset: Asset, amount: f64| -> Result> { - asset.amount = amount; Ok(asset) - }); - engine.register_fn("set_address", |mut asset: Asset, address: ImmutableString| -> Result> { - asset.address = address.to_string(); Ok(asset) - }); - engine.register_fn("set_asset_type", |mut asset: Asset, asset_type_str: ImmutableString| -> Result> { - asset.asset_type = self::string_to_asset_type(asset_type_str.as_str())?; - Ok(asset) - }); - engine.register_fn("set_decimals", |mut asset: Asset, decimals: INT| -> Result> { - asset.decimals = decimals as u8; Ok(asset) - }); - - // --- Listing model --- - engine.register_type_with_name::("Listing"); - engine.register_fn("new_listing", || -> Listing { - Listing::new(None, "", "", "", AssetType::Native, "", 0.0, "", ListingType::FixedPrice, None, Vec::new(), None::) - }); - // Getters - engine.register_get("id", |l: &mut Listing| l.base_data.id as INT); - engine.register_get("created_at_ts", |l: &mut Listing| l.base_data.created_at); - engine.register_get("modified_at_ts", |l: &mut Listing| l.base_data.modified_at); - engine.register_get("title", |l: &mut Listing| -> ImmutableString { l.title.clone().into() }); - engine.register_get("description", |l: &mut Listing| -> ImmutableString { l.description.clone().into() }); - engine.register_get("asset_id", |l: &mut Listing| -> ImmutableString { l.asset_id.clone().into() }); - engine.register_get("asset_type_str", |l: &mut Listing| -> ImmutableString { self::asset_type_to_string(&l.asset_type) }); - engine.register_get("seller_id", |l: &mut Listing| -> ImmutableString { l.seller_id.clone().into() }); - engine.register_get("price", |l: &mut Listing| l.price); - engine.register_get("currency", |l: &mut Listing| -> ImmutableString { l.currency.clone().into() }); - engine.register_get("listing_type", |l: &mut Listing| l.listing_type.clone()); - engine.register_get("status", |l: &mut Listing| l.status.clone()); - engine.register_get("expires_at_ts", |l: &mut Listing| l.expires_at); - engine.register_get("expires_at_ts_opt", |l: &mut Listing| l.expires_at.map(|dt| dt.timestamp())); - engine.register_get("tags", |l: &mut Listing| -> Result> { - Ok(l.tags.iter().map(|s| Dynamic::from(s.clone())).collect()) - }); - engine.register_get("image_url", |l: &mut Listing| -> Option { l.image_url.as_ref().map(|s| s.clone().into()) }); - engine.register_get("bids_list", |l: &mut Listing| -> Result> { - Ok(l.bids.iter().cloned().map(rhai::Dynamic::from).collect()) - }); - // Setters (Builder Pattern) - engine.register_fn("set_title", |mut l: Listing, title: ImmutableString| -> Result> { - l.title = title.to_string(); Ok(l) - }); - engine.register_fn("set_description", |mut l: Listing, description: ImmutableString| -> Result> { - l.description = description.to_string(); Ok(l) - }); - engine.register_fn("set_asset_id", |mut l: Listing, asset_id: ImmutableString| -> Result> { - l.asset_id = asset_id.to_string(); Ok(l) - }); - engine.register_fn("set_asset_type", |mut l: Listing, asset_type_str: ImmutableString| -> Result> { - l.asset_type = self::string_to_asset_type(asset_type_str.as_str())?; - Ok(l) - }); - engine.register_fn("set_seller_id", |mut l: Listing, seller_id: ImmutableString| -> Result> { - l.seller_id = seller_id.to_string(); Ok(l) - }); - engine.register_fn("set_price", |mut l: Listing, price: f64| -> Result> { - l.price = price; Ok(l) - }); - engine.register_fn("set_currency", |mut l: Listing, currency: ImmutableString| -> Result> { - l.currency = currency.to_string(); Ok(l) - }); - engine.register_fn("set_listing_type", |mut l: Listing, listing_type: ImmutableString| -> Result> { - l.listing_type = self::string_to_listing_type(listing_type.as_str())?; - Ok(l) - }); - engine.register_fn("set_status_str", |mut l: Listing, status_str: ImmutableString| -> Result> { - l.status = self::string_to_listing_status(status_str.as_str())?; - Ok(l) - }); - engine.register_fn("set_expires_at_ts", |mut l: Listing, expires_at_ts: Option| -> Result> { - l.expires_at = expires_at_ts.map(|ts| DateTime::from_timestamp(ts, 0).unwrap_or_else(|| Utc::now())); - Ok(l) - }); - engine.register_fn("set_tags", |mut l: Listing, tags_array: Array| -> Result> { - l.tags = tags_array.into_iter().map(|d| d.into_string().unwrap_or_default()).collect(); - Ok(l) - }); - engine.register_fn("add_tag", |mut l: Listing, tag: ImmutableString| -> Result> { - l.tags.push(tag.to_string()); - Ok(l) - }); - engine.register_fn("set_image_url", |mut l: Listing, image_url: Option| -> Result> { - l.image_url = image_url.map(|s| s.to_string()); - Ok(l) - }); - // Listing Action Methods (preserved) - engine.register_fn("add_listing_bid", |listing: Listing, bid: Bid| -> Result> { - listing.add_bid(bid).map_err(|e_str| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to add bid".to_string(), - Box::new(RhaiStringError(e_str.to_string())), - )) - }) - }); - engine.register_fn("accept_listing_bid", |listing: Listing, bid_index_rhai: i64| -> Result> { - let bid_index = bid_index_rhai as usize; - if bid_index >= listing.bids.len() { - return Err(Box::new(EvalAltResult::ErrorSystem( - "Invalid bid index".to_string(), - Box::new(RhaiStringError(format!("Bid index {} out of bounds for {} bids", bid_index, listing.bids.len()))), - ))); - } - - let bid_to_accept = listing.bids[bid_index].clone(); - - if bid_to_accept.status != BidStatus::Active { - return Err(Box::new(EvalAltResult::ErrorSystem( - "Bid not active".to_string(), - Box::new(RhaiStringError(format!("Cannot accept bid at index {} as it is not active (status: {:?})", bid_index, bid_to_accept.status))), - ))); - } - - let mut listing_after_sale = listing.complete_sale(bid_to_accept.bidder_id.to_string(), bid_to_accept.amount) - .map_err(|e_str| Box::new(EvalAltResult::ErrorSystem( - "Failed to complete sale".to_string(), - Box::new(RhaiStringError(e_str.to_string())), - )))?; - - // Update bid statuses on the new listing state - for (idx, bid_in_list) in listing_after_sale.bids.iter_mut().enumerate() { - if idx == bid_index { - *bid_in_list = bid_in_list.clone().update_status(BidStatus::Accepted); - } else { - if bid_in_list.status == BidStatus::Active { // Only reject other active bids - *bid_in_list = bid_in_list.clone().update_status(BidStatus::Rejected); - } - } - } - Ok(listing_after_sale) - }); - engine.register_fn("cancel_listing", |_ctx: NativeCallContext, listing: Listing| -> Result> { - listing.cancel().map_err(|e_str| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to cancel listing".to_string(), - Box::new(RhaiStringError(e_str.to_string())) - )) - }) - }); - // --- Bid model (preserved as is) --- - engine.register_type_with_name::("Bid") - .register_fn("new_bid", - |listing_id_rhai: ImmutableString, bidder_id_rhai: INT, amount_rhai: f64, currency_rhai: ImmutableString| -> Bid { - Bid::new(listing_id_rhai, bidder_id_rhai as u32, amount_rhai, currency_rhai) - } - ) - .register_get_set("listing_id", - |bid: &mut Bid| -> ImmutableString { bid.listing_id.clone().into() }, - |bid: &mut Bid, val: ImmutableString| bid.listing_id = val.to_string() - ) - .register_get_set("bidder_id", |bid: &mut Bid| bid.bidder_id as INT, |bid: &mut Bid, val: INT| bid.bidder_id = val as u32) - .register_get_set("amount", |bid: &mut Bid| bid.amount, |bid: &mut Bid, val: f64| bid.amount = val) - .register_get_set("currency", - |bid: &mut Bid| -> ImmutableString { bid.currency.clone().into() }, - |bid: &mut Bid, val: ImmutableString| bid.currency = val.to_string() - ) - .register_get("status_str", |bid: &mut Bid| -> ImmutableString { self::bid_status_to_string(&bid.status) }); - - engine.register_fn("accept_bid_script", |bid: Bid| -> Result> { - // Ensure the bid is active before accepting - if bid.status != BidStatus::Active { - return Err(Box::new(EvalAltResult::ErrorSystem( - "Bid not active".to_string(), - Box::new(RhaiStringError(format!("Cannot accept bid as it is not active (status: {:?})", bid.status))) - ))); - } - Ok(bid.update_status(BidStatus::Accepted)) - }); - engine.register_fn("reject_bid_script", |bid: Bid| -> Result> { - // Ensure the bid is active before rejecting - if bid.status != BidStatus::Active { - return Err(Box::new(EvalAltResult::ErrorSystem( - "Bid not active".to_string(), - Box::new(RhaiStringError(format!("Cannot reject bid as it is not active (status: {:?})", bid.status))) - ))); - } - Ok(bid.update_status(BidStatus::Rejected)) - }); - engine.register_fn("cancel_bid_script", |bid: Bid| -> Result> { - // Ensure the bid is active before cancelling - if bid.status != BidStatus::Active { - return Err(Box::new(EvalAltResult::ErrorSystem( - "Bid not active".to_string(), - Box::new(RhaiStringError(format!("Cannot cancel bid as it is not active (status: {:?})", bid.status))) - ))); - } - Ok(bid.update_status(BidStatus::Cancelled)) - }); - - // --- Global Helper Functions (Enum conversions, potentially already covered by macros but good for direct script use) --- + // --- Global Helper Functions (Enum conversions) --- engine.register_fn("str_to_asset_type", |s: ImmutableString| self::string_to_asset_type(s.as_str())); engine.register_fn("asset_type_to_str", self::asset_type_to_string); engine.register_fn("str_to_listing_status", |s: ImmutableString| self::string_to_listing_status(s.as_str())); @@ -350,67 +726,73 @@ pub fn register_rhai_engine_functions( engine.register_fn("str_to_bid_status", |s: ImmutableString| self::string_to_bid_status(s.as_str())); engine.register_fn("bid_status_to_str", self::bid_status_to_string); - // --- Mock DB functions (preserved) --- - let accounts_db_clone = Arc::clone(&db_accounts); - engine.register_fn("set_account", move |mut account: Account| -> Account { - let mut db = accounts_db_clone.lock().unwrap(); - if account.base_data.id == 0 { - let next_id = db.keys().max().cloned().unwrap_or(0) + 1; - account.base_data.update_id(next_id); - } - db.insert(account.base_data.id, account.clone()); - account + // --- Database interaction functions (registered in a separate db_module) --- + let mut db_module = Module::new(); + + // Account DB functions + let db_set_account = Arc::clone(&db); + db_module.set_native_fn("set_account", move |account: Account| -> Result> { + let collection = db_set_account.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; + collection.set(&account) + .map(|(id, _)| id as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) }); - let accounts_db_clone_get = Arc::clone(&db_accounts); - engine.register_fn("get_account_by_id", move |id_rhai: INT| -> Result> { - let db = accounts_db_clone_get.lock().unwrap(); - match db.get(&(id_rhai as u32)) { - Some(account) => Ok(account.clone()), - None => Err(format!("Account not found with ID: {}", id_rhai).into()), - } + let db_get_account = Arc::clone(&db); + db_module.set_native_fn("get_account_by_id", move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_account.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; + collection.get_by_id(id_u32) + .map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); - let assets_db_clone = Arc::clone(&db_assets); - engine.register_fn("set_asset", move |mut asset: Asset| -> Asset { - let mut db = assets_db_clone.lock().unwrap(); - if asset.base_data.id == 0 { - let next_id = db.keys().max().cloned().unwrap_or(0) + 1; - asset.base_data.update_id(next_id); - } - db.insert(asset.base_data.id, asset.clone()); - asset + // Asset DB functions + let db_set_asset = Arc::clone(&db); + db_module.set_native_fn("set_asset", move |asset: Asset| -> Result> { + let collection = db_set_asset.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; + collection.set(&asset) + .map(|(id, _)| id as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) }); - let assets_db_clone_get = Arc::clone(&db_assets); - engine.register_fn("get_asset_by_id", move |id_rhai: INT| -> Result> { - let db = assets_db_clone_get.lock().unwrap(); - match db.get(&(id_rhai as u32)) { - Some(asset) => Ok(asset.clone()), - None => Err(format!("Asset not found with ID: {}", id_rhai).into()), - } + let db_get_asset = Arc::clone(&db); + db_module.set_native_fn("get_asset_by_id", move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_asset.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; + collection.get_by_id(id_u32) + .map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); - let listings_db_clone = Arc::clone(&db_listings); - engine.register_fn("set_listing", move |mut listing: Listing| -> Listing { - let mut db = listings_db_clone.lock().unwrap(); - if listing.base_data.id == 0 { - let next_id = db.keys().max().cloned().unwrap_or(0) + 1; - listing.base_data.update_id(next_id); - } - db.insert(listing.base_data.id, listing.clone()); - listing + // Listing DB functions + let db_set_listing = Arc::clone(&db); + db_module.set_native_fn("set_listing", move |listing: Listing| -> Result> { + let collection = db_set_listing.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; + collection.set(&listing) + .map(|(id, _)| id as INT) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) }); - let listings_db_clone_get = Arc::clone(&db_listings); - engine.register_fn("get_listing_by_id", move |id_rhai: INT| -> Result> { - let db = listings_db_clone_get.lock().unwrap(); - match db.get(&(id_rhai as u32)) { - Some(listing) => Ok(listing.clone()), - None => Err(format!("Listing not found with ID: {}", id_rhai).into()), - } + let db_get_listing = Arc::clone(&db); + db_module.set_native_fn("get_listing_by_id", move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_listing.collection::().map_err(|e| + Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; + collection.get_by_id(id_u32) + .map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) }); + engine.register_global_module(db_module.into()); + // Global timestamp function for scripts to get current time engine.register_fn("timestamp", || Utc::now().timestamp()); + + println!("Successfully registered finance Rhai module."); } diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index 71a0e46..c586df4 100644 --- a/heromodels/src/models/flow/flow.rs +++ b/heromodels/src/models/flow/flow.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use super::flow_step::FlowStep; /// Represents a signing flow. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] #[model] pub struct Flow { /// Base model data (id, created_at, updated_at). @@ -27,9 +27,9 @@ pub struct Flow { impl Flow { /// Create a new flow. - /// The `id` is the database primary key. /// The `flow_uuid` should be a Uuid::new_v4().to_string(). - pub fn new(_id: u32, flow_uuid: impl ToString) -> Self { + /// The ID is managed by `BaseModelData::new()` and the database. + pub fn new(flow_uuid: impl ToString) -> Self { Self { base_data: BaseModelData::new(), flow_uuid: flow_uuid.to_string(), diff --git a/heromodels/src/models/flow/flow_step.rs b/heromodels/src/models/flow/flow_step.rs index 432279f..ce554f6 100644 --- a/heromodels/src/models/flow/flow_step.rs +++ b/heromodels/src/models/flow/flow_step.rs @@ -1,6 +1,7 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; +use std::default::Default; /// Represents a step within a signing flow. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] @@ -20,6 +21,17 @@ pub struct FlowStep { pub status: String, } +impl Default for FlowStep { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + description: None, + step_order: 0, + status: String::from("Pending"), // Default status + } + } +} + impl FlowStep { /// Create a new flow step. pub fn new(_id: u32, step_order: u32) -> Self { diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index dff653d..b564850 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -1,140 +1,384 @@ -use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, Position}; +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; +use std::mem; -use heromodels_core::BaseModelData; -use crate::db::hero::OurDB; // Import OurDB for actual DB operations -use crate::db::Collection; // Collection might be needed if we add more specific DB functions -use super::{ - flow::Flow, - flow_step::FlowStep, - signature_requirement::SignatureRequirement, -}; -// use rhai_wrapper::wrap_vec_return; // Not currently used for flow, but keep for potential future use. +use super::flow::Flow; +use super::flow_step::FlowStep; +use super::signature_requirement::SignatureRequirement; +type RhaiFlow = Flow; +type RhaiFlowStep = FlowStep; +type RhaiSignatureRequirement = SignatureRequirement; +use crate::db::hero::OurDB; +use crate::db::Collection; +use crate::db::Db; +use heromodels_core::Model; -// Helper function to convert Rhai's i64 to u32 for IDs -fn i64_to_u32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { - val.try_into().map_err(|_e| { +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| Box::new(EvalAltResult::ErrorArithmetic( - format!("Conversion error for {} in {} from i64 to u32", field_name, object_name), - context_pos, + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE )) - }) + ) } +// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers +fn val_from_i64_to_u64(val_i64: i64) -> Result> { + u64::try_from(val_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert value '{}' to u64", val_i64).into(), + Position::NONE + )) + ) +} + +#[export_module] +mod rhai_flow_module { + // --- Flow Functions --- + #[rhai_fn(name = "new_flow")] + pub fn new_flow(flow_uuid: String) -> RhaiFlow { + Flow::new(flow_uuid) + } + + /// Sets the flow name + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result> { + let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement + *flow = owned_flow.name(name); + Ok(flow.clone()) + } + + /// Sets the flow status + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn flow_status(flow: &mut RhaiFlow, status: String) -> Result> { + let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement + *flow = owned_flow.status(status); + Ok(flow.clone()) + } + + /// Adds a step to the flow + #[rhai_fn(name = "add_step", return_raw, global, pure)] + pub fn flow_add_step(flow: &mut RhaiFlow, step: RhaiFlowStep) -> Result> { + let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement + *flow = owned_flow.add_step(step); + Ok(flow.clone()) + } + + // Flow Getters + #[rhai_fn(get = "id", pure)] + pub fn get_id(flow: &mut RhaiFlow) -> i64 { flow.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.modified_at } + #[rhai_fn(get = "flow_uuid", pure)] + pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { flow.flow_uuid.clone() } + #[rhai_fn(get = "name", pure)] + pub fn get_name(flow: &mut RhaiFlow) -> String { flow.name.clone() } + #[rhai_fn(get = "status", pure)] + pub fn get_status(flow: &mut RhaiFlow) -> String { flow.status.clone() } + #[rhai_fn(get = "steps", pure)] + pub fn get_steps(flow: &mut RhaiFlow) -> Array { + flow.steps.iter().cloned().map(Dynamic::from).collect::() + } + + // --- FlowStep Functions --- + #[rhai_fn(global)] + pub fn new_flow_step(step_order_i64: i64) -> Dynamic { + match id_from_i64_to_u32(step_order_i64) { + Ok(step_order) => { + let mut flow_step = FlowStep::default(); + flow_step.step_order = step_order; + Dynamic::from(flow_step) + }, + Err(err) => Dynamic::from(err.to_string()) + } + } + + /// Sets the flow step description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn flow_step_description(step: &mut RhaiFlowStep, description: String) -> Result> { + let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait + *step = owned_step.description(description); + Ok(step.clone()) + } + + /// Sets the flow step status + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn flow_step_status(step: &mut RhaiFlowStep, status: String) -> Result> { + let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait + *step = owned_step.status(status); + Ok(step.clone()) + } + + // FlowStep Getters + #[rhai_fn(get = "id", pure)] + pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { step.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.modified_at } + #[rhai_fn(get = "description", pure)] + pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic { + match &step.description { + Some(desc) => Dynamic::from(desc.clone()), + None => Dynamic::UNIT, + } + } + #[rhai_fn(get = "step_order", pure)] + pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { step.step_order as i64 } + #[rhai_fn(get = "status", pure)] + pub fn get_step_status(step: &mut RhaiFlowStep) -> String { step.status.clone() } + + // --- SignatureRequirement Functions --- + /// Create a new signature requirement + #[rhai_fn(global)] + pub fn new_signature_requirement(flow_step_id_i64: i64, public_key: String, message: String) -> Dynamic { + match id_from_i64_to_u32(flow_step_id_i64) { + Ok(flow_step_id) => { + let mut signature_requirement = SignatureRequirement::default(); + signature_requirement.flow_step_id = flow_step_id; + signature_requirement.public_key = public_key; + signature_requirement.message = message; + Dynamic::from(signature_requirement) + }, + Err(err) => Dynamic::from(err.to_string()) + } + } + + /// Sets the signed_by field + #[rhai_fn(name = "signed_by", return_raw, global, pure)] + pub fn signature_requirement_signed_by(sr: &mut RhaiSignatureRequirement, signed_by: String) -> Result> { + let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait + *sr = owned_sr.signed_by(signed_by); + Ok(sr.clone()) + } + + /// Sets the signature field + #[rhai_fn(name = "signature", return_raw, global, pure)] + pub fn signature_requirement_signature(sr: &mut RhaiSignatureRequirement, signature: String) -> Result> { + let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait + *sr = owned_sr.signature(signature); + Ok(sr.clone()) + } + + /// Sets the status field + #[rhai_fn(name = "status", return_raw, global, pure)] + pub fn signature_requirement_status(sr: &mut RhaiSignatureRequirement, status: String) -> Result> { + let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait + *sr = owned_sr.status(status); + Ok(sr.clone()) + } + + // SignatureRequirement Getters + #[rhai_fn(get = "id", pure)] + pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.modified_at } + #[rhai_fn(get = "flow_step_id", pure)] + pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.flow_step_id as i64 } + #[rhai_fn(get = "public_key", pure)] + pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { sr.public_key.clone() } + #[rhai_fn(get = "message", pure)] + pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { sr.message.clone() } + #[rhai_fn(get = "signed_by", pure)] + pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic { + match &sr.signed_by { + Some(signed_by) => Dynamic::from(signed_by.clone()), + None => Dynamic::UNIT, + } + } + #[rhai_fn(get = "signature", pure)] + pub fn get_sr_signature(sr: &mut RhaiSignatureRequirement) -> Dynamic { + match &sr.signature { + Some(signature) => Dynamic::from(signature.clone()), + None => Dynamic::UNIT, + } + } + #[rhai_fn(get = "status", pure)] + pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { sr.status.clone() } +} + +/// Register the flow module with the Rhai engine pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { - // --- Flow Model --- - - // Constructor: new_flow(id: u32, flow_uuid: String) - engine.register_fn("new_flow", move |context: NativeCallContext, id_i64: i64, flow_uuid: String| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_flow")?; - Ok(Flow::new(id_u32, flow_uuid)) + // Create a module for database functions + let mut db_module = Module::new(); + + // Flow database functions + let db_clone = Arc::clone(&db); + db_module.set_native_fn("save_flow", move |flow: Flow| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&flow) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?; + + // Return the updated flow with the correct ID + Ok(result.1) }); - - // Builder methods for Flow - engine.register_fn("name", |flow: Flow, name_val: String| -> Flow { flow.name(name_val) }); - engine.register_fn("status", |flow: Flow, status_val: String| -> Flow { flow.status(status_val) }); - engine.register_fn("add_step", |flow: Flow, step: FlowStep| -> Flow { flow.add_step(step) }); - - // Getters for Flow fields - engine.register_get("id", |flow: &mut Flow| -> Result> { Ok(flow.base_data.id as i64) }); - engine.register_get("base_data", |flow: &mut Flow| -> Result> { Ok(flow.base_data.clone()) }); - engine.register_get("flow_uuid", |flow: &mut Flow| -> Result> { Ok(flow.flow_uuid.clone()) }); - engine.register_get("name", |flow: &mut Flow| -> Result> { Ok(flow.name.clone()) }); - engine.register_get("status", |flow: &mut Flow| -> Result> { Ok(flow.status.clone()) }); - engine.register_get("steps", |flow: &mut Flow| -> Result> { - let rhai_array = flow.steps.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }); - - // --- FlowStep Model --- - - // Constructor: new_flow_step(id: u32, step_order: u32) - engine.register_fn("new_flow_step", move |context: NativeCallContext, id_i64: i64, step_order_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_flow_step")?; - let step_order_u32 = i64_to_u32(step_order_i64, context.position(), "step_order", "new_flow_step")?; - Ok(FlowStep::new(id_u32, step_order_u32)) - }); - - // Builder methods for FlowStep - engine.register_fn("description", |fs: FlowStep, desc_val: String| -> FlowStep { fs.description(desc_val) }); // Assuming FlowStep::description takes impl ToString - engine.register_fn("status", |fs: FlowStep, status_val: String| -> FlowStep { fs.status(status_val) }); - - // Getters for FlowStep fields - engine.register_get("id", |step: &mut FlowStep| -> Result> { Ok(step.base_data.id as i64) }); - engine.register_get("base_data", |step: &mut FlowStep| -> Result> { Ok(step.base_data.clone()) }); - engine.register_get("description", |step: &mut FlowStep| -> Result> { Ok(match step.description.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) }); - engine.register_get("step_order", |step: &mut FlowStep| -> Result> { Ok(step.step_order as i64) }); - engine.register_get("status", |step: &mut FlowStep| -> Result> { Ok(step.status.clone()) }); - - // --- SignatureRequirement Model --- - - // Constructor: new_signature_requirement(id: u32, flow_step_id: u32, public_key: String, message: String) - engine.register_fn("new_signature_requirement", - move |context: NativeCallContext, id_i64: i64, flow_step_id_i64: i64, public_key: String, message: String| - -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "new_signature_requirement")?; - let flow_step_id_u32 = i64_to_u32(flow_step_id_i64, context.position(), "flow_step_id", "new_signature_requirement")?; - Ok(SignatureRequirement::new(id_u32, flow_step_id_u32, public_key, message)) - }); - - // Builder methods for SignatureRequirement - engine.register_fn("signed_by", |sr: SignatureRequirement, signed_by_val: String| -> SignatureRequirement { sr.signed_by(signed_by_val) }); // Assuming SR::signed_by takes impl ToString - engine.register_fn("signature", |sr: SignatureRequirement, sig_val: String| -> SignatureRequirement { sr.signature(sig_val) }); // Assuming SR::signature takes impl ToString - engine.register_fn("status", |sr: SignatureRequirement, status_val: String| -> SignatureRequirement { sr.status(status_val) }); - - // Getters for SignatureRequirement fields - engine.register_get("id", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.base_data.id as i64) }); - engine.register_get("base_data", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.base_data.clone()) }); - engine.register_get("flow_step_id", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.flow_step_id as i64) }); - engine.register_get("public_key", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.public_key.clone()) }); - engine.register_get("message", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.message.clone()) }); - engine.register_get("signed_by", |sr: &mut SignatureRequirement| -> Result> { Ok(match sr.signed_by.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) }); - engine.register_get("signature", |sr: &mut SignatureRequirement| -> Result> { Ok(match sr.signature.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) }); - engine.register_get("status", |sr: &mut SignatureRequirement| -> Result> { Ok(sr.status.clone()) }); - - // --- BaseModelData Getters (if not already globally registered) --- - // Assuming these might be specific to the context or shadowed, explicit registration is safer. - engine.register_get("id", |bmd: &mut BaseModelData| -> Result> { Ok(bmd.id as i64) }); - engine.register_get("created_at", |bmd: &mut BaseModelData| -> Result> { Ok(bmd.created_at) }); - engine.register_get("modified_at", |bmd: &mut BaseModelData| -> Result> { Ok(bmd.modified_at) }); - // engine.register_get("comments", |bmd: &mut BaseModelData| Ok(bmd.comments.clone())); // Rhai might need specific handling for Vec - - // --- Database Interaction Functions --- - - let captured_db_for_set_flow = Arc::clone(&db); - engine.register_fn("set_flow", move |flow: Flow| -> Result<(), Box> { - captured_db_for_set_flow.set(&flow).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set Flow (ID: {}): {}", flow.base_data.id, e).into(), Position::NONE)) - }) - }); - - let captured_db_for_get_flow = Arc::clone(&db); - engine.register_fn("get_flow_by_id", move |context: NativeCallContext, id_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_flow_by_id")?; - captured_db_for_get_flow.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting Flow (ID: {}): {}", id_u32, e).into(), Position::NONE)))? + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("get_flow_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))? .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE))) }); - // Add get_flows_by_uuid, flow_exists etc. as needed, using wrap_vec_return for Vec results. - - // FlowStep DB functions are removed as FlowSteps are now part of Flow. - - let captured_db_for_set_sig_req = Arc::clone(&db); - engine.register_fn("set_signature_requirement", move |sr: SignatureRequirement| -> Result<(), Box> { - captured_db_for_set_sig_req.set(&sr).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to set SignatureRequirement (ID: {}): {}", sr.base_data.id, e).into(), Position::NONE)) - }) + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("delete_flow", move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE + )))?; + collection.delete_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), + Position::NONE + ))) }); - - let captured_db_for_get_sig_req = Arc::clone(&db); - engine.register_fn("get_signature_requirement_by_id", - move |context: NativeCallContext, id_i64: i64| - -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_signature_requirement_by_id")?; - captured_db_for_get_sig_req.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Error getting SignatureRequirement (ID: {}): {}", id_u32, e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("list_flows", move || -> Result> { + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE + )))?; + let flows = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flows: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for flow in flows { + array.push(Dynamic::from(flow)); + } + Ok(Dynamic::from(array)) }); - + + // FlowStep database functions + let db_clone = Arc::clone(&db); + db_module.set_native_fn("save_flow_step", move |step: FlowStep| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&step) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?; + + // Return the updated flow step with the correct ID + Ok(result.1) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("get_flow_step_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE))) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("delete_flow_step", move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE + )))?; + collection.delete_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), + Position::NONE + ))) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("list_flow_steps", move || -> Result> { + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE + )))?; + let steps = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flow steps: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for step in steps { + array.push(Dynamic::from(step)); + } + Ok(Dynamic::from(array)) + }); + + // SignatureRequirement database functions + let db_clone = Arc::clone(&db); + db_module.set_native_fn("save_signature_requirement", move |sr: SignatureRequirement| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&sr) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?; + + // Return the updated signature requirement with the correct ID + Ok(result.1) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("get_signature_requirement_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("delete_signature_requirement", move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE + )))?; + collection.delete_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, e).into(), + Position::NONE + ))) + }); + + let db_clone = Arc::clone(&db); + db_module.set_native_fn("list_signature_requirements", move || -> Result> { + let collection = db_clone.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE + )))?; + let srs = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all signature requirements: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for sr in srs { + array.push(Dynamic::from(sr)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_static_module("db", db_module.into()); + + // Register the flow module using exported_module! macro + let module = exported_module!(rhai_flow_module); + engine.register_global_module(module.into()); + println!("Flow Rhai module registered."); } diff --git a/heromodels/src/models/flow/signature_requirement.rs b/heromodels/src/models/flow/signature_requirement.rs index 44007cb..05093f6 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -1,9 +1,10 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; +use std::default::Default; /// Represents a signature requirement for a flow step. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[model] pub struct SignatureRequirement { /// Base model data. diff --git a/heromodels/src/models/governance/attached_file.rs b/heromodels/src/models/governance/attached_file.rs new file mode 100644 index 0000000..8885924 --- /dev/null +++ b/heromodels/src/models/governance/attached_file.rs @@ -0,0 +1,27 @@ +// heromodels/src/models/governance/attached_file.rs +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +#[model] +pub struct AttachedFile { + pub base_data: BaseModelData, // Provides id, created_at, updated_at + pub name: String, + pub url: String, + pub file_type: String, // e.g., "pdf", "image/jpeg", "application/msword" + pub size_bytes: u64, + // Optional: could add uploader_id: u32 if needed +} + +impl AttachedFile { + pub fn new(name: String, url: String, file_type: String, size_bytes: u64) -> Self { + Self { + base_data: BaseModelData::new(), + name, + url, + file_type, + size_bytes, + } + } +} diff --git a/heromodels/src/models/governance/mod.rs b/heromodels/src/models/governance/mod.rs index b3df030..5570233 100644 --- a/heromodels/src/models/governance/mod.rs +++ b/heromodels/src/models/governance/mod.rs @@ -2,4 +2,7 @@ // This module will contain the Proposal model and related types. pub mod proposal; -pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; \ No newline at end of file +pub mod attached_file; + +pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; +pub use self::attached_file::AttachedFile; \ No newline at end of file diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 0440f89..8e9a1a3 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -3,10 +3,11 @@ use chrono::{DateTime, Utc}; use heromodels_derive::model; // For #[model] use rhai::{CustomType, TypeBuilder}; -use rhai_autobind_macros::rhai_model_export; use serde::{Deserialize, Serialize}; use heromodels_core::BaseModelData; +use crate::models::core::Comment; +use super::AttachedFile; // --- Enums --- @@ -29,6 +30,7 @@ impl Default for ProposalStatus { /// VoteEventStatus represents the status of the voting process for a proposal #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum VoteEventStatus { + Upcoming, // Voting is scheduled but not yet open Open, // Voting is currently open Closed, // Voting has finished Cancelled, // The voting event was cancelled @@ -36,14 +38,14 @@ pub enum VoteEventStatus { impl Default for VoteEventStatus { fn default() -> Self { - VoteEventStatus::Open + VoteEventStatus::Upcoming } } // --- Structs --- /// VoteOption represents a specific choice that can be voted on -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] pub struct VoteOption { pub id: u8, // Simple identifier for this option pub text: String, // Descriptive text of the option @@ -65,8 +67,8 @@ impl VoteOption { } /// Ballot represents an individual vote cast by a user -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] -#[rhai_model_export(db_type = "std::sync::Arc")] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] +// Removed rhai_model_export macro as it's causing compilation errors #[model] // Has base.Base in V spec pub struct Ballot { pub base_data: BaseModelData, @@ -102,7 +104,7 @@ impl Ballot { /// Proposal represents a governance proposal that can be voted upon. #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] -#[rhai_model_export(db_type = "std::sync::Arc")] +// Removed rhai_model_export macro as it's causing compilation errors #[model] // Has base.Base in V spec pub struct Proposal { pub base_data: BaseModelData, @@ -113,9 +115,6 @@ pub struct Proposal { pub description: String, pub status: ProposalStatus, - pub created_at: DateTime, - pub updated_at: DateTime, - // Voting event aspects pub vote_start_date: DateTime, pub vote_end_date: DateTime, @@ -123,6 +122,35 @@ pub struct Proposal { pub options: Vec, pub ballots: Vec, // This will store actual Ballot structs pub private_group: Option>, // Optional list of eligible user IDs + + pub tags: Vec, + pub comments: Vec, + pub attached_files: Vec, + pub urgency_score: Option, +} + +impl Default for Proposal { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + creator_id: "".to_string(), + creator_name: String::new(), // Added missing field + title: "".to_string(), + description: "".to_string(), + status: ProposalStatus::Draft, + // created_at and updated_at are now in base_data + vote_start_date: Utc::now(), + vote_end_date: Utc::now(), + vote_status: VoteEventStatus::Upcoming, + options: vec![], + ballots: vec![], + private_group: None, + tags: vec![], + comments: vec![], + attached_files: vec![], + urgency_score: None, + } + } } impl Proposal { @@ -142,10 +170,8 @@ impl Proposal { title: impl ToString, description: impl ToString, status: ProposalStatus, - created_at: DateTime, - updated_at: DateTime, - vote_start_date: DateTime, - vote_end_date: DateTime, + tags: Vec, + urgency_score: Option, ) -> Self { let mut base_data = BaseModelData::new(); if let Some(id) = id { @@ -159,14 +185,16 @@ impl Proposal { title: title.to_string(), description: description.to_string(), status, - created_at, - updated_at, - vote_start_date, - vote_end_date, - vote_status: VoteEventStatus::Open, // Default to open when created + vote_start_date: Utc::now(), + vote_end_date: Utc::now(), + vote_status: VoteEventStatus::Upcoming, options: Vec::new(), ballots: Vec::new(), private_group: None, + tags, + comments: Vec::new(), + attached_files: Vec::new(), + urgency_score, } } diff --git a/heromodels/src/models/legal/contract.rs b/heromodels/src/models/legal/contract.rs index 4e871fc..f6cdd79 100644 --- a/heromodels/src/models/legal/contract.rs +++ b/heromodels/src/models/legal/contract.rs @@ -51,7 +51,7 @@ impl fmt::Display for SignerStatus { // --- Structs for nested data --- /// ContractRevision represents a version of the contract content -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct ContractRevision { pub version: u32, pub content: String, @@ -78,7 +78,7 @@ impl ContractRevision { } /// ContractSigner represents a party involved in signing a contract -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct ContractSigner { pub id: String, // Unique ID for the signer (UUID string) pub name: String, @@ -129,7 +129,7 @@ impl ContractSigner { // --- Main Contract Model --- /// Represents a legal agreement -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[model] pub struct Contract { pub base_data: BaseModelData, // Provides id (u32), created_at (u64), updated_at (u64) diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 5e37df7..b4c8db2 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -15,7 +15,7 @@ pub use core::Comment; pub use userexample::User; // pub use productexample::Product; // Temporarily remove pub use calendar::{Calendar, Event, Attendee, AttendanceStatus}; -pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption}; +pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile}; pub use finance::{Account, Asset, AssetType}; pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; @@ -23,6 +23,7 @@ pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use biz::{Sale, SaleItem, SaleStatus}; pub use flow::register_flow_rhai_module; +#[cfg(feature = "rhai")] pub use calendar::register_calendar_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] diff --git a/heromodels/src/models/projects/epic.rs b/heromodels/src/models/projects/epic.rs new file mode 100644 index 0000000..d96b4d2 --- /dev/null +++ b/heromodels/src/models/projects/epic.rs @@ -0,0 +1,83 @@ +// heromodels/src/models/projects/epic.rs + +use chrono::{DateTime, Utc}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use rhai::{CustomType, TypeBuilder}; + +use super::base::Status as ProjectStatus; // Using the generic project status for now + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[model] +pub struct Epic { + pub base_data: BaseModelData, + pub name: String, + pub description: Option, + pub status: ProjectStatus, // Or a new EpicStatus enum if needed + + pub project_id: Option, // Link to a general project/board + + pub start_date: Option>, + pub due_date: Option>, + + pub tags: Vec, + + // Explicitly list task IDs belonging to this epic + // This helps in querying and avoids relying solely on tasks pointing to the epic. + pub child_task_ids: Vec, +} + +impl Epic { + pub fn new( + name: String, + description: Option, + status: ProjectStatus, + project_id: Option, + start_date: Option>, + due_date: Option>, + tags: Vec, + ) -> Self { + Self { + base_data: BaseModelData::new(), + name, + description, + status, + project_id, + start_date, + due_date, + tags, + child_task_ids: Vec::new(), // Initialize as empty + } + } + + // Method to add a task to this epic + pub fn add_task_id(mut self, task_id: u32) -> Self { + if !self.child_task_ids.contains(&task_id) { + self.child_task_ids.push(task_id); + } + self + } + + // Method to remove a task from this epic + pub fn remove_task_id(mut self, task_id: u32) -> Self { + self.child_task_ids.retain(|&id| id != task_id); + self + } +} + +impl Default for Epic { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + name: String::new(), + description: None, + status: ProjectStatus::default(), + project_id: None, + start_date: None, + due_date: None, + tags: Vec::new(), + child_task_ids: Vec::new(), + } + } +} diff --git a/heromodels/src/models/projects/mod.rs b/heromodels/src/models/projects/mod.rs index 10dc342..ca20fb8 100644 --- a/heromodels/src/models/projects/mod.rs +++ b/heromodels/src/models/projects/mod.rs @@ -1,6 +1,11 @@ // heromodels/src/models/projects/mod.rs pub mod base; +pub mod task_enums; +pub mod task; +pub mod epic; +pub mod sprint_enums; +pub mod sprint; // pub mod epic; // pub mod issue; // pub mod kanban; @@ -8,6 +13,11 @@ pub mod base; // pub mod story; pub use base::*; +pub use task_enums::*; +pub use task::*; +pub use epic::*; +pub use sprint_enums::*; +pub use sprint::*; // pub use epic::*; // pub use issue::*; // pub use kanban::*; diff --git a/heromodels/src/models/projects/sprint.rs b/heromodels/src/models/projects/sprint.rs new file mode 100644 index 0000000..883857f --- /dev/null +++ b/heromodels/src/models/projects/sprint.rs @@ -0,0 +1,81 @@ +// heromodels/src/models/projects/sprint.rs + +use chrono::{DateTime, Utc}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use rhai::{CustomType, TypeBuilder}; + +use super::sprint_enums::SprintStatus; // Import our new enum + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[model] +pub struct Sprint { + pub base_data: BaseModelData, + pub name: String, + pub description: Option, + pub status: SprintStatus, + pub goal: Option, // Sprint goal + + pub project_id: Option, // Link to a general project/board + + pub start_date: Option>, + pub end_date: Option>, // Changed from due_date for sprints + + // Explicitly list task IDs belonging to this sprint + pub task_ids: Vec, +} + +impl Sprint { + pub fn new( + name: String, + description: Option, + status: SprintStatus, + goal: Option, + project_id: Option, + start_date: Option>, + end_date: Option>, + ) -> Self { + Self { + base_data: BaseModelData::new(), + name, + description, + status, + goal, + project_id, + start_date, + end_date, + task_ids: Vec::new(), // Initialize as empty + } + } + + // Method to add a task to this sprint + pub fn add_task_id(mut self, task_id: u32) -> Self { + if !self.task_ids.contains(&task_id) { + self.task_ids.push(task_id); + } + self + } + + // Method to remove a task from this sprint + pub fn remove_task_id(mut self, task_id: u32) -> Self { + self.task_ids.retain(|&id| id != task_id); + self + } +} + +impl Default for Sprint { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + name: String::new(), + description: None, + status: SprintStatus::default(), + goal: None, + project_id: None, + start_date: None, + end_date: None, + task_ids: Vec::new(), + } + } +} diff --git a/heromodels/src/models/projects/sprint_enums.rs b/heromodels/src/models/projects/sprint_enums.rs new file mode 100644 index 0000000..c723752 --- /dev/null +++ b/heromodels/src/models/projects/sprint_enums.rs @@ -0,0 +1,16 @@ +// heromodels/src/models/projects/sprint_enums.rs +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum SprintStatus { + Planning, + Active, + Completed, + Paused, +} + +impl Default for SprintStatus { + fn default() -> Self { + SprintStatus::Planning + } +} diff --git a/heromodels/src/models/projects/task.rs b/heromodels/src/models/projects/task.rs new file mode 100644 index 0000000..4d008dc --- /dev/null +++ b/heromodels/src/models/projects/task.rs @@ -0,0 +1,93 @@ +// heromodels/src/models/projects/task.rs + +use chrono::{DateTime, Utc}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use rhai::{CustomType, TypeBuilder}; // Assuming rhai might be used + +use super::task_enums::{TaskStatus, TaskPriority}; // Import our new enums + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[model] // This will provide id, created_at, updated_at via base_data +pub struct Task { + pub base_data: BaseModelData, + pub title: String, + pub description: Option, + pub status: TaskStatus, + pub priority: TaskPriority, + + pub assignee_id: Option, // User ID + pub reporter_id: Option, // User ID + + pub parent_task_id: Option, // For subtasks + pub epic_id: Option, + pub sprint_id: Option, + pub project_id: Option, // Link to a general project/board if applicable + + pub due_date: Option>, + pub estimated_time_hours: Option, + pub logged_time_hours: Option, + pub tags: Vec, +} + +impl Task { + #[allow(clippy::too_many_arguments)] + pub fn new( + title: String, + description: Option, + status: TaskStatus, + priority: TaskPriority, + assignee_id: Option, + reporter_id: Option, + parent_task_id: Option, + epic_id: Option, + sprint_id: Option, + project_id: Option, + due_date: Option>, + estimated_time_hours: Option, + logged_time_hours: Option, + tags: Vec, + ) -> Self { + Self { + base_data: BaseModelData::new(), + title, + description, + status, + priority, + assignee_id, + reporter_id, + parent_task_id, + epic_id, + sprint_id, + project_id, + due_date, + estimated_time_hours, + logged_time_hours, + tags, + } + } +} + +// Add Default implementation +impl Default for Task { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + status: TaskStatus::default(), + priority: TaskPriority::default(), + assignee_id: None, + reporter_id: None, + parent_task_id: None, + epic_id: None, + sprint_id: None, + project_id: None, + due_date: None, + estimated_time_hours: None, + logged_time_hours: None, + tags: Vec::new(), + } + } +} diff --git a/heromodels/src/models/projects/task_enums.rs b/heromodels/src/models/projects/task_enums.rs new file mode 100644 index 0000000..811e19d --- /dev/null +++ b/heromodels/src/models/projects/task_enums.rs @@ -0,0 +1,32 @@ +// heromodels/src/models/projects/task_enums.rs +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TaskStatus { + ToDo, + InProgress, + InReview, + Done, + Blocked, + Backlog, +} + +impl Default for TaskStatus { + fn default() -> Self { + TaskStatus::ToDo + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum TaskPriority { + Low, + Medium, + High, + Urgent, +} + +impl Default for TaskPriority { + fn default() -> Self { + TaskPriority::Medium + } +} diff --git a/heromodels/src/models/userexample/user.rs b/heromodels/src/models/userexample/user.rs index c9f68f0..76d0598 100644 --- a/heromodels/src/models/userexample/user.rs +++ b/heromodels/src/models/userexample/user.rs @@ -3,7 +3,7 @@ use heromodels_derive::model; use serde::{Deserialize, Serialize}; /// Represents a user in the system -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[model] pub struct User { /// Base model data diff --git a/heromodels/tmp/event_test_db/index/0.db b/heromodels/tmp/event_test_db/index/0.db new file mode 100644 index 0000000000000000000000000000000000000000..0ad682e3dd2bf565474087acb8a9ae3a4040d169 GIT binary patch literal 35 VcmZP&V^FxtxSs(G7$FoI2>>>+0nGpa literal 0 HcmV?d00001 diff --git a/heromodels/tmp/event_test_db/index/lookup/.inc b/heromodels/tmp/event_test_db/index/lookup/.inc new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/heromodels/tmp/event_test_db/index/lookup/.inc @@ -0,0 +1 @@ +2 \ No newline at end of file From c3f6b91aa09c0d481cea84ab5d03da9c0f08cd51 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Tue, 3 Jun 2025 21:51:21 +0300 Subject: [PATCH 2/9] final db models wip --- .../adapter_macros}/Cargo.lock | 0 .../adapter_macros}/Cargo.toml | 0 .../adapter_macros}/README.md | 0 .../adapter_macros}/src/lib.rs | 0 heromodels_core/src/base_data_builder.rs | 10 - heromodels_core/src/lib.rs | 2 +- prompts/new_rhai_rs_gen.md | 151 +++++++ rhai_client_example/Cargo.lock | 298 +++++++++++++ rhai_client_example/Cargo.toml | 8 + rhai_client_example/src/main.rs | 62 +++ rust-toolchain.toml | 3 + sigsocket_architecture.md | 401 ++++++++++++++++++ websocket/architecture.md | 327 ++++++++++++++ 13 files changed, 1251 insertions(+), 11 deletions(-) rename {adapter_macros => _archive/adapter_macros}/Cargo.lock (100%) rename {adapter_macros => _archive/adapter_macros}/Cargo.toml (100%) rename {adapter_macros => _archive/adapter_macros}/README.md (100%) rename {adapter_macros => _archive/adapter_macros}/src/lib.rs (100%) create mode 100644 prompts/new_rhai_rs_gen.md create mode 100644 rhai_client_example/Cargo.lock create mode 100644 rhai_client_example/Cargo.toml create mode 100644 rhai_client_example/src/main.rs create mode 100644 rust-toolchain.toml create mode 100644 sigsocket_architecture.md create mode 100644 websocket/architecture.md diff --git a/adapter_macros/Cargo.lock b/_archive/adapter_macros/Cargo.lock similarity index 100% rename from adapter_macros/Cargo.lock rename to _archive/adapter_macros/Cargo.lock diff --git a/adapter_macros/Cargo.toml b/_archive/adapter_macros/Cargo.toml similarity index 100% rename from adapter_macros/Cargo.toml rename to _archive/adapter_macros/Cargo.toml diff --git a/adapter_macros/README.md b/_archive/adapter_macros/README.md similarity index 100% rename from adapter_macros/README.md rename to _archive/adapter_macros/README.md diff --git a/adapter_macros/src/lib.rs b/_archive/adapter_macros/src/lib.rs similarity index 100% rename from adapter_macros/src/lib.rs rename to _archive/adapter_macros/src/lib.rs diff --git a/heromodels_core/src/base_data_builder.rs b/heromodels_core/src/base_data_builder.rs index 6055b05..2345167 100644 --- a/heromodels_core/src/base_data_builder.rs +++ b/heromodels_core/src/base_data_builder.rs @@ -8,16 +8,6 @@ pub trait BaseModelDataOps: Sized { self } - fn set_base_created_at(mut self, time: i64) -> Self { - self.get_base_data_mut().created_at = time; - self - } - - fn set_base_modified_at(mut self, time: i64) -> Self { - self.get_base_data_mut().modified_at = time; - self - } - fn add_base_comment(mut self, comment_id: u32) -> Self { self.get_base_data_mut().comments.push(comment_id); self diff --git a/heromodels_core/src/lib.rs b/heromodels_core/src/lib.rs index cd96948..00e54b6 100644 --- a/heromodels_core/src/lib.rs +++ b/heromodels_core/src/lib.rs @@ -118,7 +118,7 @@ pub trait Index { /// // Retrieve the model with the assigned ID /// let db_user = db.collection().get_by_id(user_id).expect("Failed to get user"); /// ``` -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] pub struct BaseModelData { /// Unique incremental ID - will be auto-generated by OurDB /// diff --git a/prompts/new_rhai_rs_gen.md b/prompts/new_rhai_rs_gen.md new file mode 100644 index 0000000..c46dcdd --- /dev/null +++ b/prompts/new_rhai_rs_gen.md @@ -0,0 +1,151 @@ +# AI Prompt: Generate Rhai Module Integration for Rust Models + +## Context +You are tasked with creating a Rhai scripting integration for Rust models in the heromodels crate. The integration should follow the builder pattern and provide a comprehensive set of functions for interacting with the models through Rhai scripts. + +## Key Concepts to Understand +1. **Builder Pattern vs. Rhai Mutation**: Rust models use builder pattern (methods consume self and return Self), but Rhai requires functions that mutate in place (&mut self). +2. **Database Access**: Database functions are exposed as global functions in a 'db' module namespace (e.g., `db::save_flow`, not `db.save_flow`). +3. **ID Handling**: New objects should use ID 0 to allow database assignment of real IDs. +4. **Function vs. Method Calls**: Rhai scripts must use function calls (e.g., `status(flow, STATUS_DRAFT)`) not method chaining (e.g., `flow.status(STATUS_DRAFT)`). + +## Requirements + +1. **Module Structure and Imports**: + - Create a `rhai.rs` file that exports a function named `register_[model]_rhai_module` which takes an Engine and an Arc + - **IMPORTANT**: Import `heromodels_core::Model` trait for access to methods like `get_id` + - Use the `#[export_module]` macro to define the Rhai module + - Register the module globally with the engine using the correct module registration syntax + - Create separate database functions that capture the database reference + +2. **Builder Pattern Integration**: + - For each model struct marked with `#[model]`, create constructor functions (e.g., `new_[model]`) that accept ID 0 for new objects + - For each builder method in the model, create a corresponding Rhai function that: + - Takes a mutable reference to the model as first parameter + - Uses `std::mem::take` to get ownership (if the model implements Default) or `std::mem::replace` (if it doesn't) + - Calls the builder method on the owned object + - Assigns the result back to the mutable reference + - Returns a clone of the modified object + +3. **Field Access**: + - **IMPORTANT**: Do NOT create getter methods for fields that can be accessed directly + - Use direct field access in example code (e.g., `flow.name`, not `flow.get_name()`) + - Only create getter functions for computed properties or when special conversion is needed + - Handle special types appropriately (e.g., convert timestamps, IDs, etc.) + +4. **Database Functions**: + - For each model, implement the following database functions in the `db` module namespace: + - `db::save_[model]`: Save the model to the database and return the saved model with assigned ID + - `db::get_[model]_by_id`: Retrieve a model by ID + - `db::delete_[model]`: Delete a model from the database + - `db::list_[model]s`: List all models of this type + - **IMPORTANT**: Use `db::function_name` syntax in scripts, not `db.function_name` + - Handle errors properly with detailed error messages using debug format `{:?}` for error objects + - Convert between Rhai's i64 and Rust's u32/u64 types for IDs and timestamps + - Return saved objects from save functions to ensure proper ID handling + +5. **Error Handling**: + - Use `Box` for error returns + - Provide detailed error messages with proper position information + - **IMPORTANT**: Use `context.call_position()` not the deprecated `context.position()` + - Handle type conversions safely + - Format error messages with debug format for error objects: `format!("Error: {:?}", err)` + +6. **Type Conversions**: + - Create helper functions for converting between Rhai and Rust types + - Handle special cases like enums with string representations + +## Implementation Pattern + +```rust +// Helper functions for type conversion +fn i64_to_u32(val: i64, position: Position, param_name: &str, function_name: &str) -> Result> { + u32::try_from(val).map_err(|_| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("{} must be a positive integer less than 2^32 in {}", param_name, function_name).into(), + position + )) + }) +} + +// Model constructor +#[rhai_fn(name = "new_[model]")] +pub fn new_model() -> Model { + Model::new() +} + +// Builder method integration +#[rhai_fn(name = "[method_name]", return_raw, global, pure)] +pub fn model_method(model: &mut Model, param: Type) -> Result> { + let owned_model = std::mem::take(model); // or replace if Default not implemented + *model = owned_model.method_name(param); + Ok(model.clone()) +} + +// Getter method +#[rhai_fn(name = "[field]", global)] +pub fn get_model_field(model: &mut Model) -> Type { + model.field.clone() +} + +// Database function +db_module.set_native_fn("save_[model]", move |model: Model| -> Result> { + db_clone.set(&model) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {}", e).into(), Position::NONE))) + .map(|(_, model)| model) +}); +``` + +## Important Notes + +1. All models must follow the builder pattern where methods take ownership of `self` and return `Self` +2. If a model doesn't implement `Default`, use `std::mem::replace` instead of `std::mem::take` +3. Ensure proper error handling for all operations +4. Register all functions with appropriate Rhai names +5. Follow the naming conventions established in existing modules +6. Ensure all database operations are properly wrapped with error handling +7. **CRITICAL**: When creating example scripts, use function calls not method chaining +8. **CRITICAL**: Always use ID 0 for new objects in scripts to allow database assignment +9. **CRITICAL**: After saving objects, use the returned objects with assigned IDs for further operations +10. **CRITICAL**: In Rust examples, include the Model trait import and use direct field access + +## Example Rhai Script + +```rhai +// Create a new flow with ID 0 (database will assign real ID) +let flow = new_flow(0, "12345678-1234-1234-1234-123456789012"); +name(flow, "Document Approval Flow"); +status(flow, STATUS_DRAFT); + +// Save flow to database and get saved object with assigned ID +let saved_flow = db::save_flow(flow); +print(`Flow saved to database with ID: ${get_flow_id(saved_flow)}`); + +// Retrieve flow from database using assigned ID +let retrieved_flow = db::get_flow_by_id(get_flow_id(saved_flow)); +``` + +## Example Rust Code + +```rust +use heromodels_core::Model; // Import Model trait for get_id() + +// Create flow using Rhai +let flow: Flow = engine.eval("new_flow(0, \"12345678-1234-1234-1234-123456789012\")").unwrap(); + +// Access fields directly +println!("Flow name: {}", flow.name); + +// Save to database using Rhai function +let save_script = "fn save_it(f) { return db::save_flow(f); }"; +let save_ast = engine.compile(save_script).unwrap(); +let saved_flow: Flow = engine.call_fn(&mut scope, &save_ast, "save_it", (flow,)).unwrap(); +println!("Saved flow ID: {}", saved_flow.get_id()); +``` + +## Reference Implementations +See the existing implementations for examples: +1. `heromodels/src/models/calendar/rhai.rs` - Calendar module Rhai bindings +2. `heromodels/src/models/flow/rhai.rs` - Flow module Rhai bindings +3. `heromodels_rhai/examples/flow/flow_script.rhai` - Example Rhai script +4. `heromodels_rhai/examples/flow/example.rs` - Example Rust code \ No newline at end of file diff --git a/rhai_client_example/Cargo.lock b/rhai_client_example/Cargo.lock new file mode 100644 index 0000000..571ea9e --- /dev/null +++ b/rhai_client_example/Cargo.lock @@ -0,0 +1,298 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rhai" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" +dependencies = [ + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_client_example" +version = "0.1.0" +dependencies = [ + "rhai", + "rhai_client_macros", +] + +[[package]] +name = "rhai_client_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rhai", + "syn", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/rhai_client_example/Cargo.toml b/rhai_client_example/Cargo.toml new file mode 100644 index 0000000..5fea471 --- /dev/null +++ b/rhai_client_example/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rhai_client_example" +version = "0.1.0" +edition = "2021" + +[dependencies] +rhai = "1.21.0" +rhai_client_macros = { path = "../rhai_client_macros" } diff --git a/rhai_client_example/src/main.rs b/rhai_client_example/src/main.rs new file mode 100644 index 0000000..87c3092 --- /dev/null +++ b/rhai_client_example/src/main.rs @@ -0,0 +1,62 @@ +use rhai::Engine; +use rhai_client_macros::rhai; + +// Define a Rust function with the #[rhai] attribute +#[rhai] +fn hello(name: String) -> String { + format!("Hello, {}!", name) +} + +// Define another function with multiple parameters +#[rhai] +fn add(a: i32, b: i32) -> i32 { + a + b +} + +// Define a function with different parameter types +#[rhai] +fn format_person(name: String, age: i32, is_student: bool) -> String { + let student_status = if is_student { "is" } else { "is not" }; + format!("{} is {} years old and {} a student.", name, age, student_status) +} + +// Define adapter functions for Rhai that handle i64 to i32 conversion +fn add_rhai(a: i64, b: i64) -> i64 { + add(a as i32, b as i32) as i64 +} + +fn format_person_rhai(name: String, age: i64, is_student: bool) -> String { + format_person(name, age as i32, is_student) +} + +fn main() { + // Create a Rhai engine + let mut engine = Engine::new(); + + // Register our functions with the Rhai engine + // Note: Rhai uses i64 for integers by default + engine.register_fn("hello", hello); + engine.register_fn("add", add_rhai); + engine.register_fn("format_person", format_person_rhai); + + println!("=== Calling Rust functions directly ==="); + println!("{}", hello("World".to_string())); + println!("{}", add(5, 10)); + println!("{}", format_person("Alice".to_string(), 25, true)); + + println!("\n=== Calling functions through Rhai engine ==="); + let result1: String = engine.eval("hello(\"Rhai World\")").unwrap(); + println!("{}", result1); + + let result2: i64 = engine.eval("add(20, 30)").unwrap(); + println!("{}", result2); + + let result3: String = engine.eval("format_person(\"Bob\", 30, false)").unwrap(); + println!("{}", result3); + + println!("\n=== Calling functions through generated Rhai client functions ==="); + // Use the generated client functions + println!("{}", hello_rhai_client(&engine, "Client World".to_string())); + println!("{}", add_rhai_client(&engine, 100, 200)); + println!("{}", format_person_rhai_client(&engine, "Charlie".to_string(), 35, true)); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..8e275b7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "clippy"] diff --git a/sigsocket_architecture.md b/sigsocket_architecture.md new file mode 100644 index 0000000..00a9f3d --- /dev/null +++ b/sigsocket_architecture.md @@ -0,0 +1,401 @@ +# SigSocket: WebSocket Signing Server Architecture + +Based on my analysis of the existing Actix application structure, I've designed a comprehensive architecture for implementing a WebSocket server that handles signing operations. This server will integrate seamlessly with the existing hostbasket application. + +## 1. Overview + +SigSocket will: +- Accept WebSocket connections from clients +- Allow clients to identify themselves with a public key +- Provide a `send_to_sign()` function that takes a public key and a message +- Forward the message to the appropriate client for signing +- Wait for a signed response (with a 1-minute timeout) +- Verify the signature using the client's public key +- Return the response message and signature + +## 2. Component Architecture + +```mermaid +graph TD + A[Actix Web Server] --> B[SigSocket Manager] + B --> C[Connection Registry] + B --> D[Message Handler] + D --> E[Signature Verifier] + F[Client] <--> B + G[Application Code] --> H[SigSocketService] + H --> B +``` + +### Key Components: + +1. **SigSocket Manager** + - Handles WebSocket connections + - Manages connection lifecycle + - Routes messages to appropriate handlers + +2. **Connection Registry** + - Maps public keys to active WebSocket connections + - Handles connection tracking and cleanup + - Provides lookup functionality + +3. **Message Handler** + - Processes incoming messages + - Implements the message protocol + - Manages timeouts for responses + +4. **Signature Verifier** + - Verifies signatures using public keys + - Implements cryptographic operations + - Ensures security of the signing process + +5. **SigSocket Service** + - Provides a clean API for the application to use + - Abstracts WebSocket complexity from business logic + - Handles error cases and timeouts + +## 3. Directory Structure + +``` +src/ +├── lib.rs # Main library exports +├── manager.rs # WebSocket connection manager +├── registry.rs # Connection registry +├── handler.rs # Message handling logic +├── protocol.rs # Message protocol definitions +├── crypto.rs # Cryptographic operations +└── service.rs # Service API +``` + +## 4. Data Flow + +```mermaid +sequenceDiagram + participant Client + participant SigSocketManager + participant Registry + participant Application + participant SigSocketService + + Client->>SigSocketManager: Connect + Client->>SigSocketManager: Introduce(public_key) + SigSocketManager->>Registry: Register(connection, public_key) + + Application->>SigSocketService: send_to_sign(public_key, message) + SigSocketService->>Registry: Lookup(public_key) + Registry-->>SigSocketService: connection + SigSocketService->>SigSocketManager: Send message to connection + SigSocketManager->>Client: Message to sign + + Client->>SigSocketManager: Signed response + SigSocketManager->>SigSocketService: Forward response + SigSocketService->>SigSocketService: Verify signature + SigSocketService-->>Application: Return verified response +``` + +## 5. Message Protocol + +We'll define a minimalist protocol for communication: + +``` +// Client introduction (first message upon connection) + + +// Sign request (sent from server to client) + + +// Sign response (sent from client to server) +. +``` + +This simplified protocol reduces overhead and complexity: +- The introduction is always the first message, containing only the public key +- Sign requests contain only the message to be signed +- Responses use a simple "message.signature" format + +## 6. Required Dependencies + +We'll need to add the following dependencies to the project: + +```toml +# WebSocket support +actix-web-actors = "4.2.0" + +# Cryptography +secp256k1 = "0.28.0" # For secp256k1 signatures (used in Bitcoin/Ethereum) +sha2 = "0.10.8" # For hashing before signing +hex = "0.4.3" # For hex encoding/decoding +base64 = "0.21.0" # For base64 encoding/decoding +rand = "0.8.5" # For generating random data +``` + +## 7. Implementation Details + +### 7.1 SigSocket Manager + +The SigSocket Manager will handle the lifecycle of WebSocket connections: + +```rust +pub struct SigSocketManager { + registry: Arc>, +} + +impl Actor for SigSocketManager { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + // Handle connection start + } + + fn stopped(&mut self, ctx: &mut Self::Context) { + // Handle connection close and cleanup + } +} + +impl StreamHandler> for SigSocketManager { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + // Handle different types of WebSocket messages + } +} +``` + +### 7.2 Connection Registry + +The Connection Registry will maintain a mapping of public keys to active connections: + +```rust +pub struct ConnectionRegistry { + connections: HashMap>, +} + +impl ConnectionRegistry { + pub fn register(&mut self, public_key: String, addr: Addr) { + self.connections.insert(public_key, addr); + } + + pub fn unregister(&mut self, public_key: &str) { + self.connections.remove(public_key); + } + + pub fn get(&self, public_key: &str) -> Option<&Addr> { + self.connections.get(public_key) + } +} +``` + +### 7.3 SigSocket Service + +The SigSocket Service will provide a clean API for controllers: + +```rust +pub struct SigSocketService { + registry: Arc>, +} + +impl SigSocketService { + pub async fn send_to_sign(&self, public_key: &str, message: &[u8]) + -> Result<(Vec, Vec), SigSocketError> { + + // 1. Find the connection for the public key + let connection = self.registry.read().await.get(public_key).cloned(); + + if let Some(conn) = connection { + // 2. Create a response channel + let (tx, rx) = oneshot::channel(); + + // 3. Register the response channel with the connection + self.pending_requests.write().await.insert(conn.clone(), tx); + + // 4. Send the message to the client (just the raw message) + conn.do_send(base64::encode(message)); + + // 5. Wait for the response with a timeout + match tokio::time::timeout(Duration::from_secs(60), rx).await { + Ok(Ok(response)) => { + // 6. Parse the response in format "message.signature" + let parts: Vec<&str> = response.split('.').collect(); + if parts.len() != 2 { + return Err(SigSocketError::InvalidResponseFormat); + } + + let message_b64 = parts[0]; + let signature_b64 = parts[1]; + + // 7. Decode the message and signature + let message_bytes = match base64::decode(message_b64) { + Ok(m) => m, + Err(_) => return Err(SigSocketError::DecodingError), + }; + + let signature_bytes = match base64::decode(signature_b64) { + Ok(s) => s, + Err(_) => return Err(SigSocketError::DecodingError), + }; + + // 8. Verify the signature + if self.verify_signature(&signature_bytes, &message_bytes, public_key) { + Ok((message_bytes, signature_bytes)) + } else { + Err(SigSocketError::InvalidSignature) + } + }, + Ok(Err(_)) => Err(SigSocketError::ChannelClosed), + Err(_) => Err(SigSocketError::Timeout), + } + } else { + Err(SigSocketError::ConnectionNotFound) + } + } +} +``` + +## 8. Error Handling + +We'll define a comprehensive error type for the SigSocket service: + +```rust +#[derive(Debug, thiserror::Error)] +pub enum SigSocketError { + #[error("Connection not found for the provided public key")] + ConnectionNotFound, + + #[error("Timeout waiting for signature")] + Timeout, + + #[error("Invalid signature")] + InvalidSignature, + + #[error("Channel closed unexpectedly")] + ChannelClosed, + + #[error("Invalid response format, expected 'message.signature'")] + InvalidResponseFormat, + + #[error("Error decoding base64 message or signature")] + DecodingError, + + #[error("Invalid public key format")] + InvalidPublicKey, + + #[error("Invalid signature format")] + InvalidSignature, + + #[error("Internal cryptographic error")] + InternalError, + + #[error("WebSocket error: {0}")] + WebSocketError(#[from] ws::ProtocolError), + + #[error("Base64 decoding error: {0}")] + Base64Error(#[from] base64::DecodeError), + + #[error("Hex decoding error: {0}")] + HexError(#[from] hex::FromHexError), +} +``` + +## 9. Cryptographic Operations + +The SigSocket service uses the secp256k1 elliptic curve for cryptographic operations, which is the same curve used in Bitcoin and Ethereum. This makes it compatible with many blockchain applications and wallets. + +```rust +use secp256k1::{Secp256k1, Message, PublicKey, Signature}; +use sha2::{Sha256, Digest}; + +pub fn verify_signature(public_key_hex: &str, message: &[u8], signature_hex: &str) -> Result { + // 1. Parse the public key + let public_key_bytes = hex::decode(public_key_hex) + .map_err(|_| SigSocketError::InvalidPublicKey)?; + + let public_key = PublicKey::from_slice(&public_key_bytes) + .map_err(|_| SigSocketError::InvalidPublicKey)?; + + // 2. Parse the signature + let signature_bytes = hex::decode(signature_hex) + .map_err(|_| SigSocketError::InvalidSignature)?; + + let signature = Signature::from_compact(&signature_bytes) + .map_err(|_| SigSocketError::InvalidSignature)?; + + // 3. Hash the message (secp256k1 requires a 32-byte hash) + let mut hasher = Sha256::new(); + hasher.update(message); + let message_hash = hasher.finalize(); + + // 4. Create a secp256k1 message from the hash + let secp_message = Message::from_slice(&message_hash) + .map_err(|_| SigSocketError::InternalError)?; + + // 5. Verify the signature + let secp = Secp256k1::verification_only(); + match secp.verify(&secp_message, &signature, &public_key) { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } +} +``` + +This implementation: +1. Decodes the hex-encoded public key and signature +2. Hashes the message using SHA-256 (required for secp256k1) +3. Verifies the signature using the secp256k1 library +4. Returns a boolean indicating whether the signature is valid + +## 10. Security Considerations + +1. **Public Key Validation**: Validate public keys upon connection to ensure they are properly formatted secp256k1 keys +2. **Message Authentication**: Consider adding a nonce or timestamp to prevent replay attacks +3. **Rate Limiting**: Implement rate limiting to prevent DoS attacks +4. **Connection Timeouts**: Automatically close inactive connections +5. **Error Logging**: Log errors but avoid exposing sensitive information +6. **Input Validation**: Validate all inputs to prevent injection attacks +7. **Secure Hashing**: Always hash messages before signing to prevent length extension attacks + +## 11. Testing Strategy + +1. **Unit Tests**: Test individual components in isolation +2. **Integration Tests**: Test the interaction between components +3. **End-to-End Tests**: Test the complete flow from client connection to signature verification +4. **Load Tests**: Test the system under high load to ensure stability +5. **Security Tests**: Test for common security vulnerabilities + +## 12. Integration with Existing Code + +The SigSocketService can be used by any part of the application that needs signing functionality: + +```rust +// Create and initialize the service +let registry = Arc::new(RwLock::new(ConnectionRegistry::new())); +let sigsocket_service = Arc::new(SigSocketService::new(registry.clone())); + +// Use the service to send a message for signing +async fn sign_message(service: Arc, public_key: String, message: Vec) -> Result<(Vec, Vec), SigSocketError> { + service.send_to_sign(&public_key, &message).await +} + +// Example usage in an HTTP handler +async fn handle_sign_request( + service: web::Data>, + req: web::Json, +) -> HttpResponse { + match service.send_to_sign(&req.public_key, &req.message).await { + Ok((response, signature)) => { + HttpResponse::Ok().json(json!({ + "response": base64::encode(response), + "signature": base64::encode(signature), + })) + }, + Err(e) => { + HttpResponse::InternalServerError().json(json!({ + "error": e.to_string(), + })) + } + } +} +``` + +## 13. Deployment Considerations + +1. **Scalability**: The SigSocket server should be designed to scale horizontally +2. **Monitoring**: Implement metrics for connection count, message throughput, and error rates +3. **Logging**: Log important events for debugging and auditing +4. **Documentation**: Document the API and protocol for client implementers \ No newline at end of file diff --git a/websocket/architecture.md b/websocket/architecture.md new file mode 100644 index 0000000..f8eb016 --- /dev/null +++ b/websocket/architecture.md @@ -0,0 +1,327 @@ +# WebSocket Signing Server Architecture Plan + +Based on my analysis of the existing Actix application structure, I've designed a comprehensive architecture for implementing a WebSocket server that handles signing operations. This server will integrate seamlessly with the existing hostbasket application. + +## 1. Overview + +The WebSocket Signing Server will: +- Accept WebSocket connections from clients +- Allow clients to identify themselves with a public key +- Provide a `send_to_sign()` function that takes a public key and a message +- Forward the message to the appropriate client for signing +- Wait for a signed response (with a 1-minute timeout) +- Verify the signature using the client's public key +- Return the response message and signature + +## 2. Component Architecture + +```mermaid +graph TD + A[Actix Web Server] --> B[WebSocket Manager] + B --> C[Connection Registry] + B --> D[Message Handler] + D --> E[Signature Verifier] + F[Client] <--> B + G[Controllers] --> H[SigningService] + H --> B +``` + +### Key Components: + +1. **WebSocket Manager** + - Handles WebSocket connections + - Manages connection lifecycle + - Routes messages to appropriate handlers + +2. **Connection Registry** + - Maps public keys to active WebSocket connections + - Handles connection tracking and cleanup + - Provides lookup functionality + +3. **Message Handler** + - Processes incoming messages + - Implements the message protocol + - Manages timeouts for responses + +4. **Signature Verifier** + - Verifies signatures using public keys + - Implements cryptographic operations + - Ensures security of the signing process + +5. **Signing Service** + - Provides a clean API for controllers to use + - Abstracts WebSocket complexity from business logic + - Handles error cases and timeouts + +## 3. Directory Structure + +``` +src/ +├── websocket/ +│ ├── mod.rs # Module exports +│ ├── manager.rs # WebSocket connection manager +│ ├── registry.rs # Connection registry +│ ├── handler.rs # Message handling logic +│ ├── protocol.rs # Message protocol definitions +│ ├── crypto.rs # Cryptographic operations +│ └── service.rs # Service API for controllers +├── controllers/ +│ └── [existing controllers] +│ └── websocket.rs # WebSocket controller (if needed) +└── routes/ + └── mod.rs # Updated to include WebSocket routes +``` + +## 4. Data Flow + +```mermaid +sequenceDiagram + participant Client + participant WebSocketManager + participant Registry + participant Controller + participant SigningService + + Client->>WebSocketManager: Connect + Client->>WebSocketManager: Introduce(public_key) + WebSocketManager->>Registry: Register(connection, public_key) + + Controller->>SigningService: send_to_sign(public_key, message) + SigningService->>Registry: Lookup(public_key) + Registry-->>SigningService: connection + SigningService->>WebSocketManager: Send message to connection + WebSocketManager->>Client: Message to sign + + Client->>WebSocketManager: Signed response + WebSocketManager->>SigningService: Forward response + SigningService->>SigningService: Verify signature + SigningService-->>Controller: Return verified response +``` + +## 5. Message Protocol + +We'll define a simple JSON-based protocol for communication: + +```json +// Client introduction +{ + "type": "introduction", + "public_key": "base64_encoded_public_key" +} + +// Sign request +{ + "type": "sign_request", + "message": "base64_encoded_message", + "request_id": "unique_request_id" +} + +// Sign response +{ + "type": "sign_response", + "request_id": "unique_request_id", + "message": "base64_encoded_message", + "signature": "base64_encoded_signature" +} +``` + +## 6. Required Dependencies + +We'll need to add the following dependencies to the project: + +```toml +# WebSocket support +actix-web-actors = "4.2.0" + +# Cryptography +ed25519-dalek = "2.0.0" # For Ed25519 signatures +base64 = "0.21.0" # For encoding/decoding +rand = "0.8.5" # For generating random data +``` + +## 7. Implementation Details + +### 7.1 WebSocket Manager + +The WebSocket Manager will handle the lifecycle of WebSocket connections: + +```rust +pub struct WebSocketManager { + registry: Arc>, +} + +impl Actor for WebSocketManager { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + // Handle connection start + } + + fn stopped(&mut self, ctx: &mut Self::Context) { + // Handle connection close and cleanup + } +} + +impl StreamHandler> for WebSocketManager { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + // Handle different types of WebSocket messages + } +} +``` + +### 7.2 Connection Registry + +The Connection Registry will maintain a mapping of public keys to active connections: + +```rust +pub struct ConnectionRegistry { + connections: HashMap>, +} + +impl ConnectionRegistry { + pub fn register(&mut self, public_key: String, addr: Addr) { + self.connections.insert(public_key, addr); + } + + pub fn unregister(&mut self, public_key: &str) { + self.connections.remove(public_key); + } + + pub fn get(&self, public_key: &str) -> Option<&Addr> { + self.connections.get(public_key) + } +} +``` + +### 7.3 Signing Service + +The Signing Service will provide a clean API for controllers: + +```rust +pub struct SigningService { + registry: Arc>, +} + +impl SigningService { + pub async fn send_to_sign(&self, public_key: &str, message: &[u8]) + -> Result<(Vec, Vec), SigningError> { + + // 1. Find the connection for the public key + let connection = self.registry.read().await.get(public_key).cloned(); + + if let Some(conn) = connection { + // 2. Generate a unique request ID + let request_id = Uuid::new_v4().to_string(); + + // 3. Create a response channel + let (tx, rx) = oneshot::channel(); + + // 4. Register the response channel + self.pending_requests.write().await.insert(request_id.clone(), tx); + + // 5. Send the message to the client + let sign_request = SignRequest { + request_id: request_id.clone(), + message: message.to_vec(), + }; + + conn.do_send(sign_request); + + // 6. Wait for the response with a timeout + match tokio::time::timeout(Duration::from_secs(60), rx).await { + Ok(Ok(response)) => { + // 7. Verify the signature + if self.verify_signature(&response.signature, message, public_key) { + Ok((response.message, response.signature)) + } else { + Err(SigningError::InvalidSignature) + } + }, + Ok(Err(_)) => Err(SigningError::ChannelClosed), + Err(_) => Err(SigningError::Timeout), + } + } else { + Err(SigningError::ConnectionNotFound) + } + } +} +``` + +## 8. Error Handling + +We'll define a comprehensive error type for the signing service: + +```rust +#[derive(Debug, thiserror::Error)] +pub enum SigningError { + #[error("Connection not found for the provided public key")] + ConnectionNotFound, + + #[error("Timeout waiting for signature")] + Timeout, + + #[error("Invalid signature")] + InvalidSignature, + + #[error("Channel closed unexpectedly")] + ChannelClosed, + + #[error("WebSocket error: {0}")] + WebSocketError(#[from] ws::ProtocolError), + + #[error("Serialization error: {0}")] + SerializationError(#[from] serde_json::Error), +} +``` + +## 9. Security Considerations + +1. **Public Key Validation**: Validate public keys upon connection to ensure they are properly formatted +2. **Message Authentication**: Consider adding a nonce or timestamp to prevent replay attacks +3. **Rate Limiting**: Implement rate limiting to prevent DoS attacks +4. **Connection Timeouts**: Automatically close inactive connections +5. **Error Logging**: Log errors but avoid exposing sensitive information +6. **Input Validation**: Validate all inputs to prevent injection attacks + +## 10. Testing Strategy + +1. **Unit Tests**: Test individual components in isolation +2. **Integration Tests**: Test the interaction between components +3. **End-to-End Tests**: Test the complete flow from client connection to signature verification +4. **Load Tests**: Test the system under high load to ensure stability +5. **Security Tests**: Test for common security vulnerabilities + +## 11. Integration with Existing Controllers + +Controllers can use the SigningService through dependency injection: + +```rust +pub struct SomeController { + signing_service: Arc, +} + +impl SomeController { + pub async fn some_action(&self, public_key: String, message: Vec) -> HttpResponse { + match self.signing_service.send_to_sign(&public_key, &message).await { + Ok((response, signature)) => { + HttpResponse::Ok().json(json!({ + "response": base64::encode(response), + "signature": base64::encode(signature), + })) + }, + Err(e) => { + HttpResponse::InternalServerError().json(json!({ + "error": e.to_string(), + })) + } + } + } +} +``` + +## 12. Deployment Considerations + +1. **Scalability**: The WebSocket server should be designed to scale horizontally +2. **Monitoring**: Implement metrics for connection count, message throughput, and error rates +3. **Logging**: Log important events for debugging and auditing +4. **Documentation**: Document the API and protocol for client implementers \ No newline at end of file From 00c4e6a1eb88bd4c165da04e5d4a2a768699eb6a Mon Sep 17 00:00:00 2001 From: timurgordon Date: Thu, 12 Jun 2025 05:22:17 +0300 Subject: [PATCH 3/9] improve and add models --- heromodels/Cargo.toml | 5 + heromodels/examples/library_rhai/example.rs | 38 ++ heromodels/examples/library_rhai/library.rhai | 78 +++ heromodels/src/models/biz/company.rs | 2 +- heromodels/src/models/biz/product.rs | 2 - heromodels/src/models/biz/shareholder.rs | 2 +- heromodels/src/models/calendar/calendar.rs | 1 - heromodels/src/models/calendar/rhai.rs | 11 - heromodels/src/models/circle/circle.rs | 86 +++ heromodels/src/models/circle/mod.rs | 7 + heromodels/src/models/circle/rhai.rs | 201 ++++++ heromodels/src/models/finance/account.rs | 6 +- heromodels/src/models/finance/rhai.rs | 13 +- heromodels/src/models/flow/rhai.rs | 11 - heromodels/src/models/library/collection.rs | 91 +++ heromodels/src/models/library/items.rs | 369 +++++++++++ heromodels/src/models/library/mod.rs | 4 + heromodels/src/models/library/rhai.rs | 626 ++++++++++++++++++ heromodels/src/models/mod.rs | 9 + 19 files changed, 1520 insertions(+), 42 deletions(-) create mode 100644 heromodels/examples/library_rhai/example.rs create mode 100644 heromodels/examples/library_rhai/library.rhai create mode 100644 heromodels/src/models/circle/circle.rs create mode 100644 heromodels/src/models/circle/mod.rs create mode 100644 heromodels/src/models/circle/rhai.rs create mode 100644 heromodels/src/models/library/collection.rs create mode 100644 heromodels/src/models/library/items.rs create mode 100644 heromodels/src/models/library/mod.rs create mode 100644 heromodels/src/models/library/rhai.rs diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 392a73d..9b36157 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -81,3 +81,8 @@ required-features = ["rhai"] name = "biz_rhai" path = "examples/biz_rhai/example.rs" required-features = ["rhai"] + +[[example]] +name = "library_rhai" +path = "examples/library_rhai/example.rs" +required-features = ["rhai"] diff --git a/heromodels/examples/library_rhai/example.rs b/heromodels/examples/library_rhai/example.rs new file mode 100644 index 0000000..08b41e0 --- /dev/null +++ b/heromodels/examples/library_rhai/example.rs @@ -0,0 +1,38 @@ +use heromodels::db::hero::OurDB; +use heromodels::models::register_library_rhai_module; +use rhai::Engine; +use std::sync::Arc; +use std::{fs, path::Path}; + +fn main() -> Result<(), Box> { + // Initialize Rhai engine + let mut engine = Engine::new(); + + // Initialize database with OurDB + let db_path = "temp_library_db"; + // Clean up previous database file if it exists + if Path::new(db_path).exists() { + fs::remove_dir_all(db_path)?; + } + let db = Arc::new(OurDB::new(db_path, true).expect("Failed to create database")); + + // Register the library module with Rhai + register_library_rhai_module(&mut engine, db.clone()); + + // Load and evaluate the Rhai script + let script_path = Path::new(file!()).parent().unwrap().join("library.rhai"); + let script = fs::read_to_string(&script_path)?; + + println!("--- Running Library Rhai Script ---"); + match engine.eval::<()>(&script) { + Ok(_) => println!("\n--- Script executed successfully! ---"), + Err(e) => eprintln!("\n--- Script execution failed: {} ---", e), + } + + // Clean up the database file + fs::remove_dir_all(db_path)?; + println!("--- Cleaned up temporary database. ---"); + + + Ok(()) +} diff --git a/heromodels/examples/library_rhai/library.rhai b/heromodels/examples/library_rhai/library.rhai new file mode 100644 index 0000000..eb0a579 --- /dev/null +++ b/heromodels/examples/library_rhai/library.rhai @@ -0,0 +1,78 @@ +// heromodels/examples/library_rhai/library.rhai + +print("--- Testing Library Rhai Module ---"); + +// --- Image --- +print("\n1. Creating and saving an image..."); +let my_image = new_image() + .title("A Beautiful Sunset") + .description("A photo taken from a drone.") + .url("https://example.com/sunset.jpg") + .width(1920) + .height(1080); + +let saved_image = save_image(my_image); +print(" > Saved image with ID: " + saved_image.id); +let image_id = saved_image.id; + +// --- PDF --- +print("\n2. Creating and saving a PDF..."); +let my_pdf = new_pdf() + .title("Rust Programming Guide") + .description("A comprehensive guide to Rust.") + .url("https://example.com/rust.pdf") + .page_count(500); + +let saved_pdf = save_pdf(my_pdf); +print(" > Saved PDF with ID: " + saved_pdf.id); +let pdf_id = saved_pdf.id; + +// --- Markdown --- +print("\n3. Creating and saving a Markdown document..."); +let my_markdown = new_markdown() + .title("Meeting Notes") + .description("Notes from the weekly sync.") + .content("# Meeting Notes\n\n- Discussed project status.\n- Planned next sprint."); + +let saved_markdown = save_markdown(my_markdown); +print(" > Saved Markdown with ID: " + saved_markdown.id); +let markdown_id = saved_markdown.id; + +// --- Collection --- +print("\n4. Creating a collection and adding items..."); +let my_collection = new_collection() + .title("My Awesome Collection") + .description("A collection of various media.") + .add_image(image_id) + .add_pdf(pdf_id) + .add_markdown(markdown_id); + +let saved_collection = save_collection(my_collection); +print(" > Saved collection with ID: " + saved_collection.id); +let collection_id = saved_collection.id; + +// --- Verification --- +print("\n5. Verifying saved data..."); +let fetched_collection = get_collection(collection_id); +print(" > Fetched collection: '" + fetched_collection.title + "'"); +print(" > Collection contains " + fetched_collection.images + " image(s)."); +print(" > Collection contains " + fetched_collection.pdfs + " pdf(s)."); +print(" > Collection contains " + fetched_collection.markdowns + " markdown(s)."); + +let fetched_image = get_image(image_id); +print(" > Fetched image title: '" + fetched_image.title + "'"); +if (fetched_image.url != "https://example.com/sunset.jpg") { + throw "Image URL mismatch!"; +} +print(" > Image URL verified."); + +// --- Deletion --- +print("\n6. Cleaning up database..."); +delete_image(image_id); +print(" > Deleted image with ID: " + image_id); +delete_pdf(pdf_id); +print(" > Deleted PDF with ID: " + pdf_id); +delete_markdown(markdown_id); +print(" > Deleted Markdown with ID: " + markdown_id); +delete_collection(collection_id); +print(" > Deleted collection with ID: " + collection_id); \ No newline at end of file diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index 441e446..7393f2e 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, IndexKey, IndexKeyBuilder, Index}; +use heromodels_core::{BaseModelData, Index}; use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] use heromodels_core::BaseModelDataOps; use heromodels_derive::model; diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index a87cb2d..0d9f3d0 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,7 +1,5 @@ use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; -use heromodels_core::Model; -use heromodels_core::BaseModelDataOps; use heromodels_derive::model; // ProductType represents the type of a product diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index ffc8f53..e6af823 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; +use heromodels_core::BaseModelData; use heromodels_derive::model; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 2eca950..b9317a4 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -1,4 +1,3 @@ -use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; // Temporarily removed to fix compilation issues diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index 07de7c8..6e754c3 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -2,7 +2,6 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; use std::mem; -use chrono::{DateTime, Utc}; use crate::db::Db; use super::calendar::{Event, Attendee, Calendar, AttendanceStatus}; @@ -22,16 +21,6 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { ) } -// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers -fn val_from_i64_to_u64(val_i64: i64) -> Result> { - u64::try_from(val_i64).map_err(|_| - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert value '{}' to u64", val_i64).into(), - Position::NONE - )) - ) -} - #[export_module] mod rhai_calendar_module { // --- Event Functions --- diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs new file mode 100644 index 0000000..43d4b11 --- /dev/null +++ b/heromodels/src/models/circle/circle.rs @@ -0,0 +1,86 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents an event in a calendar +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +pub struct Circle { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub title: String, + pub ws_url: String, + /// Optional description of the circle + pub description: Option, + /// List of related circles + pub circles: Vec, + /// Logo URL or symbol for the circle + pub logo: Option, + /// Theme settings for the circle (colors, styling, etc.) + pub theme: HashMap, +} + +impl Circle { + /// Creates a new circle + pub fn new() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + ws_url: String::new(), + description: None, + circles: Vec::new(), + logo: None, + theme: HashMap::new(), + } + } + + /// Sets the title for the circle + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Sets the ws_url for the circle + pub fn ws_url(mut self, ws_url: impl ToString) -> Self { + self.ws_url = ws_url.to_string(); + self + } + + /// Sets the description for the circle + pub fn description(mut self, description: impl ToString) -> Self { + self.description = Some(description.to_string()); + self + } + + /// Sets the logo for the circle + pub fn logo(mut self, logo: impl ToString) -> Self { + self.logo = Some(logo.to_string()); + self + } + + /// Sets a theme property for the circle + pub fn theme_property(mut self, key: impl ToString, value: impl ToString) -> Self { + self.theme.insert(key.to_string(), value.to_string()); + self + } + + /// Sets the entire theme for the circle + pub fn theme(mut self, theme: HashMap) -> Self { + self.theme = theme; + self + } + + /// Adds a related circle + pub fn add_circle(mut self, circle: String) -> Self { + // Prevent duplicate circles + if !self.circles.iter().any(|a| *a == circle) { + self.circles.push(circle); + } + self + } +} \ No newline at end of file diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs new file mode 100644 index 0000000..f761c82 --- /dev/null +++ b/heromodels/src/models/circle/mod.rs @@ -0,0 +1,7 @@ +// Export calendar module +pub mod circle; +pub mod rhai; + +// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs +pub use self::circle::{Circle}; +pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs new file mode 100644 index 0000000..07f9f10 --- /dev/null +++ b/heromodels/src/models/circle/rhai.rs @@ -0,0 +1,201 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array, CustomType}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; + +use super::circle::{Circle}; +type RhaiCircle = Circle; +use crate::db::hero::OurDB; +use crate::db::Collection; +use serde::Serialize; +use std::collections::HashMap; +use serde_json; + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + // The type must be: + T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai +{ + // This is the function that will be called when a script runs '.json()' + let to_json_fn = |obj: &mut T| -> Result> { + // Use serde_json to serialize the object to a pretty-formatted string. + // The '?' will automatically convert any serialization error into a Rhai error. + serde_json::to_string(obj).map_err(|e| e.to_string().into()) + }; + + // Register the function as a method named "json" for the type 'T'. + engine.build_type::().register_fn("json", to_json_fn); +} + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) +} + +#[export_module] +mod rhai_circle_module { + // --- Circle Functions --- + #[rhai_fn(name = "new_circle")] + pub fn new_circle() -> RhaiCircle { + Circle::new() + } + + /// Sets the circle title + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn circle_title(circle: &mut RhaiCircle, title: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.title(title); + Ok(circle.clone()) + } + + /// Sets the circle ws_url + #[rhai_fn(name = "ws_url", return_raw, global, pure)] + pub fn circle_ws_url(circle: &mut RhaiCircle, ws_url: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.ws_url(ws_url); + Ok(circle.clone()) + } + + /// Sets the circle description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn circle_description(circle: &mut RhaiCircle, description: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.description(description); + Ok(circle.clone()) + } + + /// Sets the circle logo + #[rhai_fn(name = "logo", return_raw, global, pure)] + pub fn circle_logo(circle: &mut RhaiCircle, logo: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.logo(logo); + Ok(circle.clone()) + } + + /// Sets the circle theme + #[rhai_fn(name = "theme", return_raw, global, pure)] + pub fn circle_theme(circle: &mut RhaiCircle, theme: HashMap) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.theme(theme); + Ok(circle.clone()) + } + + /// Adds an attendee to the circle + #[rhai_fn(name = "add_circle", return_raw, global, pure)] + pub fn circle_add_circle(circle: &mut RhaiCircle, added_circle: String) -> Result> { + // Use take to get ownership of the circle + let owned_circle = mem::take(circle); + *circle = owned_circle.add_circle(added_circle); + Ok(circle.clone()) + } + + // Circle Getters + #[rhai_fn(get = "id", pure)] + pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } + #[rhai_fn(get = "description", pure)] + pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } + #[rhai_fn(get = "circles", pure)] + pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } + #[rhai_fn(get = "ws_url", pure)] + pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } + #[rhai_fn(get = "logo", pure)] + pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } + #[rhai_fn(get = "theme", pure)] + pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { circle.theme.clone() } +} + +pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { + // Register the exported module globally + let module = exported_module!(rhai_circle_module); + engine.register_global_module(module.into()); + + // Create a module for database functions + let mut db_module = Module::new(); + + // Manually register database functions as they need to capture 'db' + let db_clone_set_circle = db.clone(); + db_module.set_native_fn("save_circle", move |circle: Circle| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_circle.set(&circle) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; + + // Return the updated circle with the correct ID + Ok(result.1) + }); + + register_json_method::(engine); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_circle = db.clone(); + db_module.set_native_fn("delete_circle", move |circle: Circle| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_circle.collection::() + .expect("can open circle collection") + .delete_by_id(circle.base_data.id) + .expect("can delete circle"); + + // Return the updated circle with the correct ID + Ok(result) + }); + + let db_clone_get_circle = db.clone(); + db_module.set_native_fn("get_circle", move || -> Result> { + // Use the Collection trait method directly + let all_circles: Vec = db_clone_get_circle.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle: {}", e).into(), Position::NONE)))?; + + if let Some(first_circle) = all_circles.first() { + Ok(first_circle.clone()) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) + } + }); + + let db_clone_get_circle_by_id = db.clone(); + db_module.set_native_fn("get_circle_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_circle_by_id.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) + }); + + // Add list_circles function to get all circles + let db_clone_list_circles = db.clone(); + db_module.set_native_fn("list_circles", move || -> Result> { + let collection = db_clone_list_circles.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get circle collection: {:?}", e).into(), + Position::NONE + )))?; + let circles = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all circles: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for circle in circles { + array.push(Dynamic::from(circle)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered circle Rhai module using export_module approach."); +} diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index f225fc4..7bb9e6c 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -80,13 +80,13 @@ impl Account { /// Get the total value of all assets in the account pub fn total_value(&self) -> f64 { - /// TODO: implement + // TODO: implement 0.0 } /// Find an asset by name - pub fn find_asset_by_name(&self, name: &str) -> Option<&Asset> { - /// TODO: implement + pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { + // TODO: implement return None } } diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index d58db70..0633904 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -2,7 +2,7 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; use std::mem; -use chrono::{DateTime, Utc}; +use chrono::Utc; use super::account::Account; use super::asset::{Asset, AssetType}; @@ -10,7 +10,6 @@ use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; use crate::db::hero::OurDB; use crate::db::{Collection, Db}; -use heromodels_core::Model; type RhaiAccount = Account; type RhaiAsset = Asset; @@ -689,18 +688,8 @@ mod bid_module { Ok(bid.clone()) } } -use std::collections::HashMap; -use std::sync::Mutex; use rhai::ImmutableString; -// Custom error type for Rhai -struct RhaiStringError(String); -impl std::fmt::Debug for RhaiStringError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - // Register all Rhai functions for the finance module pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { // --- Register model-specific modules with the engine --- diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index b564850..b0ea9eb 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -12,7 +12,6 @@ type RhaiSignatureRequirement = SignatureRequirement; use crate::db::hero::OurDB; use crate::db::Collection; use crate::db::Db; -use heromodels_core::Model; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { @@ -24,16 +23,6 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { ) } -// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers -fn val_from_i64_to_u64(val_i64: i64) -> Result> { - u64::try_from(val_i64).map_err(|_| - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert value '{}' to u64", val_i64).into(), - Position::NONE - )) - ) -} - #[export_module] mod rhai_flow_module { // --- Flow Functions --- diff --git a/heromodels/src/models/library/collection.rs b/heromodels/src/models/library/collection.rs new file mode 100644 index 0000000..220109c --- /dev/null +++ b/heromodels/src/models/library/collection.rs @@ -0,0 +1,91 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use rhai::{CustomType, TypeBuilder}; + +/// Represents a collection of library items. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Collection { + /// Base model data + pub base_data: BaseModelData, + /// Title of the collection + #[index] + pub title: String, + /// Optional description of the collection + pub description: Option, + /// List of image item IDs belonging to this collection + pub images: Vec, + /// List of PDF item IDs belonging to this collection + pub pdfs: Vec, + /// List of Markdown item IDs belonging to this collection + pub markdowns: Vec, + /// List of Book item IDs belonging to this collection + pub books: Vec, + /// List of Slides item IDs belonging to this collection + pub slides: Vec, +} + +impl Default for Collection { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + images: Vec::new(), + pdfs: Vec::new(), + markdowns: Vec::new(), + books: Vec::new(), + slides: Vec::new(), + } + } +} + +impl Collection { + /// Creates a new `Collection` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the collection. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the collection. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds an image ID to the collection. + pub fn add_image(mut self, image_id: u32) -> Self { + self.images.push(image_id); + self + } + + /// Adds a PDF ID to the collection. + pub fn add_pdf(mut self, pdf_id: u32) -> Self { + self.pdfs.push(pdf_id); + self + } + + /// Adds a markdown ID to the collection. + pub fn add_markdown(mut self, markdown_id: u32) -> Self { + self.markdowns.push(markdown_id); + self + } + + /// Adds a book ID to the collection. + pub fn add_book(mut self, book_id: u32) -> Self { + self.books.push(book_id); + self + } + + /// Adds a slides ID to the collection. + pub fn add_slides(mut self, slides_id: u32) -> Self { + self.slides.push(slides_id); + self + } +} diff --git a/heromodels/src/models/library/items.rs b/heromodels/src/models/library/items.rs new file mode 100644 index 0000000..aacd182 --- /dev/null +++ b/heromodels/src/models/library/items.rs @@ -0,0 +1,369 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an Image library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Image { + /// Base model data + pub base_data: BaseModelData, + /// Title of the image + #[index] + pub title: String, + /// Optional description of the image + pub description: Option, + /// URL of the image + pub url: String, + /// Width of the image in pixels + pub width: u32, + /// Height of the image in pixels + pub height: u32, +} + +impl Default for Image { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + url: String::new(), + width: 0, + height: 0, + } + } +} + +impl Image { + /// Creates a new `Image` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the image. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the image. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the URL of the image. + pub fn url(mut self, url: impl Into) -> Self { + self.url = url.into(); + self + } + + /// Sets the width of the image. + pub fn width(mut self, width: u32) -> Self { + self.width = width; + self + } + + /// Sets the height of the image. + pub fn height(mut self, height: u32) -> Self { + self.height = height; + self + } +} + +/// Represents a PDF document library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Pdf { + /// Base model data + pub base_data: BaseModelData, + /// Title of the PDF + #[index] + pub title: String, + /// Optional description of the PDF + pub description: Option, + /// URL of the PDF file + pub url: String, + /// Number of pages in the PDF + pub page_count: u32, +} + +impl Default for Pdf { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + url: String::new(), + page_count: 0, + } + } +} + +impl Pdf { + /// Creates a new `Pdf` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the PDF. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the PDF. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the URL of the PDF. + pub fn url(mut self, url: impl Into) -> Self { + self.url = url.into(); + self + } + + /// Sets the page count of the PDF. + pub fn page_count(mut self, page_count: u32) -> Self { + self.page_count = page_count; + self + } +} + +/// Represents a Markdown document library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Markdown { + /// Base model data + pub base_data: BaseModelData, + /// Title of the document + #[index] + pub title: String, + /// Optional description of the document + pub description: Option, + /// The markdown content + pub content: String, +} + +impl Default for Markdown { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + content: String::new(), + } + } +} + +impl Markdown { + /// Creates a new `Markdown` document with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the document. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the document. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the content of the document. + pub fn content(mut self, content: impl Into) -> Self { + self.content = content.into(); + self + } +} + +/// Represents a table of contents entry for a book. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct TocEntry { + /// Title of the chapter/section + pub title: String, + /// Page number (index in the pages array) + pub page: u32, + /// Optional subsections + pub subsections: Vec, +} + +impl Default for TocEntry { + fn default() -> Self { + Self { + title: String::new(), + page: 0, + subsections: Vec::new(), + } + } +} + +impl TocEntry { + /// Creates a new `TocEntry` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the TOC entry. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the page number of the TOC entry. + pub fn page(mut self, page: u32) -> Self { + self.page = page; + self + } + + /// Adds a subsection to the TOC entry. + pub fn add_subsection(mut self, subsection: TocEntry) -> Self { + self.subsections.push(subsection); + self + } +} + +/// Represents a Book library item (collection of markdown pages with TOC). +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Book { + /// Base model data + pub base_data: BaseModelData, + /// Title of the book + #[index] + pub title: String, + /// Optional description of the book + pub description: Option, + /// Table of contents + pub table_of_contents: Vec, + /// Pages content (markdown strings) + pub pages: Vec, +} + +impl Default for Book { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + table_of_contents: Vec::new(), + pages: Vec::new(), + } + } +} + +impl Book { + /// Creates a new `Book` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the book. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the book. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds a page to the book. + pub fn add_page(mut self, content: impl Into) -> Self { + self.pages.push(content.into()); + self + } + + /// Adds a TOC entry to the book. + pub fn add_toc_entry(mut self, entry: TocEntry) -> Self { + self.table_of_contents.push(entry); + self + } + + /// Sets the table of contents. + pub fn table_of_contents(mut self, toc: Vec) -> Self { + self.table_of_contents = toc; + self + } + + /// Sets all pages at once. + pub fn pages(mut self, pages: Vec) -> Self { + self.pages = pages; + self + } +} + +/// Represents a Slides library item (collection of images for slideshow). +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Slides { + /// Base model data + pub base_data: BaseModelData, + /// Title of the slideshow + #[index] + pub title: String, + /// Optional description of the slideshow + pub description: Option, + /// List of slide image URLs + pub slide_urls: Vec, + /// Optional slide titles/captions + pub slide_titles: Vec>, +} + +impl Default for Slides { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + slide_urls: Vec::new(), + slide_titles: Vec::new(), + } + } +} + +impl Slides { + /// Creates a new `Slides` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the slideshow. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the slideshow. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds a slide with URL and optional title. + pub fn add_slide(mut self, url: impl Into, title: Option) -> Self { + self.slide_urls.push(url.into()); + self.slide_titles.push(title); + self + } + + /// Sets all slide URLs at once. + pub fn slide_urls(mut self, urls: Vec) -> Self { + self.slide_urls = urls; + self + } + + /// Sets all slide titles at once. + pub fn slide_titles(mut self, titles: Vec>) -> Self { + self.slide_titles = titles; + self + } +} diff --git a/heromodels/src/models/library/mod.rs b/heromodels/src/models/library/mod.rs new file mode 100644 index 0000000..a9bcd44 --- /dev/null +++ b/heromodels/src/models/library/mod.rs @@ -0,0 +1,4 @@ +pub mod collection; +pub mod items; +pub mod rhai; +pub use rhai::register_library_rhai_module; diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs new file mode 100644 index 0000000..1bd2cdd --- /dev/null +++ b/heromodels/src/models/library/rhai.rs @@ -0,0 +1,626 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder, Position, Module, Dynamic, Array}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; +use serde::Serialize; +use serde_json; + + +use super::collection::{Collection as RhaiCollection}; +use super::items::{Image as RhaiImage, Pdf as RhaiPdf, Markdown as RhaiMarkdown, Book as RhaiBook, Slides as RhaiSlides, TocEntry as RhaiTocEntry}; +use crate::db::hero::OurDB; +use crate::db::Collection as DbCollectionTrait; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorMismatchDataType( + "u32".to_string(), // Expected type + format!("i64 value ({}) that cannot be represented as u32", id_i64), // Actual type/value description + Position::NONE + )) + ) +} + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + // The type must be: + T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai +{ + // This is the function that will be called when a script runs '.json()' + let to_json_fn = |obj: &mut T| -> Result> { + // Use serde_json to serialize the object to a pretty-formatted string. + // The '?' will automatically convert any serialization error into a Rhai error. + serde_json::to_string(obj).map_err(|e| e.to_string().into()) + }; + + // Register the function as a method named "json" for the type 'T'. + engine.build_type::().register_fn("json", to_json_fn); +} + +// Wrapper type for a list of collections to enable .json() method via register_json_method +#[derive(Debug, Clone, Serialize, CustomType)] +#[rhai_type(name = "CollectionArray")] +pub struct RhaiCollectionArray(pub Vec); + + +#[export_module] +mod rhai_library_module { + // --- Collection Functions --- + #[rhai_fn(name = "new_collection")] + pub fn new_collection() -> RhaiCollection { + RhaiCollection::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn collection_title(collection: &mut RhaiCollection, title: String) -> Result> { + let owned = mem::take(collection); + *collection = owned.title(title); + Ok(collection.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn collection_description(collection: &mut RhaiCollection, description: String) -> Result> { + let owned = mem::take(collection); + *collection = owned.description(description); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_image", return_raw, global, pure)] + pub fn collection_add_image(collection: &mut RhaiCollection, image_id: i64) -> Result> { + let id = id_from_i64_to_u32(image_id)?; + let owned = mem::take(collection); + *collection = owned.add_image(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_pdf", return_raw, global, pure)] + pub fn collection_add_pdf(collection: &mut RhaiCollection, pdf_id: i64) -> Result> { + let id = id_from_i64_to_u32(pdf_id)?; + let owned = mem::take(collection); + *collection = owned.add_pdf(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_markdown", return_raw, global, pure)] + pub fn collection_add_markdown(collection: &mut RhaiCollection, markdown_id: i64) -> Result> { + let id = id_from_i64_to_u32(markdown_id)?; + let owned = mem::take(collection); + *collection = owned.add_markdown(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_book", return_raw, global, pure)] + pub fn collection_add_book(collection: &mut RhaiCollection, book_id: i64) -> Result> { + let id = id_from_i64_to_u32(book_id)?; + let owned = mem::take(collection); + *collection = owned.add_book(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_slides", return_raw, global, pure)] + pub fn collection_add_slides(collection: &mut RhaiCollection, slides_id: i64) -> Result> { + let id = id_from_i64_to_u32(slides_id)?; + let owned = mem::take(collection); + *collection = owned.add_slides(id); + Ok(collection.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { collection.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_collection_title(collection: &mut RhaiCollection) -> String { collection.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { collection.description.clone() } + + #[rhai_fn(get = "images", pure)] + pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { collection.images.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "pdfs", pure)] + pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { collection.pdfs.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "markdowns", pure)] + pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { collection.markdowns.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "books", pure)] + pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { collection.books.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "slides", pure)] + pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { collection.slides.clone().into_iter().map(|id| id as i64).collect() } + + // --- Image Functions --- + #[rhai_fn(name = "new_image")] + pub fn new_image() -> RhaiImage { + RhaiImage::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn image_title(image: &mut RhaiImage, title: String) -> Result> { + let owned = mem::take(image); + *image = owned.title(title); + Ok(image.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn image_description(image: &mut RhaiImage, description: String) -> Result> { + let owned = mem::take(image); + *image = owned.description(description); + Ok(image.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn image_url(image: &mut RhaiImage, url: String) -> Result> { + let owned = mem::take(image); + *image = owned.url(url); + Ok(image.clone()) + } + + #[rhai_fn(name = "width", return_raw, global, pure)] + pub fn image_width(image: &mut RhaiImage, width: i64) -> Result> { + let owned = mem::take(image); + *image = owned.width(width as u32); + Ok(image.clone()) + } + + #[rhai_fn(name = "height", return_raw, global, pure)] + pub fn image_height(image: &mut RhaiImage, height: i64) -> Result> { + let owned = mem::take(image); + *image = owned.height(height as u32); + Ok(image.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_image_id(image: &mut RhaiImage) -> i64 { image.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { image.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { image.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_image_title(image: &mut RhaiImage) -> String { image.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_image_description(image: &mut RhaiImage) -> Option { image.description.clone() } + + #[rhai_fn(get = "url", pure)] + pub fn get_image_url(image: &mut RhaiImage) -> String { image.url.clone() } + + #[rhai_fn(get = "width", pure)] + pub fn get_image_width(image: &mut RhaiImage) -> u32 { image.width } + + #[rhai_fn(get = "height", pure)] + pub fn get_image_height(image: &mut RhaiImage) -> u32 { image.height } + + // --- Pdf Functions --- + #[rhai_fn(name = "new_pdf")] + pub fn new_pdf() -> RhaiPdf { + RhaiPdf::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn pdf_title(pdf: &mut RhaiPdf, title: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.title(title); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn pdf_description(pdf: &mut RhaiPdf, description: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.description(description); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn pdf_url(pdf: &mut RhaiPdf, url: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.url(url); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "page_count", return_raw, global, pure)] + pub fn pdf_page_count(pdf: &mut RhaiPdf, page_count: i64) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.page_count(page_count as u32); + Ok(pdf.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { pdf.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { pdf.description.clone() } + + #[rhai_fn(get = "url", pure)] + pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { pdf.url.clone() } + + #[rhai_fn(get = "page_count", pure)] + pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { pdf.page_count } + + // --- Markdown Functions --- + #[rhai_fn(name = "new_markdown")] + pub fn new_markdown() -> RhaiMarkdown { + RhaiMarkdown::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn markdown_title(markdown: &mut RhaiMarkdown, title: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.title(title); + Ok(markdown.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn markdown_description(markdown: &mut RhaiMarkdown, description: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.description(description); + Ok(markdown.clone()) + } + + #[rhai_fn(name = "content", return_raw, global, pure)] + pub fn markdown_content(markdown: &mut RhaiMarkdown, content: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.content(content); + Ok(markdown.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { markdown.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { markdown.description.clone() } + + #[rhai_fn(get = "content", pure)] + pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { markdown.content.clone() } + + // --- TocEntry Functions --- + #[rhai_fn(name = "new_toc_entry")] + pub fn new_toc_entry() -> RhaiTocEntry { + RhaiTocEntry::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn toc_entry_title(entry: &mut RhaiTocEntry, title: String) -> Result> { + let owned = mem::take(entry); + *entry = owned.title(title); + Ok(entry.clone()) + } + + #[rhai_fn(name = "page", return_raw, global, pure)] + pub fn toc_entry_page(entry: &mut RhaiTocEntry, page: i64) -> Result> { + let owned = mem::take(entry); + *entry = owned.page(page as u32); + Ok(entry.clone()) + } + + #[rhai_fn(name = "add_subsection", return_raw, global, pure)] + pub fn toc_entry_add_subsection(entry: &mut RhaiTocEntry, subsection: RhaiTocEntry) -> Result> { + let owned = mem::take(entry); + *entry = owned.add_subsection(subsection); + Ok(entry.clone()) + } + + #[rhai_fn(get = "title", pure)] + pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { entry.title.clone() } + + #[rhai_fn(get = "page", pure)] + pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { entry.page } + + #[rhai_fn(get = "subsections", pure)] + pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { entry.subsections.clone() } + + // --- Book Functions --- + #[rhai_fn(name = "new_book")] + pub fn new_book() -> RhaiBook { + RhaiBook::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn book_title(book: &mut RhaiBook, title: String) -> Result> { + let owned = mem::take(book); + *book = owned.title(title); + Ok(book.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn book_description(book: &mut RhaiBook, description: String) -> Result> { + let owned = mem::take(book); + *book = owned.description(description); + Ok(book.clone()) + } + + #[rhai_fn(name = "add_page", return_raw, global, pure)] + pub fn book_add_page(book: &mut RhaiBook, content: String) -> Result> { + let owned = mem::take(book); + *book = owned.add_page(content); + Ok(book.clone()) + } + + #[rhai_fn(name = "add_toc_entry", return_raw, global, pure)] + pub fn book_add_toc_entry(book: &mut RhaiBook, entry: RhaiTocEntry) -> Result> { + let owned = mem::take(book); + *book = owned.add_toc_entry(entry); + Ok(book.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_book_id(book: &mut RhaiBook) -> i64 { book.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { book.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { book.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_book_title(book: &mut RhaiBook) -> String { book.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_book_description(book: &mut RhaiBook) -> Option { book.description.clone() } + + #[rhai_fn(get = "table_of_contents", pure)] + pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { book.table_of_contents.clone() } + + #[rhai_fn(get = "pages", pure)] + pub fn get_book_pages(book: &mut RhaiBook) -> Vec { book.pages.clone() } + + // --- Slides Functions --- + #[rhai_fn(name = "new_slides")] + pub fn new_slides() -> RhaiSlides { + RhaiSlides::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn slides_title(slides: &mut RhaiSlides, title: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.title(title); + Ok(slides.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn slides_description(slides: &mut RhaiSlides, description: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.description(description); + Ok(slides.clone()) + } + + #[rhai_fn(name = "add_slide", return_raw, global, pure)] + pub fn slides_add_slide(slides: &mut RhaiSlides, url: String, title: String) -> Result> { + let owned = mem::take(slides); + let title_opt = if title.is_empty() { None } else { Some(title) }; + *slides = owned.add_slide(url, title_opt); + Ok(slides.clone()) + } + + #[rhai_fn(name = "add_slide", return_raw, global, pure)] + pub fn slides_add_slide_no_title(slides: &mut RhaiSlides, url: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.add_slide(url, None); + Ok(slides.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { slides.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_slides_title(slides: &mut RhaiSlides) -> String { slides.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { slides.description.clone() } + + #[rhai_fn(get = "slide_urls", pure)] + pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { slides.slide_urls.clone() } + + #[rhai_fn(get = "slide_titles", pure)] + pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { slides.slide_titles.clone() } +} + +pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { + let module = exported_module!(rhai_library_module); + engine.register_global_module(module.into()); + + let mut db_module = Module::new(); + + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + + // Register .json() method for our custom CollectionArray type + register_json_method::(engine); + + // --- Collection DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_collection", move |collection: RhaiCollection| -> Result> { + let result = db_clone.set(&collection) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_collection", move |id: i64| -> Result> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(collection_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Collection with ID {} not found", collection_id).into(), Position::NONE))) + }); + + let db_clone_list_collections = db.clone(); + db_module.set_native_fn("list_collections", move || -> Result> { + let collections_vec: Vec = db_clone_list_collections + .collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - access): {:?}", e).into(), Position::NONE)))? + .get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - get_all): {:?}", e).into(), Position::NONE)))?; + Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_collection", move |id: i64| -> Result<(), Box> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(collection_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Image DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_image", move |image: RhaiImage| -> Result> { + let result = db_clone.set(&image) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_image", move |id: i64| -> Result> { + let image_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(image_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Image with ID {} not found", image_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_image", move |id: i64| -> Result<(), Box> { + let image_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(image_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Pdf DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_pdf", move |pdf: RhaiPdf| -> Result> { + let result = db_clone.set(&pdf) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_pdf", move |id: i64| -> Result> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(pdf_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Pdf with ID {} not found", pdf_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_pdf", move |id: i64| -> Result<(), Box> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(pdf_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Markdown DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_markdown", move |markdown: RhaiMarkdown| -> Result> { + let result = db_clone.set(&markdown) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_markdown", move |id: i64| -> Result> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(markdown_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Markdown with ID {} not found", markdown_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_markdown", move |id: i64| -> Result<(), Box> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(markdown_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Book DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_book", move |book: RhaiBook| -> Result> { + let result = db_clone.set(&book) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_book", move |id: i64| -> Result> { + let book_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(book_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Book with ID {} not found", book_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_book", move |id: i64| -> Result<(), Box> { + let book_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(book_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Slides DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_slides", move |slides: RhaiSlides| -> Result> { + let result = db_clone.set(&slides) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_slides", move |id: i64| -> Result> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(slides_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Slides with ID {} not found", slides_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_slides", move |id: i64| -> Result<(), Box> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(slides_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + engine.register_global_module(db_module.into()); +} diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index b4c8db2..eae5ca6 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -3,8 +3,10 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing pub mod calendar; +pub mod circle; pub mod governance; pub mod finance; +pub mod library; pub mod legal; pub mod flow; pub mod biz; @@ -15,18 +17,25 @@ pub use core::Comment; pub use userexample::User; // pub use productexample::Product; // Temporarily remove pub use calendar::{Calendar, Event, Attendee, AttendanceStatus}; +pub use circle::{Circle}; pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile}; pub use finance::{Account, Asset, AssetType}; pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use biz::{Sale, SaleItem, SaleStatus}; +pub use library::items::{Image, Pdf, Markdown}; +pub use library::collection::Collection; pub use flow::register_flow_rhai_module; #[cfg(feature = "rhai")] pub use calendar::register_calendar_rhai_module; +#[cfg(feature = "rhai")] +pub use circle::register_circle_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] pub use biz::register_biz_rhai_module; #[cfg(feature = "rhai")] pub use projects::register_projects_rhai_module; +#[cfg(feature = "rhai")] +pub use library::register_library_rhai_module; From 6b3cbfc4b291284d33891317a199cf3e7972043e Mon Sep 17 00:00:00 2001 From: timurgordon Date: Wed, 18 Jun 2025 01:56:24 +0300 Subject: [PATCH 4/9] db new models --- .gitignore | 1 + heromodels/.DS_Store | Bin 8196 -> 8196 bytes heromodels/src/models/access/access.rs | 58 ++++++ heromodels/src/models/access/mod.rs | 7 + heromodels/src/models/access/rhai.rs | 177 +++++++++++++++++ heromodels/src/models/contact/contact.rs | 115 +++++++++++ heromodels/src/models/contact/mod.rs | 7 + heromodels/src/models/contact/rhai.rs | 235 +++++++++++++++++++++++ heromodels/src/models/mod.rs | 2 + 9 files changed, 602 insertions(+) create mode 100644 heromodels/src/models/access/access.rs create mode 100644 heromodels/src/models/access/mod.rs create mode 100644 heromodels/src/models/access/rhai.rs create mode 100644 heromodels/src/models/contact/contact.rs create mode 100644 heromodels/src/models/contact/mod.rs create mode 100644 heromodels/src/models/contact/rhai.rs diff --git a/.gitignore b/.gitignore index 8e789fd..8b93f97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ +*.DS_Store *.wasm herovm_build/ diff --git a/heromodels/.DS_Store b/heromodels/.DS_Store index 6862beaf904ed17fb069f61a7c1587e866913d7b..f4f8c5d7aede7e79827ef8e80208b9b28c6fd754 100644 GIT binary patch delta 377 zcmZp1XmOa}ÄU^hRb#$+A=DW;IB$r=K6OiyGdUl0(Ryib6O&E&u_qdk8npA%3* zFvTYG2=Xx|OqLduWBfnaO0bfdwa{hqHo;&f4u;9xLNct3>{~P&ChG_#vodlu1Tahz zR++p*$bqfvg^Y07*UA5cj1Ww<$uYvbOjQA!vxQrja+n!P7;+g3a?%Zhlk;;67(k#( zNEJvzWpeXfT#|C~lYk-|JMN!Yz2Mt1M`YO)JhBBD$fgEtOkBgbnO))qE1hj!$p`>Q C33F)x delta 159 zcmZp1XmOa}C7U^hRb%48k^DW;%hlQjhDm@faEd_h2L@;(7Bw&}tfEq1=2d`>_K z!4#X!Bgn_-Fj-nqj&bQ^E5S-;=AY%0w+RL_u_;XE7Ls9QSS_}?akAkr x39C%rA>_cePjM5-tbamA2&UTP7-3!}h6$Uqg?auH-i8G diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs new file mode 100644 index 0000000..560301e --- /dev/null +++ b/heromodels/src/models/access/access.rs @@ -0,0 +1,58 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an event in a contact +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Access { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub object_id: u32, + pub circle_id: u32, + pub contact_id: u32, + pub group_id: u32, + pub expires_at: Option, +} + +impl Access { + pub fn new() -> Self { + Access { + base_data: BaseModelData::new(), + object_id: 0, + circle_id: 0, + contact_id: 0, + group_id: 0, + expires_at: None, + } + } + + pub fn object_id(mut self, object_id: u32) -> Self { + self.object_id = object_id; + self + } + + pub fn contact_id(mut self, contact_id: u32) -> Self { + self.contact_id = contact_id; + self + } + + pub fn group_id(mut self, group_id: u32) -> Self { + self.group_id = group_id; + self + } + + pub fn circle_id(mut self, circle_id: u32) -> Self { + self.circle_id = circle_id; + self + } + + pub fn expires_at(mut self, expires_at: Option) -> Self { + self.expires_at = expires_at; + self + } +} \ No newline at end of file diff --git a/heromodels/src/models/access/mod.rs b/heromodels/src/models/access/mod.rs new file mode 100644 index 0000000..a891652 --- /dev/null +++ b/heromodels/src/models/access/mod.rs @@ -0,0 +1,7 @@ +// Export contact module +pub mod access; +pub mod rhai; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::access::{Access}; +pub use rhai::register_access_rhai_module; diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs new file mode 100644 index 0000000..0ae4d38 --- /dev/null +++ b/heromodels/src/models/access/rhai.rs @@ -0,0 +1,177 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; + +use super::access::{Access}; +type RhaiAccess = Access; +use crate::db::hero::OurDB; +use crate::db::Collection; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) +} + +#[export_module] +mod rhai_access_module { + // --- Access Functions --- + #[rhai_fn(name = "new_access", return_raw)] + pub fn new_access() -> Result> { + let access = Access::new(); + Ok(access) + } + + /// Sets the access name + #[rhai_fn(name = "object_id", return_raw, global, pure)] + pub fn access_object_id(access: &mut RhaiAccess, object_id: u32) -> Result> { + // Create a default Access to replace the taken one + let default_access = Access::new(); + + // Take ownership of the access, apply the builder method, then put it back + let owned_access = std::mem::replace(access, default_access); + *access = owned_access.object_id(object_id); + Ok(access.clone()) + } + + #[rhai_fn(name = "circle_id", return_raw, global, pure)] + pub fn access_circle_id(access: &mut RhaiAccess, circle_id: u32) -> Result> { + // Create a default Access to replace the taken one + let default_access = Access::new(); + + // Take ownership of the access, apply the builder method, then put it back + let owned_access = std::mem::replace(access, default_access); + *access = owned_access.circle_id(circle_id); + Ok(access.clone()) + } + + #[rhai_fn(name = "group_id", return_raw, global, pure)] + pub fn access_group_id(access: &mut RhaiAccess, group_id: u32) -> Result> { + // Create a default Access to replace the taken one + let default_access = Access::new(); + + // Take ownership of the access, apply the builder method, then put it back + let owned_access = std::mem::replace(access, default_access); + *access = owned_access.group_id(group_id); + Ok(access.clone()) + } + + #[rhai_fn(name = "contact_id", return_raw, global, pure)] + pub fn access_contact_id(access: &mut RhaiAccess, contact_id: u32) -> Result> { + // Create a default Access to replace the taken one + let default_access = Access::new(); + + // Take ownership of the access, apply the builder method, then put it back + let owned_access = std::mem::replace(access, default_access); + *access = owned_access.contact_id(contact_id); + Ok(access.clone()) + } + + #[rhai_fn(name = "expires_at", return_raw, global, pure)] + pub fn access_expires_at(access: &mut RhaiAccess, expires_at: Option) -> Result> { + // Create a default Access to replace the taken one + let default_access = Access::new(); + + // Take ownership of the access, apply the builder method, then put it back + let owned_access = std::mem::replace(access, default_access); + *access = owned_access.expires_at(expires_at); + Ok(access.clone()) + } + + // Access Getters + #[rhai_fn(get = "id", pure)] + pub fn get_access_id(access: &mut RhaiAccess) -> i64 { access.base_data.id as i64 } + + #[rhai_fn(get = "object_id", pure)] + pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { access.object_id as i64 } + + #[rhai_fn(get = "circle_id", pure)] + pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { access.circle_id as i64 } + + #[rhai_fn(get = "group_id", pure)] + pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { access.group_id as i64 } + + #[rhai_fn(get = "contact_id", pure)] + pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { access.contact_id as i64 } + + #[rhai_fn(get = "expires_at", pure)] + pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { access.expires_at.unwrap_or(0) as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { access.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { access.base_data.modified_at } +} + +pub fn register_access_rhai_module(engine: &mut Engine, db: Arc) { + // Register the exported module globally + let module = exported_module!(rhai_access_module); + engine.register_global_module(module.into()); + + // Create a module for database functions + let mut db_module = Module::new(); + + let db_clone_set_access = db.clone(); + db_module.set_native_fn("save_access", move |access: Access| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_access.set(&access) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?; + + // Return the updated access with the correct ID + Ok(result.1) + }); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_access = db.clone(); + db_module.set_native_fn("delete_access", move |access: Access| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_access.collection::() + .expect("can open access collection") + .delete_by_id(access.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }); + + let db_clone_get_access = db.clone(); + db_module.set_native_fn("get_access_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_access.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE))) + }); + + // Add list_accesss function to get all accesss + let db_clone_list_accesss = db.clone(); + db_module.set_native_fn("list_accesss", move || -> Result> { + let collection = db_clone_list_accesss.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get access collection: {:?}", e).into(), + Position::NONE + )))?; + let accesss = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all accesss: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for access in accesss { + array.push(Dynamic::from(access)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered access Rhai module using export_module approach."); +} diff --git a/heromodels/src/models/contact/contact.rs b/heromodels/src/models/contact/contact.rs new file mode 100644 index 0000000..32095a1 --- /dev/null +++ b/heromodels/src/models/contact/contact.rs @@ -0,0 +1,115 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an event in a contact +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Contact { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub name: String, + pub description: Option, + pub address: String, + pub phone: String, + pub email: String, + pub notes: Option, + pub circle: String, +} + +impl Contact { + pub fn new() -> Self { + Contact { + base_data: BaseModelData::new(), + name: String::new(), + description: None, + address: String::new(), + phone: String::new(), + email: String::new(), + notes: None, + circle: String::new(), + } + } + + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + pub fn description(mut self, description: impl ToString) -> Self { + self.description = Some(description.to_string()); + self + } + + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + pub fn phone(mut self, phone: impl ToString) -> Self { + self.phone = phone.to_string(); + self + } + + pub fn email(mut self, email: impl ToString) -> Self { + self.email = email.to_string(); + self + } + + pub fn notes(mut self, notes: impl ToString) -> Self { + self.notes = Some(notes.to_string()); + self + } + + pub fn circle(mut self, circle: impl ToString) -> Self { + self.circle = circle.to_string(); + self + } +} + +/// Represents an event in a contact +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, CustomType)] +pub struct Group { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub name: String, + pub description: Option, + pub contacts: Vec, +} + +impl Group { + pub fn new() -> Self { + Group { + base_data: BaseModelData::new(), + name: String::new(), + description: None, + contacts: Vec::new(), + } + } + + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + pub fn description(mut self, description: impl ToString) -> Self { + self.description = Some(description.to_string()); + self + } + + pub fn contacts(mut self, contacts: Vec) -> Self { + self.contacts = contacts; + self + } + + pub fn add_contact(mut self, contact: u32) -> Self { + self.contacts.push(contact); + self + } +} \ No newline at end of file diff --git a/heromodels/src/models/contact/mod.rs b/heromodels/src/models/contact/mod.rs new file mode 100644 index 0000000..4a3aa34 --- /dev/null +++ b/heromodels/src/models/contact/mod.rs @@ -0,0 +1,7 @@ +// Export contact module +pub mod contact; +pub mod rhai; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::contact::{Contact, Group}; +pub use rhai::register_contact_rhai_module; diff --git a/heromodels/src/models/contact/rhai.rs b/heromodels/src/models/contact/rhai.rs new file mode 100644 index 0000000..12b6518 --- /dev/null +++ b/heromodels/src/models/contact/rhai.rs @@ -0,0 +1,235 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; + +use super::contact::{Group, Contact}; +type RhaiGroup = Group; +type RhaiContact = Contact; +use crate::db::hero::OurDB; +use crate::db::Collection; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) +} + +#[export_module] +mod rhai_contact_module { + // --- Event Functions --- + #[rhai_fn(name = "new_group")] + pub fn new_group() -> RhaiGroup { + Group::new() + } + + /// Sets the event title + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn group_name(group: &mut RhaiGroup, name: String) -> Result> { + let owned_group = mem::take(group); + *group = owned_group.name(name); + Ok(group.clone()) + } + + /// Sets the event description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn group_description(group: &mut RhaiGroup, description: String) -> Result> { + let owned_group = mem::take(group); + *group = owned_group.description(description); + Ok(group.clone()) + } + + /// Adds an attendee to the event + #[rhai_fn(name = "add_contact", return_raw, global, pure)] + pub fn group_add_contact(group: &mut RhaiGroup, contact_id: i64) -> Result> { + // Use take to get ownership of the event + let owned_group = mem::take(group); + *group = owned_group.add_contact(contact_id as u32); + Ok(group.clone()) + } + + #[rhai_fn(get = "contacts", pure)] + pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { group.contacts.clone().into_iter().map(|id| id as i64).collect() } + + // Group Getters + #[rhai_fn(get = "id", pure)] + pub fn get_group_id(group: &mut RhaiGroup) -> i64 { group.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { group.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { group.base_data.modified_at } + + #[rhai_fn(get = "name", pure)] + pub fn get_group_name(group: &mut RhaiGroup) -> String { group.name.clone() } + #[rhai_fn(get = "description", pure)] + pub fn get_group_description(group: &mut RhaiGroup) -> Option { group.description.clone() } + + + // --- Contact Functions --- + #[rhai_fn(name = "new_contact", return_raw)] + pub fn new_contact() -> Result> { + let contact = Contact::new(); + Ok(contact) + } + + /// Sets the contact name + #[rhai_fn(name = "name", return_raw, global, pure)] + pub fn contact_name(contact: &mut RhaiContact, name: String) -> Result> { + // Create a default Contact to replace the taken one + let default_contact = Contact::new(); + + // Take ownership of the contact, apply the builder method, then put it back + let owned_contact = std::mem::replace(contact, default_contact); + *contact = owned_contact.name(name); + Ok(contact.clone()) + } + + /// Sets the contact description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn contact_description(contact: &mut RhaiContact, description: String) -> Result> { + // Create a default Contact to replace the taken one + let default_contact = Contact::new(); + + // Take ownership of the contact, apply the builder method, then put it back + let owned_contact = std::mem::replace(contact, default_contact); + *contact = owned_contact.description(description); + Ok(contact.clone()) + } + + // Contact Getters + #[rhai_fn(get = "id", pure)] + pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { contact.base_data.id as i64 } + + #[rhai_fn(get = "name", pure)] + pub fn get_contact_name(contact: &mut RhaiContact) -> String { contact.name.clone() } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { contact.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { contact.base_data.modified_at } +} + +pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc) { + // Register the exported module globally + let module = exported_module!(rhai_contact_module); + engine.register_global_module(module.into()); + + // Create a module for database functions + let mut db_module = Module::new(); + + // Manually register database functions as they need to capture 'db' + let db_clone_set_group = db.clone(); + db_module.set_native_fn("save_group", move |group: Group| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_group.set(&group) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?; + + // Return the updated event with the correct ID + Ok(result.1) + }); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_group = db.clone(); + db_module.set_native_fn("delete_group", move |group: Group| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_group.collection::() + .expect("can open group collection") + .delete_by_id(group.base_data.id) + .expect("can delete group"); + + // Return the updated event with the correct ID + Ok(result) + }); + + let db_clone_get_group = db.clone(); + db_module.set_native_fn("get_group_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_group.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) + }); + + let db_clone_set_contact = db.clone(); + db_module.set_native_fn("save_contact", move |contact: Contact| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_contact.set(&contact) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?; + + // Return the updated contact with the correct ID + Ok(result.1) + }); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_contact = db.clone(); + db_module.set_native_fn("delete_contact", move |contact: Contact| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_contact.collection::() + .expect("can open contact collection") + .delete_by_id(contact.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }); + + let db_clone_get_contact = db.clone(); + db_module.set_native_fn("get_contact_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_contact.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE))) + }); + + // Add list_contacts function to get all contacts + let db_clone_list_contacts = db.clone(); + db_module.set_native_fn("list_contacts", move || -> Result> { + let collection = db_clone_list_contacts.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get contact collection: {:?}", e).into(), + Position::NONE + )))?; + let contacts = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all contacts: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for contact in contacts { + array.push(Dynamic::from(contact)); + } + Ok(Dynamic::from(array)) + }); + + // Add list_events function to get all events + let db_clone_list_groups = db.clone(); + db_module.set_native_fn("list_groups", move || -> Result> { + let collection = db_clone_list_groups.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get group collection: {:?}", e).into(), + Position::NONE + )))?; + let groups = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all groups: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for group in groups { + array.push(Dynamic::from(group)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered contact Rhai module using export_module approach."); +} diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index eae5ca6..42cc76e 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -2,7 +2,9 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing +pub mod access; pub mod calendar; +pub mod contact; pub mod circle; pub mod governance; pub mod finance; From e91a44ce3717484f7e31c6c1732ae979217e6458 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Thu, 19 Jun 2025 13:18:10 +0300 Subject: [PATCH 5/9] fmt, fixes and additions --- heromodels-derive/tests/test_model_macro.rs | 20 +- heromodels/Cargo.toml | 4 - heromodels/examples/basic_user_example.rs | 30 +- heromodels/examples/biz_rhai/example.rs | 12 +- heromodels/examples/calendar_example/main.rs | 110 ++- heromodels/examples/calendar_rhai/example.rs | 39 +- heromodels/examples/custom_model_example.rs | 14 +- heromodels/examples/finance_example/main.rs | 305 ++++--- heromodels/examples/finance_rhai/example.rs | 36 +- heromodels/examples/flow_example.rs | 105 ++- heromodels/examples/flow_rhai/example.rs | 11 +- .../governance_proposal_example/main.rs | 58 +- .../governance_rhai_client/example.rs | 309 ++++--- heromodels/examples/legal_contract_example.rs | 11 +- heromodels/examples/legal_rhai/example.rs | 11 +- heromodels/examples/library_rhai/example.rs | 1 - heromodels/examples/model_macro_example.rs | 37 +- heromodels/examples/project_rhai/example.rs | 12 +- heromodels/examples/simple_model_example.rs | 15 +- heromodels/src/db.rs | 4 +- heromodels/src/db/hero.rs | 8 +- heromodels/src/models/access/access.rs | 2 +- heromodels/src/models/access/mod.rs | 2 +- heromodels/src/models/access/rhai.rs | 215 +++-- heromodels/src/models/biz/company.rs | 12 +- heromodels/src/models/biz/mod.rs | 5 +- heromodels/src/models/biz/product.rs | 4 +- heromodels/src/models/biz/rhai.rs | 590 ++++++++----- heromodels/src/models/biz/sale.rs | 2 +- heromodels/src/models/biz/shareholder.rs | 16 +- heromodels/src/models/calendar/calendar.rs | 23 +- heromodels/src/models/calendar/mod.rs | 2 +- heromodels/src/models/calendar/rhai.rs | 435 ++++++---- heromodels/src/models/circle/circle.rs | 14 +- heromodels/src/models/circle/mod.rs | 2 +- heromodels/src/models/circle/rhai.rs | 258 ++++-- heromodels/src/models/contact/contact.rs | 2 +- heromodels/src/models/contact/rhai.rs | 351 +++++--- heromodels/src/models/core/comment.rs | 2 +- heromodels/src/models/core/mod.rs | 1 - heromodels/src/models/core/model.rs | 1 + heromodels/src/models/finance/account.rs | 10 +- heromodels/src/models/finance/asset.rs | 6 +- heromodels/src/models/finance/marketplace.rs | 11 +- heromodels/src/models/finance/mod.rs | 2 +- heromodels/src/models/finance/rhai.rs | 394 ++++++--- heromodels/src/models/flow/flow.rs | 4 +- heromodels/src/models/flow/mod.rs | 4 +- heromodels/src/models/flow/rhai.rs | 568 +++++++----- .../src/models/flow/signature_requirement.rs | 7 +- heromodels/src/models/governance/mod.rs | 4 +- heromodels/src/models/governance/proposal.rs | 22 +- heromodels/src/models/legal/contract.rs | 22 +- heromodels/src/models/legal/rhai.rs | 666 ++++++++++---- heromodels/src/models/library/collection.rs | 2 +- heromodels/src/models/library/rhai.rs | 815 +++++++++++++----- heromodels/src/models/mod.rs | 40 +- heromodels/src/models/projects/base.rs | 8 +- heromodels/src/models/projects/epic.rs | 10 +- heromodels/src/models/projects/mod.rs | 14 +- heromodels/src/models/projects/rhai.rs | 464 ++++++---- heromodels/src/models/projects/sprint.rs | 8 +- heromodels/src/models/projects/task.rs | 10 +- heromodels/src/models/userexample/mod.rs | 2 +- ourdb/Cargo.toml | 6 +- ourdb/examples/advanced_usage.rs | 145 ++-- ourdb/examples/basic_usage.rs | 55 +- ourdb/examples/benchmark.rs | 93 +- ourdb/examples/main.rs | 41 +- ourdb/src/backend.rs | 122 +-- ourdb/src/error.rs | 10 +- ourdb/src/lib.rs | 62 +- ourdb/src/location.rs | 50 +- ourdb/src/lookup.rs | 118 +-- ourdb/tests/integration_tests.rs | 225 +++-- rhai_client_macros/src/lib.rs | 208 ++--- tst/examples/basic_usage.rs | 30 +- tst/examples/performance.rs | 109 ++- tst/examples/prefix_ops.rs | 152 +++- tst/src/error.rs | 16 +- tst/src/lib.rs | 4 +- tst/src/node.rs | 14 +- tst/src/operations.rs | 152 ++-- tst/src/serialize.rs | 76 +- tst/tests/basic_test.rs | 141 ++- tst/tests/prefix_test.rs | 123 +-- 86 files changed, 5292 insertions(+), 2844 deletions(-) diff --git a/heromodels-derive/tests/test_model_macro.rs b/heromodels-derive/tests/test_model_macro.rs index fab6d70..a57771b 100644 --- a/heromodels-derive/tests/test_model_macro.rs +++ b/heromodels-derive/tests/test_model_macro.rs @@ -1,5 +1,5 @@ use heromodels_derive::model; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; // Define the necessary structs and traits for testing #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,10 +46,10 @@ pub trait Index { #[model] struct TestUser { base_data: BaseModelData, - + #[index] username: String, - + #[index] is_active: bool, } @@ -59,10 +59,10 @@ struct TestUser { #[model] struct TestUserWithCustomIndex { base_data: BaseModelData, - + #[index(name = "custom_username")] username: String, - + #[index] is_active: bool, } @@ -70,13 +70,13 @@ struct TestUserWithCustomIndex { #[test] fn test_basic_model() { assert_eq!(TestUser::db_prefix(), "test_user"); - + let user = TestUser { base_data: BaseModelData::new(1), username: "test".to_string(), is_active: true, }; - + let keys = user.db_keys(); assert_eq!(keys.len(), 2); assert_eq!(keys[0].name, "username"); @@ -92,10 +92,10 @@ fn test_custom_index_name() { username: "test".to_string(), is_active: true, }; - + // Check that the Username struct uses the custom index name assert_eq!(Username::key(), "custom_username"); - + // Check that the db_keys method returns the correct keys let keys = user.db_keys(); assert_eq!(keys.len(), 2); @@ -103,4 +103,4 @@ fn test_custom_index_name() { assert_eq!(keys[0].value, "test"); assert_eq!(keys[1].name, "is_active"); assert_eq!(keys[1].value, "true"); -} \ No newline at end of file +} diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 9b36157..e7ba96c 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -42,10 +42,6 @@ path = "examples/finance_example/main.rs" name = "calendar_rhai" path = "examples/calendar_rhai/example.rs" -[[example]] -name = "calendar_rhai_client" -path = "examples/calendar_rhai_client/example.rs" - [[example]] name = "flow_rhai" path = "examples/flow_rhai/example.rs" diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index cedb29a..953b571 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -68,10 +68,26 @@ fn main() { .build(); // Save all users to database and get their assigned IDs and updated models - let (user1_id, db_user1) = db.collection().expect("can open user collection").set(&user1).expect("can set user"); - let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user"); - let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user"); - let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user"); + let (user1_id, db_user1) = db + .collection() + .expect("can open user collection") + .set(&user1) + .expect("can set user"); + let (user2_id, db_user2) = db + .collection() + .expect("can open user collection") + .set(&user2) + .expect("can set user"); + let (user3_id, db_user3) = db + .collection() + .expect("can open user collection") + .set(&user3) + .expect("can set user"); + let (user4_id, db_user4) = db + .collection() + .expect("can open user collection") + .set(&user4) + .expect("can set user"); println!("User 1 assigned ID: {}", user1_id); println!("User 2 assigned ID: {}", user2_id); @@ -170,7 +186,8 @@ fn main() { .build(); // Save the comment and get its assigned ID and updated model - let (comment_id, db_comment) = db.collection() + let (comment_id, db_comment) = db + .collection() .expect("can open comment collection") .set(&comment) .expect("can set comment"); @@ -186,7 +203,8 @@ fn main() { updated_user.base_data.add_comment(db_comment.get_id()); // Save the updated user and get the new version - let (_, user_with_comment) = db.collection::() + let (_, user_with_comment) = db + .collection::() .expect("can open user collection") .set(&updated_user) .expect("can set updated user"); diff --git a/heromodels/examples/biz_rhai/example.rs b/heromodels/examples/biz_rhai/example.rs index e857f5a..f316fae 100644 --- a/heromodels/examples/biz_rhai/example.rs +++ b/heromodels/examples/biz_rhai/example.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, Scope}; -use std::sync::Arc; use heromodels::db::hero::OurDB; // Corrected path for OurDB use heromodels::models::biz::register_biz_rhai_module; // Corrected path +use rhai::{Engine, EvalAltResult, Scope}; use std::fs; +use std::sync::Arc; fn main() -> Result<(), Box> { println!("Executing Rhai script: examples/biz_rhai/biz.rhai"); @@ -20,8 +20,12 @@ fn main() -> Result<(), Box> { // Read the Rhai script from file let script_path = "examples/biz_rhai/biz.rhai"; - let script_content = fs::read_to_string(script_path) - .map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?; + let script_content = fs::read_to_string(script_path).map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + format!("Cannot read script file: {}", script_path), + e.into(), + )) + })?; // Create a new scope let mut scope = Scope::new(); diff --git a/heromodels/examples/calendar_example/main.rs b/heromodels/examples/calendar_example/main.rs index 6afec70..ba5c51f 100644 --- a/heromodels/examples/calendar_example/main.rs +++ b/heromodels/examples/calendar_example/main.rs @@ -1,6 +1,6 @@ use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; -use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event}; +use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; use heromodels_core::Model; fn main() { @@ -12,10 +12,8 @@ fn main() { println!("===================================="); // --- Create Attendees --- - let attendee1 = Attendee::new("user_123".to_string()) - .status(AttendanceStatus::Accepted); - let attendee2 = Attendee::new("user_456".to_string()) - .status(AttendanceStatus::Tentative); + let attendee1 = Attendee::new("user_123".to_string()).status(AttendanceStatus::Accepted); + let attendee2 = Attendee::new("user_456".to_string()).status(AttendanceStatus::Tentative); let attendee3 = Attendee::new("user_789".to_string()); // Default NoResponse // --- Create Events --- @@ -45,7 +43,7 @@ fn main() { "event_gamma".to_string(), "Client Call", now + Duration::days(2), - now + Duration::days(2) + Duration::seconds(3600) + now + Duration::days(2) + Duration::seconds(3600), ); // --- Create Calendars --- @@ -58,25 +56,43 @@ fn main() { .add_event(event2.clone()); // Create a calendar with auto-generated ID (explicit IDs are no longer supported) - let calendar2 = Calendar::new(None, "Personal Calendar") - .add_event(event3_for_calendar2.clone()); - + let calendar2 = + Calendar::new(None, "Personal Calendar").add_event(event3_for_calendar2.clone()); // --- Store Calendars in DB --- - let cal_collection = db.collection::().expect("can open calendar collection"); + let cal_collection = db + .collection::() + .expect("can open calendar collection"); let (_, calendar1) = cal_collection.set(&calendar1).expect("can set calendar1"); let (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2"); - println!("Created calendar1 (ID: {}): Name - '{}'", calendar1.get_id(), calendar1.name); - println!("Created calendar2 (ID: {}): Name - '{}'", calendar2.get_id(), calendar2.name); + println!( + "Created calendar1 (ID: {}): Name - '{}'", + calendar1.get_id(), + calendar1.name + ); + println!( + "Created calendar2 (ID: {}): Name - '{}'", + calendar2.get_id(), + calendar2.name + ); // --- Retrieve a Calendar by ID --- - let stored_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load calendar1"); - assert!(stored_calendar1_opt.is_some(), "Calendar1 should be found in DB"); + let stored_calendar1_opt = cal_collection + .get_by_id(calendar1.get_id()) + .expect("can try to load calendar1"); + assert!( + stored_calendar1_opt.is_some(), + "Calendar1 should be found in DB" + ); let mut stored_calendar1 = stored_calendar1_opt.unwrap(); - println!("\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", stored_calendar1.name, stored_calendar1.events.len()); + println!( + "\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", + stored_calendar1.name, + stored_calendar1.events.len() + ); assert_eq!(stored_calendar1.name, "Work Calendar"); assert_eq!(stored_calendar1.events.len(), 2); assert_eq!(stored_calendar1.events[0].title, "Team Meeting"); @@ -84,49 +100,83 @@ fn main() { // --- Modify a Calendar (Reschedule an Event) --- let event_id_to_reschedule = event1.id.as_str(); let new_start_time = now + Duration::seconds(10800); // 3 hours from now - let new_end_time = now + Duration::seconds(14400); // 4 hours from now + let new_end_time = now + Duration::seconds(14400); // 4 hours from now stored_calendar1 = stored_calendar1.update_event(event_id_to_reschedule, |event_to_update| { println!("Rescheduling event '{}'...", event_to_update.title); event_to_update.reschedule(new_start_time, new_end_time) }); - let rescheduled_event = stored_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) + let rescheduled_event = stored_calendar1 + .events + .iter() + .find(|e| e.id == event_id_to_reschedule) .expect("Rescheduled event should exist"); assert_eq!(rescheduled_event.start_time, new_start_time); assert_eq!(rescheduled_event.end_time, new_end_time); - println!("Event '{}' rescheduled in stored_calendar1.", rescheduled_event.title); + println!( + "Event '{}' rescheduled in stored_calendar1.", + rescheduled_event.title + ); // --- Store the modified calendar --- - let (_, mut stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set modified calendar1"); - let re_retrieved_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load modified calendar1"); + let (_, mut stored_calendar1) = cal_collection + .set(&stored_calendar1) + .expect("can set modified calendar1"); + let re_retrieved_calendar1_opt = cal_collection + .get_by_id(calendar1.get_id()) + .expect("can try to load modified calendar1"); let re_retrieved_calendar1 = re_retrieved_calendar1_opt.unwrap(); - let re_retrieved_event = re_retrieved_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) + let re_retrieved_event = re_retrieved_calendar1 + .events + .iter() + .find(|e| e.id == event_id_to_reschedule) .expect("Rescheduled event should exist in re-retrieved calendar"); - assert_eq!(re_retrieved_event.start_time, new_start_time, "Reschedule not persisted correctly"); + assert_eq!( + re_retrieved_event.start_time, new_start_time, + "Reschedule not persisted correctly" + ); - println!("\nModified and re-saved calendar1. Rescheduled event start time: {}", re_retrieved_event.start_time); + println!( + "\nModified and re-saved calendar1. Rescheduled event start time: {}", + re_retrieved_event.start_time + ); // --- Add a new event to an existing calendar --- let event4_new = Event::new( "event_delta".to_string(), "1-on-1", now + Duration::days(3), - now + Duration::days(3) + Duration::seconds(1800) // 30 minutes + now + Duration::days(3) + Duration::seconds(1800), // 30 minutes ); stored_calendar1 = stored_calendar1.add_event(event4_new); assert_eq!(stored_calendar1.events.len(), 3); - let (_, stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event"); - println!("Added new event '1-on-1' to stored_calendar1. Total events: {}", stored_calendar1.events.len()); + let (_, stored_calendar1) = cal_collection + .set(&stored_calendar1) + .expect("can set calendar1 after adding new event"); + println!( + "Added new event '1-on-1' to stored_calendar1. Total events: {}", + stored_calendar1.events.len() + ); // --- Delete a Calendar --- - cal_collection.delete_by_id(calendar2.get_id()).expect("can delete calendar2"); - let deleted_calendar2_opt = cal_collection.get_by_id(calendar2.get_id()).expect("can try to load deleted calendar2"); - assert!(deleted_calendar2_opt.is_none(), "Calendar2 should be deleted from DB"); + cal_collection + .delete_by_id(calendar2.get_id()) + .expect("can delete calendar2"); + let deleted_calendar2_opt = cal_collection + .get_by_id(calendar2.get_id()) + .expect("can try to load deleted calendar2"); + assert!( + deleted_calendar2_opt.is_none(), + "Calendar2 should be deleted from DB" + ); println!("\nDeleted calendar2 (ID: {}) from DB.", calendar2.get_id()); println!("Calendar model DB Prefix: {}", Calendar::db_prefix()); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/calendar_rhai/example.rs b/heromodels/examples/calendar_rhai/example.rs index 08b5501..dc7c05f 100644 --- a/heromodels/examples/calendar_rhai/example.rs +++ b/heromodels/examples/calendar_rhai/example.rs @@ -1,18 +1,17 @@ use heromodels::db::hero::OurDB; -use heromodels::models::calendar::{Attendee, AttendanceStatus, Calendar, Event}; use heromodels::models::calendar::rhai::register_rhai_engine_functions; +use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; use rhai::Engine; +use rhai_wrapper::wrap_vec_return; use std::sync::Arc; use std::{fs, path::Path}; -use rhai_wrapper::wrap_vec_return; - fn main() -> Result<(), Box> { // Initialize Rhai engine let mut engine = Engine::new(); // Initialize database with OurDB - let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database")); + let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database")); // Register the Calendar type with Rhai // This function is generated by the #[rhai_model_export] attribute @@ -29,9 +28,12 @@ fn main() -> Result<(), Box> { }); // Register setter methods for Calendar properties - engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| { - calendar.description = Some(desc); - }); + engine.register_fn( + "set_description", + |calendar: &mut Calendar, desc: String| { + calendar.description = Some(desc); + }, + ); // Register getter methods for Calendar properties engine.register_fn("get_description", |calendar: Calendar| -> String { @@ -49,10 +51,13 @@ fn main() -> Result<(), Box> { println!("Calendar saved: {}", _calendar.name); }); - engine.register_fn("get_calendar_by_id", |_db: Arc, id: i64| -> Calendar { - // In a real implementation, this would retrieve the calendar from the database - Calendar::new(Some(id as u32), "Retrieved Calendar") - }); + engine.register_fn( + "get_calendar_by_id", + |_db: Arc, id: i64| -> Calendar { + // In a real implementation, this would retrieve the calendar from the database + Calendar::new(Some(id as u32), "Retrieved Calendar") + }, + ); // Register a function to check if a calendar exists engine.register_fn("calendar_exists", |_db: Arc, id: i64| -> bool { @@ -63,11 +68,17 @@ fn main() -> Result<(), Box> { // Define the function separately to use with the wrap_vec_return macro fn get_all_calendars(_db: Arc) -> Vec { // In a real implementation, this would retrieve all calendars from the database - vec![Calendar::new(Some(1), "Calendar 1"), Calendar::new(Some(2), "Calendar 2")] + vec![ + Calendar::new(Some(1), "Calendar 1"), + Calendar::new(Some(2), "Calendar 2"), + ] } // Register the function with the wrap_vec_return macro - engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc => Calendar)); + engine.register_fn( + "get_all_calendars", + wrap_vec_return!(get_all_calendars, Arc => Calendar), + ); engine.register_fn("delete_calendar_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the calendar from the database @@ -84,4 +95,4 @@ fn main() -> Result<(), Box> { } Ok(()) -} \ No newline at end of file +} diff --git a/heromodels/examples/custom_model_example.rs b/heromodels/examples/custom_model_example.rs index 7a3cc71..46bb02e 100644 --- a/heromodels/examples/custom_model_example.rs +++ b/heromodels/examples/custom_model_example.rs @@ -41,11 +41,16 @@ fn main() { println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys()); // Save the model to the database - let collection = db.collection::().expect("can open user collection"); + let collection = db + .collection::() + .expect("can open user collection"); let (user_id, saved_user) = collection.set(&user).expect("can save user"); println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id()); - println!("After saving - CustomUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - CustomUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned ID: {}", user_id); // Verify that the ID was auto-generated @@ -53,5 +58,8 @@ fn main() { assert_ne!(saved_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/finance_example/main.rs b/heromodels/examples/finance_example/main.rs index acae3cc..1854a2e 100644 --- a/heromodels/examples/finance_example/main.rs +++ b/heromodels/examples/finance_example/main.rs @@ -1,8 +1,10 @@ // heromodels/examples/finance_example/main.rs -use chrono::{Utc, Duration}; +use chrono::{Duration, Utc}; +use heromodels::models::finance::marketplace::{ + Bid, BidStatus, Listing, ListingStatus, ListingType, +}; use heromodels::models::finance::{Account, Asset, AssetType}; -use heromodels::models::finance::marketplace::{Listing, ListingType, ListingStatus, Bid, BidStatus}; fn main() { println!("Finance Models Example\n"); @@ -12,16 +14,19 @@ fn main() { // Create a new account with auto-generated ID let mut account = Account::new( - None, // id (auto-generated) - "Main ETH Wallet", // name - 1001, // user_id - "My primary Ethereum wallet", // description - "ethereum", // ledger + None, // id (auto-generated) + "Main ETH Wallet", // name + 1001, // user_id + "My primary Ethereum wallet", // description + "ethereum", // ledger "0x1234567890abcdef1234567890abcdef12345678", // address - "0xpubkey123456789" // pubkey + "0xpubkey123456789", // pubkey ); - println!("Created Account: '{}' (ID: {})", account.name, account.base_data.id); + println!( + "Created Account: '{}' (ID: {})", + account.name, account.base_data.id + ); println!("Owner: User {}", account.user_id); println!("Blockchain: {}", account.ledger); println!("Address: {}", account.address); @@ -30,34 +35,34 @@ fn main() { // Create some assets // Asset with auto-generated ID let eth_asset = Asset::new( - None, // id (auto-generated) - "Ethereum", // name - "Native ETH cryptocurrency", // description - 1.5, // amount + None, // id (auto-generated) + "Ethereum", // name + "Native ETH cryptocurrency", // description + 1.5, // amount "0x0000000000000000000000000000000000000000", // address (ETH has no contract address) - AssetType::Native, // asset_type - 18, // decimals + AssetType::Native, // asset_type + 18, // decimals ); // Assets with explicit IDs let usdc_asset = Asset::new( - Some(102), // id - "USDC", // name - "USD Stablecoin on Ethereum", // description - 1000.0, // amount + Some(102), // id + "USDC", // name + "USD Stablecoin on Ethereum", // description + 1000.0, // amount "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // address (USDC contract) - AssetType::Erc20, // asset_type - 6, // decimals + AssetType::Erc20, // asset_type + 6, // decimals ); let nft_asset = Asset::new( - Some(103), // id - "CryptoPunk #1234", // name - "Rare digital collectible", // description - 1.0, // amount + Some(103), // id + "CryptoPunk #1234", // name + "Rare digital collectible", // description + 1.0, // amount "0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", // address (CryptoPunks contract) - AssetType::Erc721, // asset_type - 0, // decimals + AssetType::Erc721, // asset_type + 0, // decimals ); // Add assets to the account @@ -67,7 +72,12 @@ fn main() { println!("Added Assets to Account:"); for asset in &account.assets { - println!("- {} ({:?}): {} units", asset.name, asset.asset_type, asset.formatted_amount()); + println!( + "- {} ({:?}): {} units", + asset.name, + asset.asset_type, + asset.formatted_amount() + ); } println!("\nTotal Account Value (raw sum): {}", account.total_value()); @@ -75,10 +85,10 @@ fn main() { // Update account details account = account.update_details( - Some("Primary Ethereum Wallet"), // new name - None::, // keep same description - None::, // keep same address - Some("0xnewpubkey987654321"), // new pubkey + Some("Primary Ethereum Wallet"), // new name + None::, // keep same description + None::, // keep same address + Some("0xnewpubkey987654321"), // new pubkey ); println!("Updated Account Details:"); @@ -99,23 +109,32 @@ fn main() { // Create a fixed price listing with auto-generated ID let mut fixed_price_listing = Listing::new( - None, // id (auto-generated) - "1000 USDC for Sale", // title - "Selling 1000 USDC tokens at fixed price", // description - "102", // asset_id (referencing the USDC asset) - AssetType::Erc20, // asset_type - "1001", // seller_id - 1.05, // price (in ETH) - "ETH", // currency - ListingType::FixedPrice, // listing_type + None, // id (auto-generated) + "1000 USDC for Sale", // title + "Selling 1000 USDC tokens at fixed price", // description + "102", // asset_id (referencing the USDC asset) + AssetType::Erc20, // asset_type + "1001", // seller_id + 1.05, // price (in ETH) + "ETH", // currency + ListingType::FixedPrice, // listing_type Some(Utc::now() + Duration::days(7)), // expires_at (7 days from now) vec!["token".to_string(), "stablecoin".to_string()], // tags Some("https://example.com/usdc.png"), // image_url ); - println!("Created Fixed Price Listing: '{}' (ID: {})", fixed_price_listing.title, fixed_price_listing.base_data.id); - println!("Price: {} {}", fixed_price_listing.price, fixed_price_listing.currency); - println!("Type: {:?}, Status: {:?}", fixed_price_listing.listing_type, fixed_price_listing.status); + println!( + "Created Fixed Price Listing: '{}' (ID: {})", + fixed_price_listing.title, fixed_price_listing.base_data.id + ); + println!( + "Price: {} {}", + fixed_price_listing.price, fixed_price_listing.currency + ); + println!( + "Type: {:?}, Status: {:?}", + fixed_price_listing.listing_type, fixed_price_listing.status + ); println!("Expires: {}", fixed_price_listing.expires_at.unwrap()); println!(""); @@ -126,54 +145,71 @@ fn main() { println!("Fixed Price Sale Completed:"); println!("Status: {:?}", fixed_price_listing.status); println!("Buyer: {}", fixed_price_listing.buyer_id.unwrap()); - println!("Sale Price: {} {}", fixed_price_listing.sale_price.unwrap(), fixed_price_listing.currency); + println!( + "Sale Price: {} {}", + fixed_price_listing.sale_price.unwrap(), + fixed_price_listing.currency + ); println!("Sold At: {}", fixed_price_listing.sold_at.unwrap()); println!(""); - }, + } Err(e) => println!("Error completing sale: {}", e), } // Create an auction listing for the NFT with explicit ID let mut auction_listing = Listing::new( - Some(202), // id (explicit) - "CryptoPunk #1234 Auction", // title - "Rare CryptoPunk NFT for auction", // description - "103", // asset_id (referencing the NFT asset) - AssetType::Erc721, // asset_type - "1001", // seller_id - 10.0, // starting_price (in ETH) - "ETH", // currency - ListingType::Auction, // listing_type + Some(202), // id (explicit) + "CryptoPunk #1234 Auction", // title + "Rare CryptoPunk NFT for auction", // description + "103", // asset_id (referencing the NFT asset) + AssetType::Erc721, // asset_type + "1001", // seller_id + 10.0, // starting_price (in ETH) + "ETH", // currency + ListingType::Auction, // listing_type Some(Utc::now() + Duration::days(3)), // expires_at (3 days from now) - vec!["nft".to_string(), "collectible".to_string(), "cryptopunk".to_string()], // tags + vec![ + "nft".to_string(), + "collectible".to_string(), + "cryptopunk".to_string(), + ], // tags Some("https://example.com/cryptopunk1234.png"), // image_url ); - println!("Created Auction Listing: '{}' (ID: {})", auction_listing.title, auction_listing.base_data.id); - println!("Starting Price: {} {}", auction_listing.price, auction_listing.currency); - println!("Type: {:?}, Status: {:?}", auction_listing.listing_type, auction_listing.status); + println!( + "Created Auction Listing: '{}' (ID: {})", + auction_listing.title, auction_listing.base_data.id + ); + println!( + "Starting Price: {} {}", + auction_listing.price, auction_listing.currency + ); + println!( + "Type: {:?}, Status: {:?}", + auction_listing.listing_type, auction_listing.status + ); println!(""); // Create some bids let bid1 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2001, // bidder_id - 11.0, // amount - "ETH", // currency + 2001, // bidder_id + 11.0, // amount + "ETH", // currency ); let bid2 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2002, // bidder_id - 12.5, // amount - "ETH", // currency + 2002, // bidder_id + 12.5, // amount + "ETH", // currency ); let bid3 = Bid::new( auction_listing.base_data.id.to_string(), // listing_id - 2003, // bidder_id - 15.0, // amount - "ETH", // currency + 2003, // bidder_id + 15.0, // amount + "ETH", // currency ); // Add bids to the auction @@ -184,7 +220,7 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 11.0 ETH from User 2001"); - }, + } Err(e) => println!("Error adding bid: {}", e), } @@ -192,7 +228,7 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 12.5 ETH from User 2002"); - }, + } Err(e) => println!("Error adding bid: {}", e), } @@ -200,18 +236,21 @@ fn main() { Ok(updated_listing) => { auction_listing = updated_listing; println!("- Bid added: 15.0 ETH from User 2003"); - }, + } Err(e) => println!("Error adding bid: {}", e), } println!("\nCurrent Auction Status:"); - println!("Current Price: {} {}", auction_listing.price, auction_listing.currency); + println!( + "Current Price: {} {}", + auction_listing.price, auction_listing.currency + ); if let Some(highest_bid) = auction_listing.highest_bid() { - println!("Highest Bid: {} {} from User {}", - highest_bid.amount, - highest_bid.currency, - highest_bid.bidder_id); + println!( + "Highest Bid: {} {} from User {}", + highest_bid.amount, highest_bid.currency, highest_bid.bidder_id + ); } println!("Total Bids: {}", auction_listing.bids.len()); @@ -223,42 +262,57 @@ fn main() { auction_listing = updated_listing; println!("Auction Completed:"); println!("Status: {:?}", auction_listing.status); - println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap()); - println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency); + println!( + "Winner: User {}", + auction_listing.buyer_id.as_ref().unwrap() + ); + println!( + "Winning Bid: {} {}", + auction_listing.sale_price.as_ref().unwrap(), + auction_listing.currency + ); println!(""); println!("Final Bid Statuses:"); for bid in &auction_listing.bids { - println!("- User {}: {} {} (Status: {:?})", - bid.bidder_id, - bid.amount, - bid.currency, - bid.status); + println!( + "- User {}: {} {} (Status: {:?})", + bid.bidder_id, bid.amount, bid.currency, bid.status + ); } println!(""); - }, + } Err(e) => println!("Error completing auction: {}", e), } // Create an exchange listing with auto-generated ID let exchange_listing = Listing::new( - None, // id (auto-generated) - "ETH for BTC Exchange", // title - "Looking to exchange ETH for BTC", // description - "101", // asset_id (referencing the ETH asset) - AssetType::Native, // asset_type - "1001", // seller_id - 1.0, // amount (1 ETH) - "BTC", // currency (what they want in exchange) - ListingType::Exchange, // listing_type - Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now) + None, // id (auto-generated) + "ETH for BTC Exchange", // title + "Looking to exchange ETH for BTC", // description + "101", // asset_id (referencing the ETH asset) + AssetType::Native, // asset_type + "1001", // seller_id + 1.0, // amount (1 ETH) + "BTC", // currency (what they want in exchange) + ListingType::Exchange, // listing_type + Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now) vec!["exchange".to_string(), "crypto".to_string()], // tags - None::, // image_url + None::, // image_url ); - println!("Created Exchange Listing: '{}' (ID: {})", exchange_listing.title, exchange_listing.base_data.id); - println!("Offering: Asset {} ({:?})", exchange_listing.asset_id, exchange_listing.asset_type); - println!("Wanted: {} {}", exchange_listing.price, exchange_listing.currency); + println!( + "Created Exchange Listing: '{}' (ID: {})", + exchange_listing.title, exchange_listing.base_data.id + ); + println!( + "Offering: Asset {} ({:?})", + exchange_listing.asset_id, exchange_listing.asset_type + ); + println!( + "Wanted: {} {}", + exchange_listing.price, exchange_listing.currency + ); println!(""); // --- PART 3: DEMONSTRATING EDGE CASES --- @@ -266,26 +320,26 @@ fn main() { // Create a new auction listing for edge case testing with explicit ID let test_auction = Listing::new( - Some(205), // id (explicit) - "Test Auction", // title - "For testing edge cases", // description - "101", // asset_id - AssetType::Native, // asset_type - "1001", // seller_id - 10.0, // starting_price - "ETH", // currency - ListingType::Auction, // listing_type + Some(205), // id (explicit) + "Test Auction", // title + "For testing edge cases", // description + "101", // asset_id + AssetType::Native, // asset_type + "1001", // seller_id + 10.0, // starting_price + "ETH", // currency + ListingType::Auction, // listing_type Some(Utc::now() + Duration::days(1)), // expires_at - vec![], // tags - None::, // image_url + vec![], // tags + None::, // image_url ); // Try to add a bid that's too low let low_bid = Bid::new( test_auction.base_data.id.to_string(), // listing_id - 2004, // bidder_id - 5.0, // amount (lower than starting price) - "ETH", // currency + 2004, // bidder_id + 5.0, // amount (lower than starting price) + "ETH", // currency ); println!("Attempting to add a bid that's too low (5.0 ETH):"); @@ -305,21 +359,24 @@ fn main() { // Create a listing that will expire with auto-generated ID let mut expiring_listing = Listing::new( - None, // id (auto-generated) - "About to Expire", // title + None, // id (auto-generated) + "About to Expire", // title "This listing will expire immediately", // description - "101", // asset_id - AssetType::Native, // asset_type - "1001", // seller_id - 0.1, // price - "ETH", // currency - ListingType::FixedPrice, // listing_type - Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago) - vec![], // tags - None::, // image_url + "101", // asset_id + AssetType::Native, // asset_type + "1001", // seller_id + 0.1, // price + "ETH", // currency + ListingType::FixedPrice, // listing_type + Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago) + vec![], // tags + None::, // image_url ); - println!("Created Expiring Listing: '{}' (ID: {})", expiring_listing.title, expiring_listing.base_data.id); + println!( + "Created Expiring Listing: '{}' (ID: {})", + expiring_listing.title, expiring_listing.base_data.id + ); println!("Initial Status: {:?}", expiring_listing.status); // Check expiration diff --git a/heromodels/examples/finance_rhai/example.rs b/heromodels/examples/finance_rhai/example.rs index 23ea125..10f8fde 100644 --- a/heromodels/examples/finance_rhai/example.rs +++ b/heromodels/examples/finance_rhai/example.rs @@ -1,12 +1,12 @@ -use rhai::{Engine, Scope, EvalAltResult}; -use std::sync::{Arc, Mutex}; +use rhai::{Engine, EvalAltResult, Scope}; use std::collections::HashMap; use std::fs; +use std::sync::{Arc, Mutex}; // Import the models and the registration function use heromodels::models::finance::account::Account; -use heromodels::models::finance::asset::{Asset}; -use heromodels::models::finance::marketplace::{Listing}; +use heromodels::models::finance::asset::Asset; +use heromodels::models::finance::marketplace::Listing; use heromodels::models::finance::rhai::register_rhai_engine_functions; // Define a simple in-memory mock database for the example @@ -39,10 +39,10 @@ fn main() -> Result<(), Box> { // Register finance functions and types with the engine register_rhai_engine_functions( - &mut engine, + &mut engine, Arc::clone(&mock_db.accounts), Arc::clone(&mock_db.assets), - Arc::clone(&mock_db.listings) + Arc::clone(&mock_db.listings), ); println!("Rhai functions registered."); @@ -77,8 +77,13 @@ fn main() -> Result<(), Box> { println!("No accounts in mock DB."); } for (id, account) in final_accounts.iter() { - println!("Account ID: {}, Name: '{}', User ID: {}, Assets: {}", - id, account.name, account.user_id, account.assets.len()); + println!( + "Account ID: {}, Name: '{}', User ID: {}, Assets: {}", + id, + account.name, + account.user_id, + account.assets.len() + ); } // Print final state of Assets @@ -88,8 +93,10 @@ fn main() -> Result<(), Box> { println!("No assets in mock DB."); } for (id, asset) in final_assets.iter() { - println!("Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}", - id, asset.name, asset.amount, asset.asset_type); + println!( + "Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}", + id, asset.name, asset.amount, asset.asset_type + ); } // Print final state of Listings @@ -100,8 +107,13 @@ fn main() -> Result<(), Box> { } for (id, listing) in final_listings.iter() { println!( - "Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}", - id, listing.title, listing.listing_type, listing.status, listing.price, listing.bids.len() + "Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}", + id, + listing.title, + listing.listing_type, + listing.status, + listing.price, + listing.bids.len() ); } diff --git a/heromodels/examples/flow_example.rs b/heromodels/examples/flow_example.rs index 3a489e8..b4bdc02 100644 --- a/heromodels/examples/flow_example.rs +++ b/heromodels/examples/flow_example.rs @@ -9,8 +9,8 @@ use heromodels_core::Model; fn main() { // Create a new DB instance in /tmp/ourdb_flowbroker, and reset before every run - let db = heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true) - .expect("Can create DB"); + let db = + heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true).expect("Can create DB"); println!("Hero Models - Flow Example"); println!("==========================="); @@ -20,56 +20,71 @@ fn main() { let new_flow_uuid = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Example UUID let flow1 = Flow::new( - 1, // id - new_flow_uuid, // flow_uuid - "Document Approval Flow", // name - "Pending", // status + 1, // id + new_flow_uuid, // flow_uuid + "Document Approval Flow", // name + "Pending", // status ); - db.collection().expect("can open flow collection").set(&flow1).expect("can set flow1"); + db.collection() + .expect("can open flow collection") + .set(&flow1) + .expect("can set flow1"); println!("Created Flow: {:?}", flow1); println!("Flow ID: {}", flow1.get_id()); println!("Flow DB Prefix: {}", Flow::db_prefix()); // --- Create FlowSteps for Flow1 --- let step1_flow1 = FlowStep::new( - 101, // id - flow1.get_id(), // flow_id - 1, // step_order - "Pending", // status + 101, // id + flow1.get_id(), // flow_id + 1, // step_order + "Pending", // status ) .description("Initial review by manager"); - db.collection().expect("can open flow_step collection").set(&step1_flow1).expect("can set step1_flow1"); + db.collection() + .expect("can open flow_step collection") + .set(&step1_flow1) + .expect("can set step1_flow1"); println!("Created FlowStep: {:?}", step1_flow1); let step2_flow1 = FlowStep::new( - 102, // id - flow1.get_id(), // flow_id - 2, // step_order - "Pending", // status + 102, // id + flow1.get_id(), // flow_id + 2, // step_order + "Pending", // status ) .description("Legal team sign-off"); - db.collection().expect("can open flow_step collection").set(&step2_flow1).expect("can set step2_flow1"); + db.collection() + .expect("can open flow_step collection") + .set(&step2_flow1) + .expect("can set step2_flow1"); println!("Created FlowStep: {:?}", step2_flow1); // --- Create SignatureRequirements for step2_flow1 --- let sig_req1_step2 = SignatureRequirement::new( - 201, // id - step2_flow1.get_id(), // flow_step_id - "pubkey_legal_team_lead_hex", // public_key + 201, // id + step2_flow1.get_id(), // flow_step_id + "pubkey_legal_team_lead_hex", // public_key "I approve this document for legal compliance.", // message - "Pending", // status + "Pending", // status ); - db.collection().expect("can open sig_req collection").set(&sig_req1_step2).expect("can set sig_req1_step2"); + db.collection() + .expect("can open sig_req collection") + .set(&sig_req1_step2) + .expect("can set sig_req1_step2"); println!("Created SignatureRequirement: {:?}", sig_req1_step2); let sig_req2_step2 = SignatureRequirement::new( - 202, // id - step2_flow1.get_id(), // flow_step_id - "pubkey_general_counsel_hex", // public_key + 202, // id + step2_flow1.get_id(), // flow_step_id + "pubkey_general_counsel_hex", // public_key "I, as General Counsel, approve this document.", // message - "Pending", // status + "Pending", // status ); - db.collection().expect("can open sig_req collection").set(&sig_req2_step2).expect("can set sig_req2_step2"); + db.collection() + .expect("can open sig_req collection") + .set(&sig_req2_step2) + .expect("can set sig_req2_step2"); println!("Created SignatureRequirement: {:?}", sig_req2_step2); // --- Retrieve and Verify --- @@ -101,9 +116,18 @@ fn main() { .get::(&retrieved_flow.get_id()) .expect("can load steps for flow1"); assert_eq!(steps_for_flow1.len(), 2); - println!("Retrieved {} FlowSteps for Flow ID {}:", steps_for_flow1.len(), retrieved_flow.get_id()); + println!( + "Retrieved {} FlowSteps for Flow ID {}:", + steps_for_flow1.len(), + retrieved_flow.get_id() + ); for step in &steps_for_flow1 { - println!(" - Step ID: {}, Order: {}, Desc: {:?}", step.get_id(), step.step_order, step.description); + println!( + " - Step ID: {}, Order: {}, Desc: {:?}", + step.get_id(), + step.step_order, + step.description + ); } // --- Update a SignatureRequirement (simulate signing) --- @@ -114,12 +138,18 @@ fn main() { .expect("can load sig_req1") .unwrap(); - println!("\nUpdating SignatureRequirement ID: {}", retrieved_sig_req1.get_id()); + println!( + "\nUpdating SignatureRequirement ID: {}", + retrieved_sig_req1.get_id() + ); retrieved_sig_req1.status = "Signed".to_string(); retrieved_sig_req1.signed_by = Some("pubkey_legal_team_lead_hex_actual_signer".to_string()); retrieved_sig_req1.signature = Some("mock_signature_base64_encoded".to_string()); - db.collection().expect("can open sig_req collection").set(&retrieved_sig_req1).expect("can update sig_req1"); + db.collection() + .expect("can open sig_req collection") + .set(&retrieved_sig_req1) + .expect("can update sig_req1"); let updated_sig_req1 = db .collection::() @@ -129,10 +159,13 @@ fn main() { .unwrap(); assert_eq!(updated_sig_req1.status, "Signed"); - assert_eq!(updated_sig_req1.signature.as_deref(), Some("mock_signature_base64_encoded")); + assert_eq!( + updated_sig_req1.signature.as_deref(), + Some("mock_signature_base64_encoded") + ); println!("Updated SignatureRequirement: {:?}", updated_sig_req1); - // --- Delete a FlowStep --- + // --- Delete a FlowStep --- // (In a real app, you might also want to delete associated SignatureRequirements first, or handle via DB constraints/cascade if supported) let step1_id_to_delete = step1_flow1.get_id(); db.collection::() @@ -157,7 +190,11 @@ fn main() { .expect("can load remaining steps for flow1"); assert_eq!(remaining_steps_for_flow1.len(), 1); assert_eq!(remaining_steps_for_flow1[0].get_id(), step2_flow1.get_id()); - println!("Remaining FlowSteps for Flow ID {}: count = {}", retrieved_flow.get_id(), remaining_steps_for_flow1.len()); + println!( + "Remaining FlowSteps for Flow ID {}: count = {}", + retrieved_flow.get_id(), + remaining_steps_for_flow1.len() + ); println!("\nFlow example finished successfully!"); } diff --git a/heromodels/examples/flow_rhai/example.rs b/heromodels/examples/flow_rhai/example.rs index a6cdd67..6c106d4 100644 --- a/heromodels/examples/flow_rhai/example.rs +++ b/heromodels/examples/flow_rhai/example.rs @@ -20,13 +20,18 @@ fn main() -> Result<(), Box> { let script_path = Path::new(script_path_str); if !script_path.exists() { eprintln!("Error: Rhai script not found at {}", script_path_str); - eprintln!("Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory."); - return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str)))); + eprintln!( + "Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory." + ); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Rhai script not found: {}", script_path_str), + ))); } println!("Executing Rhai script: {}", script_path_str); let script = fs::read_to_string(script_path)?; - + match engine.eval::<()>(&script) { Ok(_) => println!("\nRhai script executed successfully!"), Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e), diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index bfbe78a..7906923 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -101,23 +101,23 @@ fn main() { // Example of voting with comments using the cast_vote_with_comment method println!("Adding votes with comments..."); - + // User 7 votes for 'Approve Allocation' with a comment proposal = proposal.cast_vote_with_comment( Some(110), // ballot_id - 7, // user_id - 1, // chosen_option_id (Approve Allocation) - 80, // shares - "I strongly support this proposal because it aligns with our community values." + 7, // user_id + 1, // chosen_option_id (Approve Allocation) + 80, // shares + "I strongly support this proposal because it aligns with our community values.", ); - + // User 8 votes for 'Reject Allocation' with a comment proposal = proposal.cast_vote_with_comment( Some(111), // ballot_id - 8, // user_id - 2, // chosen_option_id (Reject Allocation) - 60, // shares - "I have concerns about the allocation priorities." + 8, // user_id + 2, // chosen_option_id (Reject Allocation) + 60, // shares + "I have concerns about the allocation priorities.", ); println!("\nBallots with Comments:"); @@ -218,34 +218,34 @@ fn main() { // Example of voting with comments on a private proposal println!("\nAdding votes with comments to private proposal..."); - + // User 20 (eligible) votes with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(202), // ballot_id - 20, // user_id (eligible) - 1, // chosen_option_id - 75, // shares - "I support this restructuring plan with some reservations." + Some(202), // ballot_id + 20, // user_id (eligible) + 1, // chosen_option_id + 75, // shares + "I support this restructuring plan with some reservations.", ); - + // User 30 (eligible) votes with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(203), // ballot_id - 30, // user_id (eligible) - 2, // chosen_option_id - 90, // shares - "I believe we should reconsider the timing of these changes." + Some(203), // ballot_id + 30, // user_id (eligible) + 2, // chosen_option_id + 90, // shares + "I believe we should reconsider the timing of these changes.", ); - + // User 40 (ineligible) tries to vote with a comment private_proposal = private_proposal.cast_vote_with_comment( - Some(204), // ballot_id - 40, // user_id (ineligible) - 1, // chosen_option_id - 50, // shares - "This restructuring seems unnecessary." + Some(204), // ballot_id + 40, // user_id (ineligible) + 1, // chosen_option_id + 50, // shares + "This restructuring seems unnecessary.", ); - + println!("Eligible users 20 and 30 added votes with comments."); println!("Ineligible user 40 attempted to vote with a comment (should be rejected)."); diff --git a/heromodels/examples/governance_rhai_client/example.rs b/heromodels/examples/governance_rhai_client/example.rs index 6dcc648..d68e3ab 100644 --- a/heromodels/examples/governance_rhai_client/example.rs +++ b/heromodels/examples/governance_rhai_client/example.rs @@ -1,10 +1,12 @@ +use chrono::{Duration, Utc}; use heromodels::db::hero::OurDB; -use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot}; +use heromodels::models::governance::{ + Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, +}; use rhai::Engine; +use rhai_client_macros::rhai; use rhai_wrapper::wrap_vec_return; use std::sync::Arc; -use chrono::{Utc, Duration}; -use rhai_client_macros::rhai; // Define the functions we want to expose to Rhai // We'll only use the #[rhai] attribute on functions with simple types @@ -13,7 +15,14 @@ use rhai_client_macros::rhai; fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal { let start_date = Utc::now(); let end_date = start_date + Duration::days(14); - Proposal::new(id as u32, creator_id, title, description, start_date, end_date) + Proposal::new( + id as u32, + creator_id, + title, + description, + start_date, + end_date, + ) } // Getter functions for Proposal properties @@ -46,7 +55,13 @@ fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: Strin proposal.add_option(option_id as u8, option_text) } -fn cast_vote_on_proposal(proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64) -> Proposal { +fn cast_vote_on_proposal( + proposal: Proposal, + ballot_id: i64, + user_id: i64, + option_id: i64, + shares: i64, +) -> Proposal { proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares) } @@ -119,14 +134,24 @@ fn get_ballot_shares(ballot: &Ballot) -> i64 { // Simple functions that we can use with the #[rhai] attribute #[rhai] -fn create_proposal_wrapper(id: i64, creator_id: String, title: String, description: String) -> String { +fn create_proposal_wrapper( + id: i64, + creator_id: String, + title: String, + description: String, +) -> String { let proposal = create_proposal(id, creator_id, title, description); format!("Created proposal with ID: {}", proposal.base_data.id) } #[rhai] fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String { - let proposal = create_proposal(id, "user".to_string(), "title".to_string(), "description".to_string()); + let proposal = create_proposal( + id, + "user".to_string(), + "title".to_string(), + "description".to_string(), + ); let updated = add_option_to_proposal(proposal, option_id, option_text.clone()); format!("Added option '{}' to proposal {}", option_text, id) } @@ -141,8 +166,22 @@ fn get_all_proposals(_db: Arc) -> Vec { let start_date = Utc::now(); let end_date = start_date + Duration::days(14); vec![ - Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date), - Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date) + Proposal::new( + 1, + "Creator 1", + "Proposal 1", + "Description 1", + start_date, + end_date, + ), + Proposal::new( + 2, + "Creator 2", + "Proposal 2", + "Description 2", + start_date, + end_date, + ), ] } @@ -161,31 +200,49 @@ fn main() -> Result<(), Box> { // Register the Proposal type with Rhai // This function is generated by the #[rhai_model_export] attribute Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone()); - + // Register the Ballot type with Rhai Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone()); // Create a clone of db for use in the get_db function let db_for_get_db = db.clone(); - + // Register a function to get the database instance engine.register_fn("get_db", move || db_for_get_db.clone()); - + // Register builder functions for Proposal and related types - engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| { - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new(id as u32, creator_id, title, description, start_date, end_date) - }); - + engine.register_fn( + "create_proposal", + |id: i64, creator_id: String, title: String, description: String| { + let start_date = Utc::now(); + let end_date = start_date + Duration::days(14); + Proposal::new( + id as u32, + creator_id, + title, + description, + start_date, + end_date, + ) + }, + ); + engine.register_fn("create_vote_option", |id: i64, text: String| { VoteOption::new(id as u8, text) }); - - engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { - Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count) - }); - + + engine.register_fn( + "create_ballot", + |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { + Ballot::new( + id as u32, + user_id as u32, + vote_option_id as u8, + shares_count, + ) + }, + ); + // Register getter and setter methods for Proposal properties engine.register_fn("get_title", get_title); engine.register_fn("get_description", get_description); @@ -193,34 +250,47 @@ fn main() -> Result<(), Box> { engine.register_fn("get_id", get_id); engine.register_fn("get_status", get_status); engine.register_fn("get_vote_status", get_vote_status); - + // Register methods for proposal operations engine.register_fn("add_option_to_proposal", add_option_to_proposal); engine.register_fn("cast_vote_on_proposal", cast_vote_on_proposal); engine.register_fn("change_proposal_status", change_proposal_status); engine.register_fn("change_vote_event_status", change_vote_event_status); - + // Register functions for database operations engine.register_fn("save_proposal", save_proposal); - - engine.register_fn("get_proposal_by_id", |_db: Arc, id: i64| -> Proposal { - // In a real implementation, this would retrieve the proposal from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date) - }); - + + engine.register_fn( + "get_proposal_by_id", + |_db: Arc, id: i64| -> Proposal { + // In a real implementation, this would retrieve the proposal from the database + let start_date = Utc::now(); + let end_date = start_date + Duration::days(14); + Proposal::new( + id as u32, + "Retrieved Creator", + "Retrieved Proposal", + "Retrieved Description", + start_date, + end_date, + ) + }, + ); + // Register a function to check if a proposal exists engine.register_fn("proposal_exists", |_db: Arc, id: i64| -> bool { // In a real implementation, this would check if the proposal exists in the database id == 1 || id == 2 }); - + // Register the function with the wrap_vec_return macro - engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc => Proposal)); - + engine.register_fn( + "get_all_proposals", + wrap_vec_return!(get_all_proposals, Arc => Proposal), + ); + engine.register_fn("delete_proposal_by_id", delete_proposal_by_id); - + // Register helper functions for accessing proposal options and ballots engine.register_fn("get_option_count", get_option_count); engine.register_fn("get_option_at", get_option_at); @@ -231,96 +301,108 @@ fn main() -> Result<(), Box> { engine.register_fn("get_ballot_user_id", get_ballot_user_id); engine.register_fn("get_ballot_option_id", get_ballot_option_id); engine.register_fn("get_ballot_shares", get_ballot_shares); - + // Register our wrapper functions that use the #[rhai] attribute engine.register_fn("create_proposal_wrapper", create_proposal_wrapper); engine.register_fn("add_option_wrapper", add_option_wrapper); // Now instead of loading and evaluating a Rhai script, we'll use direct function calls // to implement the same functionality - + // Use the database instance - + // Create a new proposal - let proposal = create_proposal(1, - "user_creator_123".to_string(), - "Community Fund Allocation for Q3".to_string(), - "Proposal to allocate funds for community projects in the third quarter.".to_string()); - - println!("Created Proposal: '{}' (ID: {})", - get_title(&proposal), - get_id(&proposal)); - println!("Status: {}, Vote Status: {}", - get_status(&proposal), - get_vote_status(&proposal)); - + let proposal = create_proposal( + 1, + "user_creator_123".to_string(), + "Community Fund Allocation for Q3".to_string(), + "Proposal to allocate funds for community projects in the third quarter.".to_string(), + ); + + println!( + "Created Proposal: '{}' (ID: {})", + get_title(&proposal), + get_id(&proposal) + ); + println!( + "Status: {}, Vote Status: {}", + get_status(&proposal), + get_vote_status(&proposal) + ); + // Add vote options - let mut proposal_with_options = add_option_to_proposal( - proposal, 1, "Approve Allocation".to_string()); - proposal_with_options = add_option_to_proposal( - proposal_with_options, 2, "Reject Allocation".to_string()); - proposal_with_options = add_option_to_proposal( - proposal_with_options, 3, "Abstain".to_string()); - + let mut proposal_with_options = + add_option_to_proposal(proposal, 1, "Approve Allocation".to_string()); + proposal_with_options = + add_option_to_proposal(proposal_with_options, 2, "Reject Allocation".to_string()); + proposal_with_options = add_option_to_proposal(proposal_with_options, 3, "Abstain".to_string()); + println!("\nAdded Vote Options:"); let option_count = get_option_count(&proposal_with_options); for i in 0..option_count { let option = get_option_at(&proposal_with_options, i); - println!("- Option ID: {}, Text: '{}', Votes: {}", - i, get_option_text(&option), - get_option_votes(&option)); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + i, + get_option_text(&option), + get_option_votes(&option) + ); } - + // Save the proposal to the database save_proposal(db.clone(), proposal_with_options.clone()); println!("\nProposal saved to database"); - + // Simulate casting votes println!("\nSimulating Votes..."); // User 1 votes for 'Approve Allocation' with 100 shares - let mut proposal_with_votes = cast_vote_on_proposal( - proposal_with_options, 101, 1, 1, 100); + let mut proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100); // User 2 votes for 'Reject Allocation' with 50 shares - proposal_with_votes = cast_vote_on_proposal( - proposal_with_votes, 102, 2, 2, 50); + proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50); // User 3 votes for 'Approve Allocation' with 75 shares - proposal_with_votes = cast_vote_on_proposal( - proposal_with_votes, 103, 3, 1, 75); + proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 103, 3, 1, 75); // User 4 abstains with 20 shares - proposal_with_votes = cast_vote_on_proposal( - proposal_with_votes, 104, 4, 3, 20); - + proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 104, 4, 3, 20); + println!("\nVote Counts After Simulation:"); let option_count = get_option_count(&proposal_with_votes); for i in 0..option_count { let option = get_option_at(&proposal_with_votes, i); - println!("- Option ID: {}, Text: '{}', Votes: {}", - i, get_option_text(&option), - get_option_votes(&option)); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + i, + get_option_text(&option), + get_option_votes(&option) + ); } - + println!("\nBallots Cast:"); let ballot_count = get_ballot_count(&proposal_with_votes); for i in 0..ballot_count { let ballot = get_ballot_at(&proposal_with_votes, i); - println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", - i, get_ballot_user_id(&ballot), - get_ballot_option_id(&ballot), - get_ballot_shares(&ballot)); + println!( + "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + i, + get_ballot_user_id(&ballot), + get_ballot_option_id(&ballot), + get_ballot_shares(&ballot) + ); } - + // Change proposal status - let active_proposal = change_proposal_status( - proposal_with_votes, "Active".to_string()); - println!("\nChanged Proposal Status to: {}", - get_status(&active_proposal)); - + let active_proposal = change_proposal_status(proposal_with_votes, "Active".to_string()); + println!( + "\nChanged Proposal Status to: {}", + get_status(&active_proposal) + ); + // Simulate closing the vote - let closed_proposal = change_vote_event_status( - active_proposal, "Closed".to_string()); - println!("Changed Vote Event Status to: {}", - get_vote_status(&closed_proposal)); - + let closed_proposal = change_vote_event_status(active_proposal, "Closed".to_string()); + println!( + "Changed Vote Event Status to: {}", + get_vote_status(&closed_proposal) + ); + // Final proposal state println!("\nFinal Proposal State:"); println!("Title: '{}'", get_title(&closed_proposal)); @@ -330,37 +412,46 @@ fn main() -> Result<(), Box> { let option_count = get_option_count(&closed_proposal); for i in 0..option_count { let option = get_option_at(&closed_proposal, i); - println!(" - {}: {} (Votes: {})", - i, get_option_text(&option), - get_option_votes(&option)); + println!( + " - {}: {} (Votes: {})", + i, + get_option_text(&option), + get_option_votes(&option) + ); } println!("Total Ballots: {}", get_ballot_count(&closed_proposal)); - + // Get all proposals from the database let all_proposals = get_all_proposals(db.clone()); println!("\nTotal Proposals in Database: {}", all_proposals.len()); for proposal in all_proposals { - println!("Proposal ID: {}, Title: '{}'", - get_id(&proposal), - get_title(&proposal)); + println!( + "Proposal ID: {}, Title: '{}'", + get_id(&proposal), + get_title(&proposal) + ); } - + // Delete a proposal delete_proposal_by_id(db.clone(), 1); println!("\nDeleted proposal with ID 1"); - + // Demonstrate the use of Rhai client functions for simple types println!("\nUsing Rhai client functions for simple types:"); - let create_result = create_proposal_wrapper_rhai_client(&engine, 2, - "rhai_user".to_string(), - "Rhai Proposal".to_string(), - "This proposal was created using a Rhai client function".to_string()); + let create_result = create_proposal_wrapper_rhai_client( + &engine, + 2, + "rhai_user".to_string(), + "Rhai Proposal".to_string(), + "This proposal was created using a Rhai client function".to_string(), + ); println!("{}", create_result); - - let add_option_result = add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string()); + + let add_option_result = + add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string()); println!("{}", add_option_result); - + println!("\nGovernance Proposal Example Finished."); - + Ok(()) } diff --git a/heromodels/examples/legal_contract_example.rs b/heromodels/examples/legal_contract_example.rs index cc0fe44..43402b9 100644 --- a/heromodels/examples/legal_contract_example.rs +++ b/heromodels/examples/legal_contract_example.rs @@ -70,7 +70,7 @@ fn main() { .add_signer(signer2.clone()) .add_revision(revision1.clone()) .add_revision(revision2.clone()); - + // The `#[model]` derive handles `created_at` and `updated_at` in `base_data`. // `base_data.touch()` might be called internally by setters or needs explicit call if fields are set directly. // For builder pattern, the final state of `base_data.updated_at` reflects the time of the last builder call if `touch()` is implicit. @@ -87,7 +87,7 @@ fn main() { println!("\n--- Contract Details After Signing ---"); println!("{:#?}", contract); - + println!("\n--- Accessing Specific Fields ---"); println!("Contract Title: {}", contract.title); println!("Contract Status: {:?}", contract.status); @@ -97,7 +97,10 @@ fn main() { println!("Updated At (timestamp): {}", contract.base_data.modified_at); // From BaseModelData if let Some(first_signer_details) = contract.signers.first() { - println!("\nFirst Signer: {} ({})", first_signer_details.name, first_signer_details.email); + println!( + "\nFirst Signer: {} ({})", + first_signer_details.name, first_signer_details.email + ); println!(" Status: {:?}", first_signer_details.status); if let Some(signed_time) = first_signer_details.signed_at { println!(" Signed At: {}", signed_time); @@ -110,6 +113,6 @@ fn main() { println!(" Created By: {}", latest_rev.created_by); println!(" Revision Created At: {}", latest_rev.created_at); } - + println!("\nLegal Contract Model demonstration complete."); } diff --git a/heromodels/examples/legal_rhai/example.rs b/heromodels/examples/legal_rhai/example.rs index b5a3e53..0b42f0d 100644 --- a/heromodels/examples/legal_rhai/example.rs +++ b/heromodels/examples/legal_rhai/example.rs @@ -22,13 +22,18 @@ fn main() -> Result<(), Box> { if !script_path.exists() { eprintln!("Error: Rhai script not found at {}", script_path_str); - eprintln!("Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory."); - return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str)))); + eprintln!( + "Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory." + ); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Rhai script not found: {}", script_path_str), + ))); } println!("Executing Rhai script: {}", script_path_str); let script = fs::read_to_string(script_path)?; - + match engine.eval::<()>(&script) { Ok(_) => println!("\nRhai script executed successfully!"), Err(e) => { diff --git a/heromodels/examples/library_rhai/example.rs b/heromodels/examples/library_rhai/example.rs index 08b41e0..13955f6 100644 --- a/heromodels/examples/library_rhai/example.rs +++ b/heromodels/examples/library_rhai/example.rs @@ -33,6 +33,5 @@ fn main() -> Result<(), Box> { fs::remove_dir_all(db_path)?; println!("--- Cleaned up temporary database. ---"); - Ok(()) } diff --git a/heromodels/examples/model_macro_example.rs b/heromodels/examples/model_macro_example.rs index 3e954ee..d43487a 100644 --- a/heromodels/examples/model_macro_example.rs +++ b/heromodels/examples/model_macro_example.rs @@ -59,21 +59,39 @@ fn main() { println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); println!("\nBefore saving - CustomUser ID: {}", custom_user.get_id()); - println!("Before saving - CustomUser DB Keys: {:?}", custom_user.db_keys()); + println!( + "Before saving - CustomUser DB Keys: {:?}", + custom_user.db_keys() + ); // Save the models to the database - let simple_collection = db.collection::().expect("can open simple user collection"); - let custom_collection = db.collection::().expect("can open custom user collection"); + let simple_collection = db + .collection::() + .expect("can open simple user collection"); + let custom_collection = db + .collection::() + .expect("can open custom user collection"); let (user_id, saved_user) = simple_collection.set(&user).expect("can save simple user"); - let (custom_user_id, saved_custom_user) = custom_collection.set(&custom_user).expect("can save custom user"); + let (custom_user_id, saved_custom_user) = custom_collection + .set(&custom_user) + .expect("can save custom user"); println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); - println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - SimpleUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned SimpleUser ID: {}", user_id); - println!("\nAfter saving - CustomUser ID: {}", saved_custom_user.get_id()); - println!("After saving - CustomUser DB Keys: {:?}", saved_custom_user.db_keys()); + println!( + "\nAfter saving - CustomUser ID: {}", + saved_custom_user.get_id() + ); + println!( + "After saving - CustomUser DB Keys: {:?}", + saved_custom_user.db_keys() + ); println!("Returned CustomUser ID: {}", custom_user_id); // Verify that the IDs were auto-generated @@ -83,5 +101,8 @@ fn main() { assert_ne!(saved_custom_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } diff --git a/heromodels/examples/project_rhai/example.rs b/heromodels/examples/project_rhai/example.rs index 08ac43b..d70bf0e 100644 --- a/heromodels/examples/project_rhai/example.rs +++ b/heromodels/examples/project_rhai/example.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, Scope}; -use std::sync::Arc; use heromodels::db::hero::OurDB; use heromodels::models::projects::register_projects_rhai_module; +use rhai::{Engine, EvalAltResult, Scope}; use std::fs; +use std::sync::Arc; fn main() -> Result<(), Box> { println!("Executing Rhai script: examples/project_rhai/project_test.rhai"); @@ -18,8 +18,12 @@ fn main() -> Result<(), Box> { // Read the Rhai script from file let script_path = "examples/project_rhai/project_test.rhai"; - let script_content = fs::read_to_string(script_path) - .map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?; + let script_content = fs::read_to_string(script_path).map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + format!("Cannot read script file: {}", script_path), + e.into(), + )) + })?; // Create a new scope let mut scope = Scope::new(); diff --git a/heromodels/examples/simple_model_example.rs b/heromodels/examples/simple_model_example.rs index fa53b4b..9981899 100644 --- a/heromodels/examples/simple_model_example.rs +++ b/heromodels/examples/simple_model_example.rs @@ -34,11 +34,16 @@ fn main() { println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); // Save the user to the database - let collection = db.collection::().expect("can open user collection"); + let collection = db + .collection::() + .expect("can open user collection"); let (user_id, saved_user) = collection.set(&user).expect("can save user"); println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); - println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!( + "After saving - SimpleUser DB Keys: {:?}", + saved_user.db_keys() + ); println!("Returned ID: {}", user_id); // Verify that the ID was auto-generated @@ -46,6 +51,8 @@ fn main() { assert_ne!(saved_user.get_id(), 0); println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); } - diff --git a/heromodels/src/db.rs b/heromodels/src/db.rs index e5736fd..5691e11 100644 --- a/heromodels/src/db.rs +++ b/heromodels/src/db.rs @@ -57,7 +57,9 @@ where fn get_all(&self) -> Result, Error>; /// Begin a transaction for this collection - fn begin_transaction(&self) -> Result>, Error>; + fn begin_transaction( + &self, + ) -> Result>, Error>; } /// Errors returned by the DB implementation diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index dff28b1..61beb2f 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -436,8 +436,12 @@ where Ok(list_of_raw_ids_set_bytes) => { for raw_ids_set_bytes in list_of_raw_ids_set_bytes { // Each item in the list is a bincode-serialized HashSet of object IDs. - match bincode::serde::decode_from_slice::, _>(&raw_ids_set_bytes, BINCODE_CONFIG) { - Ok((ids_set, _)) => { // Destructure the tuple (HashSet, usize) + match bincode::serde::decode_from_slice::, _>( + &raw_ids_set_bytes, + BINCODE_CONFIG, + ) { + Ok((ids_set, _)) => { + // Destructure the tuple (HashSet, usize) all_object_ids.extend(ids_set); } Err(e) => { diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs index 560301e..bdc4907 100644 --- a/heromodels/src/models/access/access.rs +++ b/heromodels/src/models/access/access.rs @@ -55,4 +55,4 @@ impl Access { self.expires_at = expires_at; self } -} \ No newline at end of file +} diff --git a/heromodels/src/models/access/mod.rs b/heromodels/src/models/access/mod.rs index a891652..4ecbdc2 100644 --- a/heromodels/src/models/access/mod.rs +++ b/heromodels/src/models/access/mod.rs @@ -3,5 +3,5 @@ pub mod access; pub mod rhai; // Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs -pub use self::access::{Access}; +pub use self::access::Access; pub use rhai::register_access_rhai_module; diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs index 0ae4d38..307b417 100644 --- a/heromodels/src/models/access/rhai.rs +++ b/heromodels/src/models/access/rhai.rs @@ -1,22 +1,22 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::access::{Access}; +use super::access::Access; type RhaiAccess = Access; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -27,13 +27,16 @@ mod rhai_access_module { let access = Access::new(); Ok(access) } - + /// Sets the access name #[rhai_fn(name = "object_id", return_raw, global, pure)] - pub fn access_object_id(access: &mut RhaiAccess, object_id: u32) -> Result> { + pub fn access_object_id( + access: &mut RhaiAccess, + object_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.object_id(object_id); @@ -41,21 +44,27 @@ mod rhai_access_module { } #[rhai_fn(name = "circle_id", return_raw, global, pure)] - pub fn access_circle_id(access: &mut RhaiAccess, circle_id: u32) -> Result> { + pub fn access_circle_id( + access: &mut RhaiAccess, + circle_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.circle_id(circle_id); Ok(access.clone()) } - + #[rhai_fn(name = "group_id", return_raw, global, pure)] - pub fn access_group_id(access: &mut RhaiAccess, group_id: u32) -> Result> { + pub fn access_group_id( + access: &mut RhaiAccess, + group_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.group_id(group_id); @@ -63,10 +72,13 @@ mod rhai_access_module { } #[rhai_fn(name = "contact_id", return_raw, global, pure)] - pub fn access_contact_id(access: &mut RhaiAccess, contact_id: u32) -> Result> { + pub fn access_contact_id( + access: &mut RhaiAccess, + contact_id: u32, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.contact_id(contact_id); @@ -74,10 +86,13 @@ mod rhai_access_module { } #[rhai_fn(name = "expires_at", return_raw, global, pure)] - pub fn access_expires_at(access: &mut RhaiAccess, expires_at: Option) -> Result> { + pub fn access_expires_at( + access: &mut RhaiAccess, + expires_at: Option, + ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); - + // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); *access = owned_access.expires_at(expires_at); @@ -86,90 +101,136 @@ mod rhai_access_module { // Access Getters #[rhai_fn(get = "id", pure)] - pub fn get_access_id(access: &mut RhaiAccess) -> i64 { access.base_data.id as i64 } - + pub fn get_access_id(access: &mut RhaiAccess) -> i64 { + access.base_data.id as i64 + } + #[rhai_fn(get = "object_id", pure)] - pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { access.object_id as i64 } + pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { + access.object_id as i64 + } #[rhai_fn(get = "circle_id", pure)] - pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { access.circle_id as i64 } + pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { + access.circle_id as i64 + } #[rhai_fn(get = "group_id", pure)] - pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { access.group_id as i64 } + pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { + access.group_id as i64 + } #[rhai_fn(get = "contact_id", pure)] - pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { access.contact_id as i64 } + pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { + access.contact_id as i64 + } #[rhai_fn(get = "expires_at", pure)] - pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { access.expires_at.unwrap_or(0) as i64 } + pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { + access.expires_at.unwrap_or(0) as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { access.base_data.created_at } - + pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { + access.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { access.base_data.modified_at } + pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { + access.base_data.modified_at + } } pub fn register_access_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_access_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); - + let db_clone_set_access = db.clone(); - db_module.set_native_fn("save_access", move |access: Access| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_access.set(&access) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_access: {}", e).into(), Position::NONE)))?; - - // Return the updated access with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_access", + move |access: Access| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_access.set(&access).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_access: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated access with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_access = db.clone(); - db_module.set_native_fn("delete_access", move |access: Access| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_access.collection::() - .expect("can open access collection") - .delete_by_id(access.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_access", + move |access: Access| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_access + .collection::() + .expect("can open access collection") + .delete_by_id(access.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_access = db.clone(); - db_module.set_native_fn("get_access_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_access.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_access_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Access with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_access_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_access + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_access_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Access with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_accesss function to get all accesss let db_clone_list_accesss = db.clone(); - db_module.set_native_fn("list_accesss", move || -> Result> { - let collection = db_clone_list_accesss.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get access collection: {:?}", e).into(), - Position::NONE - )))?; - let accesss = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all accesss: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for access in accesss { - array.push(Dynamic::from(access)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_accesss", + move || -> Result> { + let collection = db_clone_list_accesss.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get access collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let accesss = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all accesss: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for access in accesss { + array.push(Dynamic::from(access)); + } + Ok(Dynamic::from(array)) + }, + ); + // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index 7393f2e..d5fe452 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Index}; -use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] use heromodels_core::BaseModelDataOps; +use heromodels_core::{BaseModelData, Index}; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] +use serde::{Deserialize, Serialize}; // --- Enums --- @@ -100,17 +100,17 @@ impl Company { status: CompanyStatus::default(), } } - + pub fn name(mut self, name: impl ToString) -> Self { self.name = name.to_string(); self } - + pub fn registration_number(mut self, registration_number: impl ToString) -> Self { self.registration_number = registration_number.to_string(); self } - + pub fn incorporation_date(mut self, incorporation_date: i64) -> Self { self.incorporation_date = incorporation_date; self diff --git a/heromodels/src/models/biz/mod.rs b/heromodels/src/models/biz/mod.rs index 27a1718..abadf00 100644 --- a/heromodels/src/models/biz/mod.rs +++ b/heromodels/src/models/biz/mod.rs @@ -8,17 +8,16 @@ pub mod product; // pub mod user; // Re-export main types from sub-modules -pub use company::{Company, CompanyStatus, BusinessType}; +pub use company::{BusinessType, Company, CompanyStatus}; pub mod shareholder; +pub use product::{Product, ProductComponent, ProductStatus, ProductType}; pub use shareholder::{Shareholder, ShareholderType}; -pub use product::{Product, ProductType, ProductStatus, ProductComponent}; pub mod sale; pub use sale::{Sale, SaleItem, SaleStatus}; // pub use user::{User}; // Assuming a simple User model for now - #[cfg(feature = "rhai")] pub mod rhai; #[cfg(feature = "rhai")] diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index 0d9f3d0..c282918 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use serde::{Deserialize, Serialize}; // ProductType represents the type of a product #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -137,7 +137,7 @@ impl Product { self.components.push(component); self } - + pub fn components(mut self, components: Vec) -> Self { self.components = components; self diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs index 6fe6081..22ef6d8 100644 --- a/heromodels/src/models/biz/rhai.rs +++ b/heromodels/src/models/biz/rhai.rs @@ -1,15 +1,15 @@ -use rhai::plugin::*; -use rhai::{Engine, Module, Dynamic, EvalAltResult, Position, INT}; -use std::sync::Arc; -use std::mem; use crate::db::Collection; // For db.set and db.get_by_id -use crate::db::hero::OurDB; use crate::db::Db; +use crate::db::hero::OurDB; +use rhai::plugin::*; +use rhai::{Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::company::{Company, CompanyStatus, BusinessType}; -use crate::models::biz::shareholder::{Shareholder, ShareholderType}; -use crate::models::biz::product::{Product, ProductType, ProductStatus, ProductComponent}; +use super::company::{BusinessType, Company, CompanyStatus}; +use crate::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType}; use crate::models::biz::sale::{Sale, SaleItem, SaleStatus}; +use crate::models::biz::shareholder::{Shareholder, ShareholderType}; use heromodels_core::Model; type RhaiCompany = Company; @@ -21,12 +21,12 @@ type RhaiSaleItem = SaleItem; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -36,443 +36,541 @@ mod rhai_biz_module { pub fn new_company() -> RhaiCompany { Company::new() } - + // Company builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn company_name(company: &mut RhaiCompany, name: String) -> Result> { + pub fn company_name( + company: &mut RhaiCompany, + name: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.name(name); Ok(company.clone()) } - + #[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)] - pub fn company_fiscal_year_end(company: &mut RhaiCompany, fiscal_year_end: String) -> Result> { + pub fn company_fiscal_year_end( + company: &mut RhaiCompany, + fiscal_year_end: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.fiscal_year_end(fiscal_year_end); Ok(company.clone()) } - + #[rhai_fn(name = "registration_number", return_raw, global, pure)] - pub fn company_registration_number(company: &mut RhaiCompany, reg_num: String) -> Result> { + pub fn company_registration_number( + company: &mut RhaiCompany, + reg_num: String, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.registration_number(reg_num); Ok(company.clone()) } - + #[rhai_fn(name = "incorporation_date", return_raw, global, pure)] - pub fn company_incorporation_date(company: &mut RhaiCompany, date: i64) -> Result> { + pub fn company_incorporation_date( + company: &mut RhaiCompany, + date: i64, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.incorporation_date(date); Ok(company.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn company_status(company: &mut RhaiCompany, status: CompanyStatus) -> Result> { + pub fn company_status( + company: &mut RhaiCompany, + status: CompanyStatus, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.status(status); Ok(company.clone()) } - + #[rhai_fn(name = "business_type", return_raw, global, pure)] - pub fn company_business_type(company: &mut RhaiCompany, business_type: BusinessType) -> Result> { + pub fn company_business_type( + company: &mut RhaiCompany, + business_type: BusinessType, + ) -> Result> { let owned_company = mem::take(company); *company = owned_company.business_type(business_type); Ok(company.clone()) } - + // Company getters #[rhai_fn(name = "get_company_id")] pub fn get_company_id(company: &mut RhaiCompany) -> i64 { company.get_id() as i64 } - + #[rhai_fn(name = "get_company_name")] pub fn get_company_name(company: &mut RhaiCompany) -> String { company.name.clone() } - + #[rhai_fn(name = "get_company_created_at")] pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 { company.base_data.created_at } - + #[rhai_fn(name = "get_company_modified_at")] pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 { company.base_data.modified_at } - + #[rhai_fn(name = "get_company_registration_number")] pub fn get_company_registration_number(company: &mut RhaiCompany) -> String { company.registration_number.clone() } - + #[rhai_fn(name = "get_company_fiscal_year_end")] pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String { company.fiscal_year_end.clone() } - + #[rhai_fn(name = "get_company_incorporation_date")] pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 { company.incorporation_date } - + #[rhai_fn(name = "get_company_status")] pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus { company.status.clone() } - + #[rhai_fn(name = "get_company_business_type")] pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType { company.business_type.clone() } - + // --- Shareholder Functions --- #[rhai_fn(name = "new_shareholder")] pub fn new_shareholder() -> RhaiShareholder { Shareholder::new() } - + // Shareholder builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn shareholder_name(shareholder: &mut RhaiShareholder, name: String) -> Result> { + pub fn shareholder_name( + shareholder: &mut RhaiShareholder, + name: String, + ) -> Result> { let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.name(name); Ok(shareholder.clone()) } - + #[rhai_fn(name = "company_id", return_raw, global, pure)] - pub fn shareholder_company_id(shareholder: &mut RhaiShareholder, company_id: i64) -> Result> { + pub fn shareholder_company_id( + shareholder: &mut RhaiShareholder, + company_id: i64, + ) -> Result> { let company_id_u32 = id_from_i64_to_u32(company_id)?; let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.company_id(company_id_u32); Ok(shareholder.clone()) } - + #[rhai_fn(name = "share_count", return_raw, global, pure)] - pub fn shareholder_share_count(shareholder: &mut RhaiShareholder, share_count: f64) -> Result> { - let owned_shareholder = mem::take(shareholder); + pub fn shareholder_share_count( + shareholder: &mut RhaiShareholder, + share_count: f64, + ) -> Result> { shareholder.shares = share_count; Ok(shareholder.clone()) } - + #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn shareholder_type(shareholder: &mut RhaiShareholder, type_: ShareholderType) -> Result> { + pub fn shareholder_type( + shareholder: &mut RhaiShareholder, + type_: ShareholderType, + ) -> Result> { let owned_shareholder = mem::take(shareholder); *shareholder = owned_shareholder.type_(type_); Ok(shareholder.clone()) } - + // Shareholder getters #[rhai_fn(name = "get_shareholder_id")] pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 { shareholder.get_id() as i64 } - + #[rhai_fn(name = "get_shareholder_name")] pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String { shareholder.name.clone() } - + #[rhai_fn(name = "get_shareholder_company_id")] pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 { shareholder.company_id as i64 } - + #[rhai_fn(name = "get_shareholder_share_count")] pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 { shareholder.shares as i64 } - + #[rhai_fn(name = "get_shareholder_type")] pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType { shareholder.type_.clone() } - + // --- ProductComponent Functions --- #[rhai_fn(name = "new_product_component")] pub fn new_product_component() -> RhaiProductComponent { ProductComponent::new() } - + // ProductComponent builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_component_name(component: &mut RhaiProductComponent, name: String) -> Result> { + pub fn product_component_name( + component: &mut RhaiProductComponent, + name: String, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.name(name); Ok(component.clone()) } - + #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_component_description(component: &mut RhaiProductComponent, description: String) -> Result> { + pub fn product_component_description( + component: &mut RhaiProductComponent, + description: String, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.description(description); Ok(component.clone()) } - + #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn product_component_quantity(component: &mut RhaiProductComponent, quantity: i64) -> Result> { + pub fn product_component_quantity( + component: &mut RhaiProductComponent, + quantity: i64, + ) -> Result> { let owned_component = mem::take(component); *component = owned_component.quantity(quantity as u32); Ok(component.clone()) } - + // ProductComponent getters #[rhai_fn(name = "get_product_component_name")] pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String { component.name.clone() } - + #[rhai_fn(name = "get_product_component_description")] pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String { component.description.clone() } - + #[rhai_fn(name = "get_product_component_quantity")] pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 { component.quantity as i64 } - + // --- Product Functions --- #[rhai_fn(name = "new_product")] pub fn new_product() -> RhaiProduct { Product::new() } - + // Product builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_name(product: &mut RhaiProduct, name: String) -> Result> { + pub fn product_name( + product: &mut RhaiProduct, + name: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.name(name); Ok(product.clone()) } - + #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_description(product: &mut RhaiProduct, description: String) -> Result> { + pub fn product_description( + product: &mut RhaiProduct, + description: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.description(description); Ok(product.clone()) } - + #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn product_price(product: &mut RhaiProduct, price: f64) -> Result> { + pub fn product_price( + product: &mut RhaiProduct, + price: f64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.price(price); Ok(product.clone()) } - + #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn product_type(product: &mut RhaiProduct, type_: ProductType) -> Result> { + pub fn product_type( + product: &mut RhaiProduct, + type_: ProductType, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.type_(type_); Ok(product.clone()) } - + #[rhai_fn(name = "category", return_raw, global, pure)] - pub fn product_category(product: &mut RhaiProduct, category: String) -> Result> { + pub fn product_category( + product: &mut RhaiProduct, + category: String, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.category(category); Ok(product.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn product_status(product: &mut RhaiProduct, status: ProductStatus) -> Result> { + pub fn product_status( + product: &mut RhaiProduct, + status: ProductStatus, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.status(status); Ok(product.clone()) } - + #[rhai_fn(name = "max_amount", return_raw, global, pure)] - pub fn product_max_amount(product: &mut RhaiProduct, max_amount: i64) -> Result> { + pub fn product_max_amount( + product: &mut RhaiProduct, + max_amount: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.max_amount(max_amount as u16); Ok(product.clone()) } - + #[rhai_fn(name = "purchase_till", return_raw, global, pure)] - pub fn product_purchase_till(product: &mut RhaiProduct, purchase_till: i64) -> Result> { + pub fn product_purchase_till( + product: &mut RhaiProduct, + purchase_till: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.purchase_till(purchase_till); Ok(product.clone()) } - + #[rhai_fn(name = "active_till", return_raw, global, pure)] - pub fn product_active_till(product: &mut RhaiProduct, active_till: i64) -> Result> { + pub fn product_active_till( + product: &mut RhaiProduct, + active_till: i64, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.active_till(active_till); Ok(product.clone()) } - + #[rhai_fn(name = "add_component", return_raw, global, pure)] - pub fn product_add_component(product: &mut RhaiProduct, component: RhaiProductComponent) -> Result> { + pub fn product_add_component( + product: &mut RhaiProduct, + component: RhaiProductComponent, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.add_component(component); Ok(product.clone()) } - + #[rhai_fn(name = "components", return_raw, global, pure)] - pub fn product_components(product: &mut RhaiProduct, components: Vec) -> Result> { + pub fn product_components( + product: &mut RhaiProduct, + components: Vec, + ) -> Result> { let owned_product = mem::take(product); *product = owned_product.components(components); Ok(product.clone()) } - + // Product getters #[rhai_fn(name = "get_product_id")] pub fn get_product_id(product: &mut RhaiProduct) -> i64 { product.get_id() as i64 } - + #[rhai_fn(name = "get_product_name")] pub fn get_product_name(product: &mut RhaiProduct) -> String { product.name.clone() } - + #[rhai_fn(name = "get_product_description")] pub fn get_product_description(product: &mut RhaiProduct) -> String { product.description.clone() } - + #[rhai_fn(name = "get_product_price")] pub fn get_product_price(product: &mut RhaiProduct) -> f64 { product.price } - + #[rhai_fn(name = "get_product_type")] pub fn get_product_type(product: &mut RhaiProduct) -> ProductType { product.type_.clone() } - + #[rhai_fn(name = "get_product_category")] pub fn get_product_category(product: &mut RhaiProduct) -> String { product.category.clone() } - + #[rhai_fn(name = "get_product_status")] pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus { product.status.clone() } - + #[rhai_fn(name = "get_product_max_amount")] pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 { product.max_amount as i64 } - + #[rhai_fn(name = "get_product_purchase_till")] pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 { product.purchase_till } - + #[rhai_fn(name = "get_product_active_till")] pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 { product.active_till } - + #[rhai_fn(name = "get_product_components")] pub fn get_product_components(product: &mut RhaiProduct) -> Vec { product.components.clone() } - + #[rhai_fn(name = "get_product_created_at")] pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 { product.base_data.created_at } - + #[rhai_fn(name = "get_product_modified_at")] pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 { product.base_data.modified_at } - + #[rhai_fn(name = "get_product_comments")] pub fn get_product_comments(product: &mut RhaiProduct) -> Vec { - product.base_data.comments.iter().map(|&id| id as i64).collect() + product + .base_data + .comments + .iter() + .map(|&id| id as i64) + .collect() } - + // --- SaleItem Functions --- #[rhai_fn(name = "new_sale_item")] pub fn new_sale_item() -> RhaiSaleItem { SaleItem::new() } - + // SaleItem builder methods #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn sale_item_name(item: &mut RhaiSaleItem, name: String) -> Result> { + pub fn sale_item_name( + item: &mut RhaiSaleItem, + name: String, + ) -> Result> { let owned_item = mem::take(item); *item = owned_item.name(name); Ok(item.clone()) } - + #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn sale_item_price(item: &mut RhaiSaleItem, price: f64) -> Result> { - let owned_item = mem::take(item); + pub fn sale_item_price( + item: &mut RhaiSaleItem, + price: f64, + ) -> Result> { item.unit_price = price; Ok(item.clone()) } - + #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn sale_item_quantity(item: &mut RhaiSaleItem, quantity: i64) -> Result> { + pub fn sale_item_quantity( + item: &mut RhaiSaleItem, + quantity: i64, + ) -> Result> { let owned_item = mem::take(item); *item = owned_item.quantity(quantity.try_into().unwrap()); Ok(item.clone()) } - + #[rhai_fn(name = "product_id", return_raw, global, pure)] - pub fn sale_item_product_id(item: &mut RhaiSaleItem, product_id: i64) -> Result> { + pub fn sale_item_product_id( + item: &mut RhaiSaleItem, + product_id: i64, + ) -> Result> { let product_id_u32 = id_from_i64_to_u32(product_id)?; let owned_item = mem::take(item); *item = owned_item.product_id(product_id_u32); Ok(item.clone()) } - + // SaleItem getters #[rhai_fn(name = "get_sale_item_name")] pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String { item.name.clone() } - + #[rhai_fn(name = "get_sale_item_price")] pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 { item.unit_price } - + #[rhai_fn(name = "get_sale_item_quantity")] pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 { item.quantity as i64 } - + #[rhai_fn(name = "get_sale_item_product_id")] pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 { item.product_id as i64 } - + // --- Sale Functions --- #[rhai_fn(name = "new_sale")] pub fn new_sale() -> RhaiSale { Sale::new() } - + #[rhai_fn(name = "transaction_id", return_raw, global, pure)] - pub fn sale_transaction_id(sale: &mut RhaiSale, transaction_id: u32) -> Result> { - let owned_sale = mem::take(sale); + pub fn sale_transaction_id( + sale: &mut RhaiSale, + transaction_id: u32, + ) -> Result> { sale.transaction_id = transaction_id; Ok(sale.clone()) } - + #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn sale_status(sale: &mut RhaiSale, status: SaleStatus) -> Result> { + pub fn sale_status( + sale: &mut RhaiSale, + status: SaleStatus, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.status(status); Ok(sale.clone()) } - + #[rhai_fn(name = "add_item", return_raw, global, pure)] - pub fn sale_add_item(sale: &mut RhaiSale, item: RhaiSaleItem) -> Result> { + pub fn sale_add_item( + sale: &mut RhaiSale, + item: RhaiSaleItem, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.add_item(item); Ok(sale.clone()) } - + #[rhai_fn(name = "items", return_raw, global, pure)] - pub fn sale_items(sale: &mut RhaiSale, items: Vec) -> Result> { + pub fn sale_items( + sale: &mut RhaiSale, + items: Vec, + ) -> Result> { let owned_sale = mem::take(sale); *sale = owned_sale.items(items); Ok(sale.clone()) @@ -483,35 +581,39 @@ mod rhai_biz_module { pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { sale.get_id() as i64 } - + #[rhai_fn(name = "get_sale_transaction_id")] pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 { sale.transaction_id } - + #[rhai_fn(name = "get_sale_status")] pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus { sale.status.clone() } - + #[rhai_fn(name = "get_sale_items")] pub fn get_sale_items(sale: &mut RhaiSale) -> Vec { sale.items.clone() } - + #[rhai_fn(name = "get_sale_created_at")] pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 { sale.base_data.created_at } - + #[rhai_fn(name = "get_sale_modified_at")] pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 { sale.base_data.modified_at } - + #[rhai_fn(name = "get_sale_comments")] pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec { - sale.base_data.comments.iter().map(|&id| id as i64).collect() + sale.base_data + .comments + .iter() + .map(|&id| id as i64) + .collect() } } @@ -519,110 +621,174 @@ pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_biz_module); engine.register_global_module(module.into()); - + // Create a new module for database operations let mut db_module = Module::new(); // Database operations will obtain fresh collection handles directly. - + // Add database functions for Company let db_for_set_company = Arc::clone(&db); - db_module.set_native_fn("set_company", move |company: Company| -> Result> { - let company_collection_set = db_for_set_company.collection::().expect("Failed to get company collection for set in closure"); - company_collection_set.set(&company) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save company: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_company", + move |company: Company| -> Result> { + let company_collection_set = db_for_set_company + .collection::() + .expect("Failed to get company collection for set in closure"); + company_collection_set + .set(&company) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save company: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_company = Arc::clone(&db); - db_module.set_native_fn("get_company_by_id", move |id: INT| -> Result> { - let company_collection_get = db_for_get_company.collection::().expect("Failed to get company collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - company_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get company with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_company_by_id", + move |id: INT| -> Result> { + let company_collection_get = db_for_get_company + .collection::() + .expect("Failed to get company collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + company_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get company with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Shareholder let db_for_set_shareholder = Arc::clone(&db); - db_module.set_native_fn("set_shareholder", move |shareholder: Shareholder| -> Result> { - let shareholder_collection_set = db_for_set_shareholder.collection::().expect("Failed to get shareholder collection for set in closure"); - shareholder_collection_set.set(&shareholder) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save shareholder: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_shareholder", + move |shareholder: Shareholder| -> Result> { + let shareholder_collection_set = db_for_set_shareholder + .collection::() + .expect("Failed to get shareholder collection for set in closure"); + shareholder_collection_set + .set(&shareholder) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save shareholder: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_shareholder = Arc::clone(&db); - db_module.set_native_fn("get_shareholder_by_id", move |id: INT| -> Result> { - let shareholder_collection_get = db_for_get_shareholder.collection::().expect("Failed to get shareholder collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - shareholder_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get shareholder with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_shareholder_by_id", + move |id: INT| -> Result> { + let shareholder_collection_get = db_for_get_shareholder + .collection::() + .expect("Failed to get shareholder collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + shareholder_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get shareholder with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Product let db_for_set_product = Arc::clone(&db); - db_module.set_native_fn("set_product", move |product: Product| -> Result> { - let product_collection_set = db_for_set_product.collection::().expect("Failed to get product collection for set in closure"); - product_collection_set.set(&product) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save product: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_product", + move |product: Product| -> Result> { + let product_collection_set = db_for_set_product + .collection::() + .expect("Failed to get product collection for set in closure"); + product_collection_set + .set(&product) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save product: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_product = Arc::clone(&db); - db_module.set_native_fn("get_product_by_id", move |id: INT| -> Result> { - let product_collection_get = db_for_get_product.collection::().expect("Failed to get product collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - product_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get product with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "get_product_by_id", + move |id: INT| -> Result> { + let product_collection_get = db_for_get_product + .collection::() + .expect("Failed to get product collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + product_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get product with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); + // Add database functions for Sale let db_for_set_sale = Arc::clone(&db); - db_module.set_native_fn("set_sale", move |sale: Sale| -> Result> { - let sale_collection_set = db_for_set_sale.collection::().expect("Failed to get sale collection for set in closure"); - sale_collection_set.set(&sale) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save sale: {:?}", e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "set_sale", + move |sale: Sale| -> Result> { + let sale_collection_set = db_for_set_sale + .collection::() + .expect("Failed to get sale collection for set in closure"); + sale_collection_set + .set(&sale) + .map(|(id_val, _)| id_val as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save sale: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); + let db_for_get_sale = Arc::clone(&db); - db_module.set_native_fn("get_sale_by_id", move |id: INT| -> Result> { - let sale_collection_get = db_for_get_sale.collection::().expect("Failed to get sale collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - sale_collection_get.get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get sale with id {}: {:?}", id, e).into(), - Position::NONE - ))) - }); + db_module.set_native_fn( + "get_sale_by_id", + move |id: INT| -> Result> { + let sale_collection_get = db_for_get_sale + .collection::() + .expect("Failed to get sale collection for get in closure"); + let id_u32 = id_from_i64_to_u32(id)?; + sale_collection_get + .get_by_id(id_u32) + .map(Dynamic::from) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get sale with id {}: {:?}", id, e).into(), + Position::NONE, + )) + }) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); - + println!("Successfully registered biz Rhai module using export_module approach."); } diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index a699329..938f58d 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -1,5 +1,5 @@ +use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; /// Represents the status of a sale. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index e6af823..ce7b58a 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ShareholderType { @@ -31,13 +31,13 @@ impl Shareholder { pub fn new() -> Self { Self { base_data: BaseModelData::new(), - company_id: 0, // Default, to be set by builder - user_id: 0, // Default, to be set by builder - name: String::new(), // Default - shares: 0.0, // Default - percentage: 0.0, // Default + company_id: 0, // Default, to be set by builder + user_id: 0, // Default, to be set by builder + name: String::new(), // Default + shares: 0.0, // Default + percentage: 0.0, // Default type_: ShareholderType::default(), // Uses ShareholderType's Default impl - since: 0, // Default timestamp, to be set by builder + since: 0, // Default timestamp, to be set by builder } } @@ -78,4 +78,4 @@ impl Shareholder { } // Base data operations are now handled by BaseModelDataOps trait -} \ No newline at end of file +} diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index b9317a4..64edfa3 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -26,7 +26,7 @@ impl AttendanceStatus { _ => Err(format!("Invalid attendance status: '{}'", s)), } } - + /// Convert an AttendanceStatus to a string pub fn to_string(&self) -> String { match self { @@ -134,7 +134,11 @@ impl Event { /// Adds an attendee to the event pub fn add_attendee(mut self, attendee: Attendee) -> Self { // Prevent duplicate attendees by contact_id - if !self.attendees.iter().any(|a| a.contact_id == attendee.contact_id) { + if !self + .attendees + .iter() + .any(|a| a.contact_id == attendee.contact_id) + { self.attendees.push(attendee); } self @@ -148,18 +152,18 @@ impl Event { /// Updates the status of an existing attendee pub fn update_attendee_status(mut self, contact_id: u32, status: AttendanceStatus) -> Self { - if let Some(attendee) = self.attendees.iter_mut().find(|a| a.contact_id == contact_id) { + if let Some(attendee) = self + .attendees + .iter_mut() + .find(|a| a.contact_id == contact_id) + { attendee.status = status; } self } /// Reschedules the event to new start and end times - pub fn reschedule( - mut self, - new_start_time: i64, - new_end_time: i64, - ) -> Self { + pub fn reschedule(mut self, new_start_time: i64, new_end_time: i64) -> Self { // Basic validation: end_time should be after start_time if new_end_time > new_start_time { self.start_time = new_start_time; @@ -236,7 +240,8 @@ impl Calendar { /// Removes an event from the calendar by its ID pub fn remove_event(mut self, event_id_to_remove: i64) -> Self { - self.events.retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove); + self.events + .retain(|&event_id_in_vec| event_id_in_vec != event_id_to_remove); self } } diff --git a/heromodels/src/models/calendar/mod.rs b/heromodels/src/models/calendar/mod.rs index fb0216a..fc7beb4 100644 --- a/heromodels/src/models/calendar/mod.rs +++ b/heromodels/src/models/calendar/mod.rs @@ -3,5 +3,5 @@ pub mod calendar; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::calendar::{Calendar, Event, Attendee, AttendanceStatus}; +pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event}; pub use rhai::register_calendar_rhai_module; diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index 6e754c3..a8e6af7 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -1,24 +1,24 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::calendar::{Event, Attendee, Calendar, AttendanceStatus}; +use super::calendar::{AttendanceStatus, Attendee, Calendar, Event}; type RhaiEvent = Event; type RhaiAttendee = Attendee; type RhaiCalendar = Calendar; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -28,26 +28,35 @@ mod rhai_calendar_module { pub fn new_event() -> RhaiEvent { Event::new() } - + /// Sets the event title #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn event_title(event: &mut RhaiEvent, title: String) -> Result> { + pub fn event_title( + event: &mut RhaiEvent, + title: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.title(title); Ok(event.clone()) } - + /// Sets the event description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn event_description(event: &mut RhaiEvent, description: String) -> Result> { + pub fn event_description( + event: &mut RhaiEvent, + description: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.description(description); Ok(event.clone()) } - + /// Sets the event location #[rhai_fn(name = "location", return_raw, global, pure)] - pub fn event_location(event: &mut RhaiEvent, location: String) -> Result> { + pub fn event_location( + event: &mut RhaiEvent, + location: String, + ) -> Result> { let owned_event = mem::take(event); *event = owned_event.location(location); Ok(event.clone()) @@ -55,7 +64,10 @@ mod rhai_calendar_module { /// Adds an attendee to the event #[rhai_fn(name = "add_attendee", return_raw, global, pure)] - pub fn event_add_attendee(event: &mut RhaiEvent, attendee: RhaiAttendee) -> Result> { + pub fn event_add_attendee( + event: &mut RhaiEvent, + attendee: RhaiAttendee, + ) -> Result> { // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.add_attendee(attendee); @@ -64,30 +76,38 @@ mod rhai_calendar_module { /// Reschedules the event with new start and end times #[rhai_fn(name = "reschedule", return_raw, global, pure)] - pub fn event_reschedule(event: &mut RhaiEvent, new_start_time: i64, new_end_time: i64) -> Result> { + pub fn event_reschedule( + event: &mut RhaiEvent, + new_start_time: i64, + new_end_time: i64, + ) -> Result> { // Validate timestamps if new_end_time <= new_start_time { return Err(Box::new(EvalAltResult::ErrorRuntime( "End time must be after start time".into(), - Position::NONE + Position::NONE, ))); } - + // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.reschedule(new_start_time, new_end_time); Ok(event.clone()) } - + /// Updates an attendee's status in the event #[rhai_fn(name = "update_attendee_status", return_raw, global, pure)] - pub fn event_update_attendee_status(event: &mut RhaiEvent, contact_id: i64, status_str: String) -> Result> { + pub fn event_update_attendee_status( + event: &mut RhaiEvent, + contact_id: i64, + status_str: String, + ) -> Result> { let status_enum = AttendanceStatus::from_string(&status_str) .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(), Position::NONE )))?; - + // Use take to get ownership of the event let owned_event = mem::take(event); *event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum); @@ -96,50 +116,74 @@ mod rhai_calendar_module { // Event Getters #[rhai_fn(get = "id", pure)] - pub fn get_event_id(event: &mut RhaiEvent) -> i64 { event.base_data.id as i64 } + pub fn get_event_id(event: &mut RhaiEvent) -> i64 { + event.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { event.base_data.created_at } + pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { + event.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { event.base_data.modified_at } - + pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { + event.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_event_title(event: &mut RhaiEvent) -> String { event.title.clone() } + pub fn get_event_title(event: &mut RhaiEvent) -> String { + event.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_event_description(event: &mut RhaiEvent) -> Option { event.description.clone() } + pub fn get_event_description(event: &mut RhaiEvent) -> Option { + event.description.clone() + } #[rhai_fn(get = "start_time", pure)] - pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { event.start_time } + pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { + event.start_time + } #[rhai_fn(get = "end_time", pure)] - pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { event.end_time } + pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { + event.end_time + } #[rhai_fn(get = "location", pure)] - pub fn get_event_location(event: &mut RhaiEvent) -> Option { event.location.clone() } + pub fn get_event_location(event: &mut RhaiEvent) -> Option { + event.location.clone() + } #[rhai_fn(get = "attendees", pure)] - pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { event.attendees.clone() } + pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { + event.attendees.clone() + } // --- Attendee Functions --- #[rhai_fn(name = "new_attendee")] pub fn new_attendee() -> RhaiAttendee { Attendee::new(0) // Default contact_id, will be set via builder } - + /// Sets the contact ID for an attendee #[rhai_fn(name = "with_contact_id", return_raw, global, pure)] - pub fn attendee_with_contact_id(attendee: &mut RhaiAttendee, contact_id: i64) -> Result> { + pub fn attendee_with_contact_id( + attendee: &mut RhaiAttendee, + contact_id: i64, + ) -> Result> { let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0); let owned_attendee = mem::replace(attendee, Attendee::new(0)); *attendee = Attendee::new(new_contact_id); attendee.status = owned_attendee.status; Ok(attendee.clone()) } - + /// Sets the status for an attendee #[rhai_fn(name = "with_status", return_raw, global, pure)] - pub fn attendee_with_status(attendee: &mut RhaiAttendee, status_str: String) -> Result> { + pub fn attendee_with_status( + attendee: &mut RhaiAttendee, + status_str: String, + ) -> Result> { let status_enum = AttendanceStatus::from_string(&status_str) .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(), Position::NONE )))?; - + let owned_attendee = mem::replace(attendee, Attendee::new(0)); *attendee = owned_attendee.status(status_enum); Ok(attendee.clone()) @@ -149,9 +193,13 @@ mod rhai_calendar_module { // Attendee Getters #[rhai_fn(get = "contact_id", pure)] - pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { attendee.contact_id as i64 } + pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { + attendee.contact_id as i64 + } #[rhai_fn(get = "status", pure)] - pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { attendee.status.to_string() } + pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { + attendee.status.to_string() + } // --- Calendar Functions --- #[rhai_fn(name = "new_calendar", return_raw)] @@ -159,25 +207,31 @@ mod rhai_calendar_module { let calendar = Calendar::new(None, ""); Ok(calendar) } - + /// Sets the calendar name #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn calendar_name(calendar: &mut RhaiCalendar, name: String) -> Result> { + pub fn calendar_name( + calendar: &mut RhaiCalendar, + name: String, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = Calendar::new(Some(owned_calendar.base_data.id), name); Ok(calendar.clone()) } - + /// Sets the calendar description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn calendar_description(calendar: &mut RhaiCalendar, description: String) -> Result> { + pub fn calendar_description( + calendar: &mut RhaiCalendar, + description: String, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); let updated_calendar = owned_calendar.description(description); @@ -186,10 +240,13 @@ mod rhai_calendar_module { } #[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)] - pub fn calendar_add_event(calendar: &mut RhaiCalendar, event: RhaiEvent) -> Result> { + pub fn calendar_add_event( + calendar: &mut RhaiCalendar, + event: RhaiEvent, + ) -> Result> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = owned_calendar.add_event(event.base_data.id as i64); @@ -197,10 +254,13 @@ mod rhai_calendar_module { } #[rhai_fn(name = "remove_event_from_calendar", return_raw)] - pub fn calendar_remove_event(calendar: &mut RhaiCalendar, event_id: i64) -> Result<(), Box> { + pub fn calendar_remove_event( + calendar: &mut RhaiCalendar, + event_id: i64, + ) -> Result<(), Box> { // Create a default Calendar to replace the taken one let default_calendar = Calendar::new(None, ""); - + // Take ownership of the calendar, apply the builder method, then put it back let owned_calendar = std::mem::replace(calendar, default_calendar); *calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64); @@ -209,140 +269,213 @@ mod rhai_calendar_module { // Calendar Getters #[rhai_fn(get = "id", pure)] - pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.id as i64 } - + pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.id as i64 + } + #[rhai_fn(get = "name", pure)] - pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { calendar.name.clone() } - + pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { + calendar.name.clone() + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.created_at } - + pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { calendar.base_data.modified_at } - + pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { + calendar.base_data.modified_at + } + #[rhai_fn(get = "events", pure)] - pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { calendar.events.clone() } - + pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { + calendar.events.clone() + } + #[rhai_fn(get = "description", pure)] - pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { calendar.description.clone() } - + pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { + calendar.description.clone() + } + // Calendar doesn't have an owner_id field in the current implementation // pub fn get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 } - } pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_calendar_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_event = db.clone(); - db_module.set_native_fn("save_event", move |event: Event| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_event.set(&event) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_event: {}", e).into(), Position::NONE)))?; - - // Return the updated event with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_event", + move |event: Event| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_event.set(&event).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_event: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated event with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_event = db.clone(); - db_module.set_native_fn("delete_event", move |event: Event| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_event.collection::() - .expect("can open event collection") - .delete_by_id(event.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_event", + move |event: Event| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_event + .collection::() + .expect("can open event collection") + .delete_by_id(event.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_event = db.clone(); - db_module.set_native_fn("get_event_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_event.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_event_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_event + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_event_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Event with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone_set_calendar = db.clone(); - db_module.set_native_fn("save_calendar", move |calendar: Calendar| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_calendar.set(&calendar) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_calendar: {}", e).into(), Position::NONE)))?; - - // Return the updated calendar with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_calendar", + move |calendar: Calendar| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_calendar.set(&calendar).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_calendar: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated calendar with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_calendar = db.clone(); - db_module.set_native_fn("delete_calendar", move |calendar: Calendar| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_calendar.collection::() - .expect("can open calendar collection") - .delete_by_id(calendar.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_calendar", + move |calendar: Calendar| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_calendar + .collection::() + .expect("can open calendar collection") + .delete_by_id(calendar.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_calendar = db.clone(); - db_module.set_native_fn("get_calendar_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_calendar.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_calendar_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Calendar with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_calendar_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_calendar + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_calendar_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Calendar with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_calendars function to get all calendars let db_clone_list_calendars = db.clone(); - db_module.set_native_fn("list_calendars", move || -> Result> { - let collection = db_clone_list_calendars.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get calendar collection: {:?}", e).into(), - Position::NONE - )))?; - let calendars = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all calendars: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for calendar in calendars { - array.push(Dynamic::from(calendar)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_calendars", + move || -> Result> { + let collection = db_clone_list_calendars + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get calendar collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let calendars = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all calendars: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for calendar in calendars { + array.push(Dynamic::from(calendar)); + } + Ok(Dynamic::from(array)) + }, + ); + // Add list_events function to get all events let db_clone_list_events = db.clone(); - db_module.set_native_fn("list_events", move || -> Result> { - let collection = db_clone_list_events.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get event collection: {:?}", e).into(), - Position::NONE - )))?; - let events = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all events: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for event in events { - array.push(Dynamic::from(event)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_events", + move || -> Result> { + let collection = db_clone_list_events.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get event collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let events = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all events: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for event in events { + array.push(Dynamic::from(event)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs index 43d4b11..450444b 100644 --- a/heromodels/src/models/circle/circle.rs +++ b/heromodels/src/models/circle/circle.rs @@ -19,6 +19,8 @@ pub struct Circle { pub description: Option, /// List of related circles pub circles: Vec, + /// List of members in the circle (their public keys) + pub members: Vec, /// Logo URL or symbol for the circle pub logo: Option, /// Theme settings for the circle (colors, styling, etc.) @@ -35,6 +37,7 @@ impl Circle { description: None, circles: Vec::new(), logo: None, + members: Vec::new(), theme: HashMap::new(), } } @@ -83,4 +86,13 @@ impl Circle { } self } -} \ No newline at end of file + + /// Adds a member to the circle + pub fn add_member(mut self, member: String) -> Self { + // Prevent duplicate members + if !self.members.iter().any(|a| *a == member) { + self.members.push(member); + } + self + } +} diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs index f761c82..cb5c353 100644 --- a/heromodels/src/models/circle/mod.rs +++ b/heromodels/src/models/circle/mod.rs @@ -3,5 +3,5 @@ pub mod circle; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::circle::{Circle}; +pub use self::circle::Circle; pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs index 07f9f10..3bab892 100644 --- a/heromodels/src/models/circle/rhai.rs +++ b/heromodels/src/models/circle/rhai.rs @@ -1,16 +1,16 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array, CustomType}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::circle::{Circle}; +use super::circle::Circle; type RhaiCircle = Circle; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; use serde::Serialize; -use std::collections::HashMap; use serde_json; +use std::collections::HashMap; /// Registers a `.json()` method for any type `T` that implements the required traits. fn register_json_method(engine: &mut Engine) @@ -31,12 +31,12 @@ where // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -46,10 +46,13 @@ mod rhai_circle_module { pub fn new_circle() -> RhaiCircle { Circle::new() } - + /// Sets the circle title #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn circle_title(circle: &mut RhaiCircle, title: String) -> Result> { + pub fn circle_title( + circle: &mut RhaiCircle, + title: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.title(title); Ok(circle.clone()) @@ -57,15 +60,21 @@ mod rhai_circle_module { /// Sets the circle ws_url #[rhai_fn(name = "ws_url", return_raw, global, pure)] - pub fn circle_ws_url(circle: &mut RhaiCircle, ws_url: String) -> Result> { + pub fn circle_ws_url( + circle: &mut RhaiCircle, + ws_url: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.ws_url(ws_url); Ok(circle.clone()) } - + /// Sets the circle description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn circle_description(circle: &mut RhaiCircle, description: String) -> Result> { + pub fn circle_description( + circle: &mut RhaiCircle, + description: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.description(description); Ok(circle.clone()) @@ -73,7 +82,10 @@ mod rhai_circle_module { /// Sets the circle logo #[rhai_fn(name = "logo", return_raw, global, pure)] - pub fn circle_logo(circle: &mut RhaiCircle, logo: String) -> Result> { + pub fn circle_logo( + circle: &mut RhaiCircle, + logo: String, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.logo(logo); Ok(circle.clone()) @@ -81,118 +93,194 @@ mod rhai_circle_module { /// Sets the circle theme #[rhai_fn(name = "theme", return_raw, global, pure)] - pub fn circle_theme(circle: &mut RhaiCircle, theme: HashMap) -> Result> { + pub fn circle_theme( + circle: &mut RhaiCircle, + theme: HashMap, + ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.theme(theme); Ok(circle.clone()) } - + /// Adds an attendee to the circle #[rhai_fn(name = "add_circle", return_raw, global, pure)] - pub fn circle_add_circle(circle: &mut RhaiCircle, added_circle: String) -> Result> { + pub fn circle_add_circle( + circle: &mut RhaiCircle, + added_circle: String, + ) -> Result> { // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_circle(added_circle); Ok(circle.clone()) } + /// Adds an attendee to the circle + #[rhai_fn(name = "add_member", return_raw, global, pure)] + pub fn circle_add_member( + circle: &mut RhaiCircle, + added_member: String, + ) -> Result> { + // Use take to get ownership of the circle + let owned_circle = mem::take(circle); + *circle = owned_circle.add_member(added_member); + Ok(circle.clone()) + } + // Circle Getters #[rhai_fn(get = "id", pure)] - pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } + pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { + circle.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } + pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } - + pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } + pub fn get_circle_title(circle: &mut RhaiCircle) -> String { + circle.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } + pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { + circle.description.clone() + } #[rhai_fn(get = "circles", pure)] - pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } + pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { + circle.circles.clone() + } #[rhai_fn(get = "ws_url", pure)] - pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } + pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { + circle.ws_url.clone() + } #[rhai_fn(get = "logo", pure)] - pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } + pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { + circle.logo.clone() + } #[rhai_fn(get = "theme", pure)] - pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { circle.theme.clone() } + pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { + circle.theme.clone() + } } pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_circle_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_circle = db.clone(); - db_module.set_native_fn("save_circle", move |circle: Circle| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_circle.set(&circle) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; - - // Return the updated circle with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_circle", + move |circle: Circle| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_circle.set(&circle).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_circle: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated circle with the correct ID + Ok(result.1) + }, + ); register_json_method::(engine); // Manually register database functions as they need to capture 'db' let db_clone_delete_circle = db.clone(); - db_module.set_native_fn("delete_circle", move |circle: Circle| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_circle.collection::() - .expect("can open circle collection") - .delete_by_id(circle.base_data.id) - .expect("can delete circle"); - - // Return the updated circle with the correct ID - Ok(result) - }); - - let db_clone_get_circle = db.clone(); - db_module.set_native_fn("get_circle", move || -> Result> { - // Use the Collection trait method directly - let all_circles: Vec = db_clone_get_circle.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle: {}", e).into(), Position::NONE)))?; + db_module.set_native_fn( + "delete_circle", + move |circle: Circle| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_circle + .collection::() + .expect("can open circle collection") + .delete_by_id(circle.base_data.id) + .expect("can delete circle"); - if let Some(first_circle) = all_circles.first() { - Ok(first_circle.clone()) - } else { - Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) - } - }); + // Return the updated circle with the correct ID + Ok(result) + }, + ); + + let db_clone_get_circle = db.clone(); + db_module.set_native_fn( + "get_circle", + move || -> Result> { + // Use the Collection trait method directly + let all_circles: Vec = db_clone_get_circle.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_circle: {}", e).into(), + Position::NONE, + )) + })?; + + if let Some(first_circle) = all_circles.first() { + Ok(first_circle.clone()) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime( + "Circle not found".into(), + Position::NONE, + ))) + } + }, + ); let db_clone_get_circle_by_id = db.clone(); - db_module.set_native_fn("get_circle_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_circle_by_id.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_circle_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_circle_by_id + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_circle_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Circle with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + // Add list_circles function to get all circles let db_clone_list_circles = db.clone(); - db_module.set_native_fn("list_circles", move || -> Result> { - let collection = db_clone_list_circles.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get circle collection: {:?}", e).into(), - Position::NONE - )))?; - let circles = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all circles: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for circle in circles { - array.push(Dynamic::from(circle)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_circles", + move || -> Result> { + let collection = db_clone_list_circles.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get circle collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let circles = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all circles: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for circle in circles { + array.push(Dynamic::from(circle)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/contact/contact.rs b/heromodels/src/models/contact/contact.rs index 32095a1..1b9da79 100644 --- a/heromodels/src/models/contact/contact.rs +++ b/heromodels/src/models/contact/contact.rs @@ -112,4 +112,4 @@ impl Group { self.contacts.push(contact); self } -} \ No newline at end of file +} diff --git a/heromodels/src/models/contact/rhai.rs b/heromodels/src/models/contact/rhai.rs index 12b6518..fb0b01d 100644 --- a/heromodels/src/models/contact/rhai.rs +++ b/heromodels/src/models/contact/rhai.rs @@ -1,23 +1,23 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; -use super::contact::{Group, Contact}; +use super::contact::{Contact, Group}; type RhaiGroup = Group; type RhaiContact = Contact; -use crate::db::hero::OurDB; use crate::db::Collection; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -27,18 +27,24 @@ mod rhai_contact_module { pub fn new_group() -> RhaiGroup { Group::new() } - + /// Sets the event title #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn group_name(group: &mut RhaiGroup, name: String) -> Result> { + pub fn group_name( + group: &mut RhaiGroup, + name: String, + ) -> Result> { let owned_group = mem::take(group); *group = owned_group.name(name); Ok(group.clone()) } - + /// Sets the event description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn group_description(group: &mut RhaiGroup, description: String) -> Result> { + pub fn group_description( + group: &mut RhaiGroup, + description: String, + ) -> Result> { let owned_group = mem::take(group); *group = owned_group.description(description); Ok(group.clone()) @@ -46,7 +52,10 @@ mod rhai_contact_module { /// Adds an attendee to the event #[rhai_fn(name = "add_contact", return_raw, global, pure)] - pub fn group_add_contact(group: &mut RhaiGroup, contact_id: i64) -> Result> { + pub fn group_add_contact( + group: &mut RhaiGroup, + contact_id: i64, + ) -> Result> { // Use take to get ownership of the event let owned_group = mem::take(group); *group = owned_group.add_contact(contact_id as u32); @@ -54,21 +63,37 @@ mod rhai_contact_module { } #[rhai_fn(get = "contacts", pure)] - pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { group.contacts.clone().into_iter().map(|id| id as i64).collect() } + pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { + group + .contacts + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } // Group Getters #[rhai_fn(get = "id", pure)] - pub fn get_group_id(group: &mut RhaiGroup) -> i64 { group.base_data.id as i64 } + pub fn get_group_id(group: &mut RhaiGroup) -> i64 { + group.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { group.base_data.created_at } + pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { + group.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { group.base_data.modified_at } - - #[rhai_fn(get = "name", pure)] - pub fn get_group_name(group: &mut RhaiGroup) -> String { group.name.clone() } - #[rhai_fn(get = "description", pure)] - pub fn get_group_description(group: &mut RhaiGroup) -> Option { group.description.clone() } + pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { + group.base_data.modified_at + } + #[rhai_fn(get = "name", pure)] + pub fn get_group_name(group: &mut RhaiGroup) -> String { + group.name.clone() + } + #[rhai_fn(get = "description", pure)] + pub fn get_group_description(group: &mut RhaiGroup) -> Option { + group.description.clone() + } // --- Contact Functions --- #[rhai_fn(name = "new_contact", return_raw)] @@ -76,25 +101,31 @@ mod rhai_contact_module { let contact = Contact::new(); Ok(contact) } - + /// Sets the contact name #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn contact_name(contact: &mut RhaiContact, name: String) -> Result> { + pub fn contact_name( + contact: &mut RhaiContact, + name: String, + ) -> Result> { // Create a default Contact to replace the taken one let default_contact = Contact::new(); - + // Take ownership of the contact, apply the builder method, then put it back let owned_contact = std::mem::replace(contact, default_contact); *contact = owned_contact.name(name); Ok(contact.clone()) } - + /// Sets the contact description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn contact_description(contact: &mut RhaiContact, description: String) -> Result> { + pub fn contact_description( + contact: &mut RhaiContact, + description: String, + ) -> Result> { // Create a default Contact to replace the taken one let default_contact = Contact::new(); - + // Take ownership of the contact, apply the builder method, then put it back let owned_contact = std::mem::replace(contact, default_contact); *contact = owned_contact.description(description); @@ -103,130 +134,200 @@ mod rhai_contact_module { // Contact Getters #[rhai_fn(get = "id", pure)] - pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { contact.base_data.id as i64 } - + pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { + contact.base_data.id as i64 + } + #[rhai_fn(get = "name", pure)] - pub fn get_contact_name(contact: &mut RhaiContact) -> String { contact.name.clone() } - + pub fn get_contact_name(contact: &mut RhaiContact) -> String { + contact.name.clone() + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { contact.base_data.created_at } - + pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { + contact.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { contact.base_data.modified_at } + pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { + contact.base_data.modified_at + } } pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc) { // Register the exported module globally let module = exported_module!(rhai_contact_module); engine.register_global_module(module.into()); - + // Create a module for database functions let mut db_module = Module::new(); // Manually register database functions as they need to capture 'db' let db_clone_set_group = db.clone(); - db_module.set_native_fn("save_group", move |group: Group| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_group.set(&group) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_group: {}", e).into(), Position::NONE)))?; - - // Return the updated event with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_group", + move |group: Group| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_group.set(&group).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_group: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated event with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_group = db.clone(); - db_module.set_native_fn("delete_group", move |group: Group| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_group.collection::() - .expect("can open group collection") - .delete_by_id(group.base_data.id) - .expect("can delete group"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_group", + move |group: Group| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_group + .collection::() + .expect("can open group collection") + .delete_by_id(group.base_data.id) + .expect("can delete group"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_group = db.clone(); - db_module.set_native_fn("get_group_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_group.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_event_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Event with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_group_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_group + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_event_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Event with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone_set_contact = db.clone(); - db_module.set_native_fn("save_contact", move |contact: Contact| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_contact.set(&contact) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_contact: {}", e).into(), Position::NONE)))?; - - // Return the updated contact with the correct ID - Ok(result.1) - }); + db_module.set_native_fn( + "save_contact", + move |contact: Contact| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_contact.set(&contact).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_contact: {}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated contact with the correct ID + Ok(result.1) + }, + ); // Manually register database functions as they need to capture 'db' let db_clone_delete_contact = db.clone(); - db_module.set_native_fn("delete_contact", move |contact: Contact| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_contact.collection::() - .expect("can open contact collection") - .delete_by_id(contact.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }); - + db_module.set_native_fn( + "delete_contact", + move |contact: Contact| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_contact + .collection::() + .expect("can open contact collection") + .delete_by_id(contact.base_data.id) + .expect("can delete event"); + + // Return the updated event with the correct ID + Ok(result) + }, + ); + let db_clone_get_contact = db.clone(); - db_module.set_native_fn("get_contact_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_contact.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_contact_by_id: {}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Contact with ID {} not found", id_u32).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_contact_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_contact + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_contact_by_id: {}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Contact with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); // Add list_contacts function to get all contacts let db_clone_list_contacts = db.clone(); - db_module.set_native_fn("list_contacts", move || -> Result> { - let collection = db_clone_list_contacts.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get contact collection: {:?}", e).into(), - Position::NONE - )))?; - let contacts = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all contacts: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for contact in contacts { - array.push(Dynamic::from(contact)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_contacts", + move || -> Result> { + let collection = db_clone_list_contacts + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get contact collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let contacts = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all contacts: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for contact in contacts { + array.push(Dynamic::from(contact)); + } + Ok(Dynamic::from(array)) + }, + ); + // Add list_events function to get all events let db_clone_list_groups = db.clone(); - db_module.set_native_fn("list_groups", move || -> Result> { - let collection = db_clone_list_groups.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get group collection: {:?}", e).into(), - Position::NONE - )))?; - let groups = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all groups: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for group in groups { - array.push(Dynamic::from(group)); - } - Ok(Dynamic::from(array)) - }); + db_module.set_native_fn( + "list_groups", + move || -> Result> { + let collection = db_clone_list_groups.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get group collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let groups = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all groups: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for group in groups { + array.push(Dynamic::from(group)); + } + Ok(Dynamic::from(array)) + }, + ); // Register the database module globally engine.register_global_module(db_module.into()); diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index 0122b02..715c2d3 100644 --- a/heromodels/src/models/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; pub struct Comment { pub base_data: BaseModelData, // Provides id, created_at, updated_at #[index] - pub user_id: u32, // Maps to commenter_id + pub user_id: u32, // Maps to commenter_id pub content: String, // Maps to text pub parent_comment_id: Option, // For threaded comments } diff --git a/heromodels/src/models/core/mod.rs b/heromodels/src/models/core/mod.rs index f7fde31..e535e98 100644 --- a/heromodels/src/models/core/mod.rs +++ b/heromodels/src/models/core/mod.rs @@ -4,4 +4,3 @@ pub mod model; // Re-export key types for convenience pub use comment::Comment; - diff --git a/heromodels/src/models/core/model.rs b/heromodels/src/models/core/model.rs index e69de29..8b13789 100644 --- a/heromodels/src/models/core/model.rs +++ b/heromodels/src/models/core/model.rs @@ -0,0 +1 @@ + diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 7bb9e6c..eb72c8d 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/account.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::asset::Asset; @@ -18,7 +18,7 @@ pub struct Account { pub ledger: String, // describes the ledger/blockchain where the account is located pub address: String, // address of the account on the blockchain pub pubkey: String, // public key - pub assets: Vec, // list of assets in this account + pub assets: Vec, // list of assets in this account } impl Account { @@ -87,6 +87,6 @@ impl Account { /// Find an asset by name pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { // TODO: implement - return None + return None; } } diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 20dbff1..7eb59cf 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/asset.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; /// AssetType defines the type of blockchain asset #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index f82bd90..0f5300d 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -1,10 +1,10 @@ // heromodels/src/models/finance/marketplace.rs -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::asset::AssetType; @@ -53,8 +53,7 @@ impl Default for BidStatus { } /// Bid represents a bid on an auction listing -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] -#[derive(Default)] +#[derive(Debug, Clone, Serialize, Deserialize, CustomType, Default)] pub struct Bid { pub listing_id: String, // ID of the listing this bid belongs to pub bidder_id: u32, // ID of the user who placed the bid @@ -272,7 +271,7 @@ impl Listing { } self.status = ListingStatus::Sold; - + if self.sold_at.is_none() { self.sold_at = Some(Utc::now()); } @@ -281,7 +280,7 @@ impl Listing { if self.listing_type == ListingType::Auction { let buyer_id_str = self.buyer_id.as_ref().unwrap().to_string(); let sale_price_val = self.sale_price.unwrap(); - + for bid in &mut self.bids { if bid.bidder_id.to_string() == buyer_id_str && bid.amount == sale_price_val { bid.status = BidStatus::Accepted; diff --git a/heromodels/src/models/finance/mod.rs b/heromodels/src/models/finance/mod.rs index bf94b7e..871c4f9 100644 --- a/heromodels/src/models/finance/mod.rs +++ b/heromodels/src/models/finance/mod.rs @@ -9,4 +9,4 @@ pub mod rhai; // Re-export main models for easier access pub use self::account::Account; pub use self::asset::{Asset, AssetType}; -pub use self::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; +pub use self::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index 0633904..e45057e 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -1,12 +1,12 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use chrono::Utc; +use rhai::plugin::*; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; +use std::mem; +use std::sync::Arc; use super::account::Account; use super::asset::{Asset, AssetType}; -use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; +use super::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; use crate::db::hero::OurDB; use crate::db::{Collection, Db}; @@ -18,12 +18,12 @@ type RhaiBid = Bid; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } // Helper functions for enum conversions @@ -45,8 +45,12 @@ fn string_to_asset_type(s: &str) -> Result> { "Erc721" => Ok(AssetType::Erc721), "Erc1155" => Ok(AssetType::Erc1155), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", s).into(), - Position::NONE + format!( + "Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", + s + ) + .into(), + Position::NONE, ))), } } @@ -68,8 +72,12 @@ fn string_to_listing_status(s: &str) -> Result "Cancelled" => Ok(ListingStatus::Cancelled), "Expired" => Ok(ListingStatus::Expired), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", s).into(), - Position::NONE + format!( + "Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", + s + ) + .into(), + Position::NONE, ))), } } @@ -89,8 +97,12 @@ fn string_to_listing_type(s: &str) -> Result> { "Auction" => Ok(ListingType::Auction), "Exchange" => Ok(ListingType::Exchange), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", s).into(), - Position::NONE + format!( + "Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", + s + ) + .into(), + Position::NONE, ))), } } @@ -112,8 +124,12 @@ fn string_to_bid_status(s: &str) -> Result> { "Rejected" => Ok(BidStatus::Rejected), "Cancelled" => Ok(BidStatus::Cancelled), _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", s).into(), - Position::NONE + format!( + "Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", + s + ) + .into(), + Position::NONE, ))), } } @@ -181,7 +197,10 @@ mod account_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn name(account: &mut RhaiAccount, name: String) -> Result> { + pub fn name( + account: &mut RhaiAccount, + name: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.name(name); *account = acc; @@ -189,7 +208,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn user_id(account: &mut RhaiAccount, user_id: INT) -> Result> { + pub fn user_id( + account: &mut RhaiAccount, + user_id: INT, + ) -> Result> { let user_id = id_from_i64_to_u32(user_id)?; let mut acc = mem::take(account); acc = acc.user_id(user_id); @@ -198,7 +220,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn description(account: &mut RhaiAccount, description: String) -> Result> { + pub fn description( + account: &mut RhaiAccount, + description: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.description(description); *account = acc; @@ -206,7 +231,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn ledger(account: &mut RhaiAccount, ledger: String) -> Result> { + pub fn ledger( + account: &mut RhaiAccount, + ledger: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.ledger(ledger); *account = acc; @@ -214,7 +242,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn address(account: &mut RhaiAccount, address: String) -> Result> { + pub fn address( + account: &mut RhaiAccount, + address: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.address(address); *account = acc; @@ -222,7 +253,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn pubkey(account: &mut RhaiAccount, pubkey: String) -> Result> { + pub fn pubkey( + account: &mut RhaiAccount, + pubkey: String, + ) -> Result> { let mut acc = mem::take(account); acc = acc.pubkey(pubkey); *account = acc; @@ -230,7 +264,10 @@ mod account_module { } #[rhai_fn(return_raw, global)] - pub fn add_asset(account: &mut RhaiAccount, asset_id: INT) -> Result> { + pub fn add_asset( + account: &mut RhaiAccount, + asset_id: INT, + ) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut acc = mem::take(account); acc = acc.add_asset(asset_id); @@ -301,7 +338,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn description(asset: &mut RhaiAsset, description: String) -> Result> { + pub fn description( + asset: &mut RhaiAsset, + description: String, + ) -> Result> { let mut ast = mem::take(asset); ast = ast.description(description); *asset = ast; @@ -317,7 +357,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn address(asset: &mut RhaiAsset, address: String) -> Result> { + pub fn address( + asset: &mut RhaiAsset, + address: String, + ) -> Result> { let mut ast = mem::take(asset); ast = ast.address(address); *asset = ast; @@ -325,7 +368,10 @@ mod asset_module { } #[rhai_fn(return_raw, global)] - pub fn asset_type(asset: &mut RhaiAsset, asset_type_str: String) -> Result> { + pub fn asset_type( + asset: &mut RhaiAsset, + asset_type_str: String, + ) -> Result> { let asset_type_enum = string_to_asset_type(&asset_type_str)?; let mut ast = mem::take(asset); ast = ast.asset_type(asset_type_enum); @@ -338,7 +384,7 @@ mod asset_module { if decimals < 0 || decimals > 255 { return Err(Box::new(EvalAltResult::ErrorArithmetic( format!("Decimals value must be between 0 and 255, got {}", decimals).into(), - Position::NONE + Position::NONE, ))); } let mut ast = mem::take(asset); @@ -441,7 +487,10 @@ mod listing_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn seller_id(listing: &mut RhaiListing, seller_id: INT) -> Result> { + pub fn seller_id( + listing: &mut RhaiListing, + seller_id: INT, + ) -> Result> { let seller_id = id_from_i64_to_u32(seller_id)?; let mut lst = mem::take(listing); lst = lst.seller_id(seller_id); @@ -450,7 +499,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn asset_id(listing: &mut RhaiListing, asset_id: INT) -> Result> { + pub fn asset_id( + listing: &mut RhaiListing, + asset_id: INT, + ) -> Result> { let asset_id = id_from_i64_to_u32(asset_id)?; let mut lst = mem::take(listing); lst = lst.asset_id(asset_id); @@ -467,7 +519,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn currency(listing: &mut RhaiListing, currency: String) -> Result> { + pub fn currency( + listing: &mut RhaiListing, + currency: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.currency(currency); *listing = lst; @@ -475,7 +530,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn listing_type(listing: &mut RhaiListing, listing_type_str: String) -> Result> { + pub fn listing_type( + listing: &mut RhaiListing, + listing_type_str: String, + ) -> Result> { let listing_type_enum = string_to_listing_type(&listing_type_str)?; let mut lst = mem::take(listing); lst = lst.listing_type(listing_type_enum); @@ -484,7 +542,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn title(listing: &mut RhaiListing, title: String) -> Result> { + pub fn title( + listing: &mut RhaiListing, + title: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.title(title); *listing = lst; @@ -492,7 +553,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn description(listing: &mut RhaiListing, description: String) -> Result> { + pub fn description( + listing: &mut RhaiListing, + description: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.description(description); *listing = lst; @@ -500,7 +564,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn image_url(listing: &mut RhaiListing, image_url: String) -> Result> { + pub fn image_url( + listing: &mut RhaiListing, + image_url: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.image_url(Some(image_url)); *listing = lst; @@ -508,15 +575,21 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn expires_at(listing: &mut RhaiListing, end_date_ts: INT) -> Result> { + pub fn expires_at( + listing: &mut RhaiListing, + end_date_ts: INT, + ) -> Result> { use chrono::TimeZone; - let end_date = chrono::Utc.timestamp_opt(end_date_ts, 0) + let end_date = chrono::Utc + .timestamp_opt(end_date_ts, 0) .single() - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid timestamp: {}", end_date_ts).into(), - Position::NONE - )))?; - + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid timestamp: {}", end_date_ts).into(), + Position::NONE, + )) + })?; + let mut lst = mem::take(listing); lst = lst.expires_at(Some(end_date)); *listing = lst; @@ -524,7 +597,10 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn add_tag(listing: &mut RhaiListing, tag: String) -> Result> { + pub fn add_tag( + listing: &mut RhaiListing, + tag: String, + ) -> Result> { let mut lst = mem::take(listing); lst = lst.add_tag(tag); *listing = lst; @@ -532,44 +608,50 @@ mod listing_module { } #[rhai_fn(return_raw, global)] - pub fn add_bid(listing: &mut RhaiListing, bid: RhaiBid) -> Result> { + pub fn add_bid( + listing: &mut RhaiListing, + bid: RhaiBid, + ) -> Result> { let lst = mem::take(listing); match lst.clone().add_bid(bid) { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the add_bid failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to add bid: {}", err).into(), - Position::NONE + Position::NONE, ))) } } } #[rhai_fn(return_raw, global)] - pub fn complete_sale(listing: &mut RhaiListing, buyer_id: INT) -> Result> { + pub fn complete_sale( + listing: &mut RhaiListing, + buyer_id: INT, + ) -> Result> { let buyer_id = id_from_i64_to_u32(buyer_id)?; let lst = mem::take(listing); - + // First set the buyer_id and sale_price let lst_with_buyer = lst.clone().buyer_id(buyer_id); - + // Now complete the sale match lst_with_buyer.complete_sale() { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the complete_sale failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to complete sale: {}", err).into(), - Position::NONE + Position::NONE, ))) } } @@ -582,13 +664,13 @@ mod listing_module { Ok(updated_lst) => { *listing = updated_lst; Ok(listing.clone()) - }, + } Err(err) => { // Put back the original listing since the cancel failed *listing = lst; Err(Box::new(EvalAltResult::ErrorRuntime( format!("Failed to cancel listing: {}", err).into(), - Position::NONE + Position::NONE, ))) } } @@ -647,7 +729,10 @@ mod bid_module { // Setters using builder pattern with proper mutability handling #[rhai_fn(return_raw, global)] - pub fn listing_id(bid: &mut RhaiBid, listing_id: String) -> Result> { + pub fn listing_id( + bid: &mut RhaiBid, + listing_id: String, + ) -> Result> { let mut b = mem::take(bid); b = b.listing_id(listing_id); *bid = b; @@ -680,7 +765,10 @@ mod bid_module { } #[rhai_fn(return_raw, global)] - pub fn update_status(bid: &mut RhaiBid, status_str: String) -> Result> { + pub fn update_status( + bid: &mut RhaiBid, + status_str: String, + ) -> Result> { let status = string_to_bid_status(&status_str)?; let mut b = mem::take(bid); b = b.status(status); @@ -706,13 +794,21 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { engine.register_global_module(bid_module.into()); // --- Global Helper Functions (Enum conversions) --- - engine.register_fn("str_to_asset_type", |s: ImmutableString| self::string_to_asset_type(s.as_str())); + engine.register_fn("str_to_asset_type", |s: ImmutableString| { + self::string_to_asset_type(s.as_str()) + }); engine.register_fn("asset_type_to_str", self::asset_type_to_string); - engine.register_fn("str_to_listing_status", |s: ImmutableString| self::string_to_listing_status(s.as_str())); + engine.register_fn("str_to_listing_status", |s: ImmutableString| { + self::string_to_listing_status(s.as_str()) + }); engine.register_fn("listing_status_to_str", self::listing_status_to_string); - engine.register_fn("str_to_listing_type", |s: ImmutableString| self::string_to_listing_type(s.as_str())); + engine.register_fn("str_to_listing_type", |s: ImmutableString| { + self::string_to_listing_type(s.as_str()) + }); engine.register_fn("listing_type_to_str", self::listing_type_to_string); - engine.register_fn("str_to_bid_status", |s: ImmutableString| self::string_to_bid_status(s.as_str())); + engine.register_fn("str_to_bid_status", |s: ImmutableString| { + self::string_to_bid_status(s.as_str()) + }); engine.register_fn("bid_status_to_str", self::bid_status_to_string); // --- Database interaction functions (registered in a separate db_module) --- @@ -720,68 +816,158 @@ pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { // Account DB functions let db_set_account = Arc::clone(&db); - db_module.set_native_fn("set_account", move |account: Account| -> Result> { - let collection = db_set_account.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&account) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Account: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_account", + move |account: Account| -> Result> { + let collection = db_set_account.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&account) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Account: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_account = Arc::clone(&db); - db_module.set_native_fn("get_account_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_account.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_account| opt_account.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_account_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_account.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_account| { + opt_account + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); // Asset DB functions let db_set_asset = Arc::clone(&db); - db_module.set_native_fn("set_asset", move |asset: Asset| -> Result> { - let collection = db_set_asset.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&asset) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Asset: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_asset", + move |asset: Asset| -> Result> { + let collection = db_set_asset.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&asset) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Asset: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_asset = Arc::clone(&db); - db_module.set_native_fn("get_asset_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_asset.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_asset| opt_asset.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_asset_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_asset.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_asset| { + opt_asset + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); // Listing DB functions let db_set_listing = Arc::clone(&db); - db_module.set_native_fn("set_listing", move |listing: Listing| -> Result> { - let collection = db_set_listing.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; - collection.set(&listing) - .map(|(id, _)| id as INT) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to save Listing: {:?}", e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "set_listing", + move |listing: Listing| -> Result> { + let collection = db_set_listing.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .set(&listing) + .map(|(id, _)| id as INT) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to save Listing: {:?}", e).into(), + Position::NONE, + )) + }) + }, + ); let db_get_listing = Arc::clone(&db); - db_module.set_native_fn("get_listing_by_id", move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_listing.collection::().map_err(|e| - Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing collection: {:?}", e).into(), Position::NONE)) )?; - collection.get_by_id(id_u32) - .map(|opt_listing| opt_listing.map(Dynamic::from).unwrap_or_else(|| Dynamic::UNIT)) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_listing_by_id", + move |id_rhai: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_rhai)?; + let collection = db_get_listing.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection + .get_by_id(id_u32) + .map(|opt_listing| { + opt_listing + .map(Dynamic::from) + .unwrap_or_else(|| Dynamic::UNIT) + }) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), + Position::NONE, + )) + }) + }, + ); engine.register_global_module(db_module.into()); // Global timestamp function for scripts to get current time engine.register_fn("timestamp", || Utc::now().timestamp()); - + println!("Successfully registered finance Rhai module."); } diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index c586df4..28d16f9 100644 --- a/heromodels/src/models/flow/flow.rs +++ b/heromodels/src/models/flow/flow.rs @@ -1,7 +1,7 @@ +use super::flow_step::FlowStep; use heromodels_core::BaseModelData; use heromodels_derive::model; use serde::{Deserialize, Serialize}; -use super::flow_step::FlowStep; /// Represents a signing flow. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] @@ -33,7 +33,7 @@ impl Flow { Self { base_data: BaseModelData::new(), flow_uuid: flow_uuid.to_string(), - name: String::new(), // Default name, to be set by builder + name: String::new(), // Default name, to be set by builder status: String::from("Pending"), // Default status, to be set by builder steps: Vec::new(), } diff --git a/heromodels/src/models/flow/mod.rs b/heromodels/src/models/flow/mod.rs index f729594..8dd2206 100644 --- a/heromodels/src/models/flow/mod.rs +++ b/heromodels/src/models/flow/mod.rs @@ -1,11 +1,11 @@ // Export flow model submodules pub mod flow; pub mod flow_step; -pub mod signature_requirement; pub mod rhai; +pub mod signature_requirement; // Re-export key types for convenience pub use flow::Flow; pub use flow_step::FlowStep; -pub use signature_requirement::SignatureRequirement; pub use rhai::register_flow_rhai_module; +pub use signature_requirement::SignatureRequirement; diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index b0ea9eb..03f96b9 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -1,7 +1,7 @@ use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; -use std::sync::Arc; +use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; use std::mem; +use std::sync::Arc; use super::flow::Flow; use super::flow_step::FlowStep; @@ -9,18 +9,18 @@ use super::signature_requirement::SignatureRequirement; type RhaiFlow = Flow; type RhaiFlowStep = FlowStep; type RhaiSignatureRequirement = SignatureRequirement; -use crate::db::hero::OurDB; use crate::db::Collection; use crate::db::Db; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorArithmetic( format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE + Position::NONE, )) - ) + }) } #[export_module] @@ -30,7 +30,7 @@ mod rhai_flow_module { pub fn new_flow(flow_uuid: String) -> RhaiFlow { Flow::new(flow_uuid) } - + /// Sets the flow name #[rhai_fn(name = "name", return_raw, global, pure)] pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result> { @@ -38,41 +38,63 @@ mod rhai_flow_module { *flow = owned_flow.name(name); Ok(flow.clone()) } - + /// Sets the flow status #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_status(flow: &mut RhaiFlow, status: String) -> Result> { + pub fn flow_status( + flow: &mut RhaiFlow, + status: String, + ) -> Result> { let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement *flow = owned_flow.status(status); Ok(flow.clone()) } - + /// Adds a step to the flow #[rhai_fn(name = "add_step", return_raw, global, pure)] - pub fn flow_add_step(flow: &mut RhaiFlow, step: RhaiFlowStep) -> Result> { + pub fn flow_add_step( + flow: &mut RhaiFlow, + step: RhaiFlowStep, + ) -> Result> { let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement *flow = owned_flow.add_step(step); Ok(flow.clone()) } - + // Flow Getters #[rhai_fn(get = "id", pure)] - pub fn get_id(flow: &mut RhaiFlow) -> i64 { flow.base_data.id as i64 } + pub fn get_id(flow: &mut RhaiFlow) -> i64 { + flow.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.created_at } + pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { + flow.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { flow.base_data.modified_at } + pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { + flow.base_data.modified_at + } #[rhai_fn(get = "flow_uuid", pure)] - pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { flow.flow_uuid.clone() } + pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { + flow.flow_uuid.clone() + } #[rhai_fn(get = "name", pure)] - pub fn get_name(flow: &mut RhaiFlow) -> String { flow.name.clone() } + pub fn get_name(flow: &mut RhaiFlow) -> String { + flow.name.clone() + } #[rhai_fn(get = "status", pure)] - pub fn get_status(flow: &mut RhaiFlow) -> String { flow.status.clone() } + pub fn get_status(flow: &mut RhaiFlow) -> String { + flow.status.clone() + } #[rhai_fn(get = "steps", pure)] pub fn get_steps(flow: &mut RhaiFlow) -> Array { - flow.steps.iter().cloned().map(Dynamic::from).collect::() + flow.steps + .iter() + .cloned() + .map(Dynamic::from) + .collect::() } - + // --- FlowStep Functions --- #[rhai_fn(global)] pub fn new_flow_step(step_order_i64: i64) -> Dynamic { @@ -81,34 +103,46 @@ mod rhai_flow_module { let mut flow_step = FlowStep::default(); flow_step.step_order = step_order; Dynamic::from(flow_step) - }, - Err(err) => Dynamic::from(err.to_string()) + } + Err(err) => Dynamic::from(err.to_string()), } } - + /// Sets the flow step description #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn flow_step_description(step: &mut RhaiFlowStep, description: String) -> Result> { + pub fn flow_step_description( + step: &mut RhaiFlowStep, + description: String, + ) -> Result> { let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait *step = owned_step.description(description); Ok(step.clone()) } - + /// Sets the flow step status #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_step_status(step: &mut RhaiFlowStep, status: String) -> Result> { + pub fn flow_step_status( + step: &mut RhaiFlowStep, + status: String, + ) -> Result> { let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait *step = owned_step.status(status); Ok(step.clone()) } - + // FlowStep Getters #[rhai_fn(get = "id", pure)] - pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { step.base_data.id as i64 } + pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { + step.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.created_at } + pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { + step.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { step.base_data.modified_at } + pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { + step.base_data.modified_at + } #[rhai_fn(get = "description", pure)] pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic { match &step.description { @@ -117,14 +151,22 @@ mod rhai_flow_module { } } #[rhai_fn(get = "step_order", pure)] - pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { step.step_order as i64 } + pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { + step.step_order as i64 + } #[rhai_fn(get = "status", pure)] - pub fn get_step_status(step: &mut RhaiFlowStep) -> String { step.status.clone() } - + pub fn get_step_status(step: &mut RhaiFlowStep) -> String { + step.status.clone() + } + // --- SignatureRequirement Functions --- /// Create a new signature requirement #[rhai_fn(global)] - pub fn new_signature_requirement(flow_step_id_i64: i64, public_key: String, message: String) -> Dynamic { + pub fn new_signature_requirement( + flow_step_id_i64: i64, + public_key: String, + message: String, + ) -> Dynamic { match id_from_i64_to_u32(flow_step_id_i64) { Ok(flow_step_id) => { let mut signature_requirement = SignatureRequirement::default(); @@ -132,48 +174,69 @@ mod rhai_flow_module { signature_requirement.public_key = public_key; signature_requirement.message = message; Dynamic::from(signature_requirement) - }, - Err(err) => Dynamic::from(err.to_string()) + } + Err(err) => Dynamic::from(err.to_string()), } } - + /// Sets the signed_by field #[rhai_fn(name = "signed_by", return_raw, global, pure)] - pub fn signature_requirement_signed_by(sr: &mut RhaiSignatureRequirement, signed_by: String) -> Result> { + pub fn signature_requirement_signed_by( + sr: &mut RhaiSignatureRequirement, + signed_by: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.signed_by(signed_by); Ok(sr.clone()) } - + /// Sets the signature field #[rhai_fn(name = "signature", return_raw, global, pure)] - pub fn signature_requirement_signature(sr: &mut RhaiSignatureRequirement, signature: String) -> Result> { + pub fn signature_requirement_signature( + sr: &mut RhaiSignatureRequirement, + signature: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.signature(signature); Ok(sr.clone()) } - + /// Sets the status field #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn signature_requirement_status(sr: &mut RhaiSignatureRequirement, status: String) -> Result> { + pub fn signature_requirement_status( + sr: &mut RhaiSignatureRequirement, + status: String, + ) -> Result> { let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait *sr = owned_sr.status(status); Ok(sr.clone()) } - + // SignatureRequirement Getters #[rhai_fn(get = "id", pure)] - pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.id as i64 } + pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.created_at } + pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { sr.base_data.modified_at } + pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.base_data.modified_at + } #[rhai_fn(get = "flow_step_id", pure)] - pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { sr.flow_step_id as i64 } + pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { + sr.flow_step_id as i64 + } #[rhai_fn(get = "public_key", pure)] - pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { sr.public_key.clone() } + pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { + sr.public_key.clone() + } #[rhai_fn(get = "message", pure)] - pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { sr.message.clone() } + pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { + sr.message.clone() + } #[rhai_fn(get = "signed_by", pure)] pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic { match &sr.signed_by { @@ -189,185 +252,284 @@ mod rhai_flow_module { } } #[rhai_fn(get = "status", pure)] - pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { sr.status.clone() } + pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { + sr.status.clone() + } } /// Register the flow module with the Rhai engine pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { // Create a module for database functions let mut db_module = Module::new(); - + // Flow database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_flow", move |flow: Flow| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&flow) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow: {:?}", e).into(), Position::NONE)))?; - - // Return the updated flow with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_flow", + move |flow: Flow| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&flow).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_flow: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated flow with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_flow_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Flow with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_flow_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_flow_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Flow with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_flow", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_flow", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_flows", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE - )))?; - let flows = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flows: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for flow in flows { - array.push(Dynamic::from(flow)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_flows", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let flows = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flows: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for flow in flows { + array.push(Dynamic::from(flow)); + } + Ok(Dynamic::from(array)) + }, + ); + // FlowStep database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_flow_step", move |step: FlowStep| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&step) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_flow_step: {:?}", e).into(), Position::NONE)))?; - - // Return the updated flow step with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_flow_step", + move |step: FlowStep| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&step).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_flow_step: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated flow step with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_flow_step_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_flow_step_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("FlowStep with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_flow_step_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_flow_step_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("FlowStep with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_flow_step", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_flow_step", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_flow_steps", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE - )))?; - let steps = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flow steps: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for step in steps { - array.push(Dynamic::from(step)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_flow_steps", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get flow step collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let steps = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all flow steps: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for step in steps { + array.push(Dynamic::from(step)); + } + Ok(Dynamic::from(array)) + }, + ); + // SignatureRequirement database functions let db_clone = Arc::clone(&db); - db_module.set_native_fn("save_signature_requirement", move |sr: SignatureRequirement| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&sr) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error save_signature_requirement: {:?}", e).into(), Position::NONE)))?; - - // Return the updated signature requirement with the correct ID - Ok(result.1) - }); - + db_module.set_native_fn( + "save_signature_requirement", + move |sr: SignatureRequirement| -> Result> { + // Use the Collection trait method directly + let result = db_clone.set(&sr).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error save_signature_requirement: {:?}", e).into(), + Position::NONE, + )) + })?; + + // Return the updated signature requirement with the correct ID + Ok(result.1) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("get_signature_requirement_by_id", move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("SignatureRequirement with ID {} not found", id_u32).into(), Position::NONE))) - }); - + db_module.set_native_fn( + "get_signature_requirement_by_id", + move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("SignatureRequirement with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("delete_signature_requirement", move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE - )))?; - collection.delete_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete SignatureRequirement (ID: {}): {:?}", id_u32, e).into(), - Position::NONE - ))) - }); - + db_module.set_native_fn( + "delete_signature_requirement", + move |id_i64: INT| -> Result<(), Box> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE, + )) + })?; + collection.delete_by_id(id_u32).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!( + "Failed to delete SignatureRequirement (ID: {}): {:?}", + id_u32, e + ) + .into(), + Position::NONE, + )) + }) + }, + ); + let db_clone = Arc::clone(&db); - db_module.set_native_fn("list_signature_requirements", move || -> Result> { - let collection = db_clone.collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE - )))?; - let srs = collection.get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all signature requirements: {:?}", e).into(), - Position::NONE - )))?; - let mut array = Array::new(); - for sr in srs { - array.push(Dynamic::from(sr)); - } - Ok(Dynamic::from(array)) - }); - + db_module.set_native_fn( + "list_signature_requirements", + move || -> Result> { + let collection = db_clone.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get signature requirement collection: {:?}", e).into(), + Position::NONE, + )) + })?; + let srs = collection.get_all().map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all signature requirements: {:?}", e).into(), + Position::NONE, + )) + })?; + let mut array = Array::new(); + for sr in srs { + array.push(Dynamic::from(sr)); + } + Ok(Dynamic::from(array)) + }, + ); + // Register the database module globally engine.register_static_module("db", db_module.into()); - + // Register the flow module using exported_module! macro let module = exported_module!(rhai_flow_module); engine.register_global_module(module.into()); - + println!("Flow Rhai module registered."); } diff --git a/heromodels/src/models/flow/signature_requirement.rs b/heromodels/src/models/flow/signature_requirement.rs index 05093f6..453b99d 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -32,7 +32,12 @@ pub struct SignatureRequirement { impl SignatureRequirement { /// Create a new signature requirement. - pub fn new(_id: u32, flow_step_id: u32, public_key: impl ToString, message: impl ToString) -> Self { + pub fn new( + _id: u32, + flow_step_id: u32, + public_key: impl ToString, + message: impl ToString, + ) -> Self { Self { base_data: BaseModelData::new(), flow_step_id, diff --git a/heromodels/src/models/governance/mod.rs b/heromodels/src/models/governance/mod.rs index 5570233..e15c0f0 100644 --- a/heromodels/src/models/governance/mod.rs +++ b/heromodels/src/models/governance/mod.rs @@ -4,5 +4,5 @@ pub mod proposal; pub mod attached_file; -pub use self::proposal::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; -pub use self::attached_file::AttachedFile; \ No newline at end of file +pub use self::attached_file::AttachedFile; +pub use self::proposal::{Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption}; diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 8e9a1a3..6d64d31 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -5,9 +5,9 @@ use heromodels_derive::model; // For #[model] use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; -use heromodels_core::BaseModelData; -use crate::models::core::Comment; use super::AttachedFile; +use crate::models::core::Comment; +use heromodels_core::BaseModelData; // --- Enums --- @@ -72,9 +72,9 @@ impl VoteOption { #[model] // Has base.Base in V spec pub struct Ballot { pub base_data: BaseModelData, - pub user_id: u32, // The ID of the user who cast this ballot - pub vote_option_id: u8, // The 'id' of the VoteOption chosen - pub shares_count: i64, // Number of shares/tokens/voting power + pub user_id: u32, // The ID of the user who cast this ballot + pub vote_option_id: u8, // The 'id' of the VoteOption chosen + pub shares_count: i64, // Number of shares/tokens/voting power pub comment: Option, // Optional comment from the voter } @@ -281,7 +281,7 @@ impl Proposal { eprintln!("Voting is not open for proposal '{}'", self.title); return self; } - + // Check if the option exists if !self.options.iter().any(|opt| opt.id == chosen_option_id) { eprintln!( @@ -290,7 +290,7 @@ impl Proposal { ); return self; } - + // Check eligibility for private proposals if let Some(group) = &self.private_group { if !group.contains(&user_id) { @@ -301,14 +301,14 @@ impl Proposal { return self; } } - + // Create a new ballot with the comment let mut new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); new_ballot.comment = Some(comment.to_string()); - + // Add the ballot to the proposal self.ballots.push(new_ballot); - + // Update the vote count for the chosen option if let Some(option) = self .options @@ -317,7 +317,7 @@ impl Proposal { { option.count += shares; } - + self } } diff --git a/heromodels/src/models/legal/contract.rs b/heromodels/src/models/legal/contract.rs index f6cdd79..25fcbc9 100644 --- a/heromodels/src/models/legal/contract.rs +++ b/heromodels/src/models/legal/contract.rs @@ -1,7 +1,7 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -use std::fmt; use serde::{Deserialize, Serialize}; +use std::fmt; // --- Enums --- @@ -109,7 +109,7 @@ impl ContractSigner { self.signed_at = Some(signed_at); self } - + pub fn clear_signed_at(mut self) -> Self { self.signed_at = None; self @@ -139,21 +139,21 @@ pub struct Contract { pub title: String, pub description: String, - + #[index] pub contract_type: String, - + #[index] pub status: crate::models::ContractStatus, // Use re-exported path for #[model] macro - + pub created_by: String, pub terms_and_conditions: String, - + pub start_date: Option, pub end_date: Option, pub renewal_period_days: Option, pub next_renewal_date: Option, - + pub signers: Vec, pub revisions: Vec, pub current_version: u32, @@ -217,7 +217,7 @@ impl Contract { self.start_date = Some(start_date); self } - + pub fn clear_start_date(mut self) -> Self { self.start_date = None; self @@ -257,7 +257,7 @@ impl Contract { self.signers.push(signer); self } - + pub fn signers(mut self, signers: Vec) -> Self { self.signers = signers; self @@ -272,7 +272,7 @@ impl Contract { self.revisions = revisions; self } - + pub fn current_version(mut self, version: u32) -> Self { self.current_version = version; self @@ -287,7 +287,7 @@ impl Contract { self.last_signed_date = None; self } - + // Example methods for state changes pub fn set_status(&mut self, status: crate::models::ContractStatus) { self.status = status; diff --git a/heromodels/src/models/legal/rhai.rs b/heromodels/src/models/legal/rhai.rs index d374d54..afb83e8 100644 --- a/heromodels/src/models/legal/rhai.rs +++ b/heromodels/src/models/legal/rhai.rs @@ -1,51 +1,60 @@ -use rhai::{ - Dynamic, Engine, EvalAltResult, NativeCallContext, Position, Module, Array, -}; +use rhai::{Array, Dynamic, Engine, EvalAltResult, Module, NativeCallContext, Position}; use std::sync::Arc; use crate::db::hero::OurDB; // Updated path based on compiler suggestion // use heromodels_core::BaseModelData; // Removed as fields are accessed via contract.base_data directly -use crate::models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; +use crate::models::legal::{ + Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus, +}; use crate::db::Collection; // Import the Collection trait // --- Helper Functions for ID and Timestamp Conversion --- -fn i64_to_u32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_u32( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u32", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) }) } -fn i64_to_u64(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_u64( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u64", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) }) } -fn i64_to_i32(val: i64, context_pos: Position, field_name: &str, object_name: &str) -> Result> { +fn i64_to_i32( + val: i64, + context_pos: Position, + field_name: &str, + object_name: &str, +) -> Result> { val.try_into().map_err(|_e| { Box::new(EvalAltResult::ErrorArithmetic( format!( "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to i32", - field_name, - object_name, - val + field_name, object_name, val ), context_pos, )) @@ -73,193 +82,532 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { engine.register_static_module("SignerStatusConstants", signer_status_module.into()); engine.register_type_with_name::("SignerStatus"); // Expose the type itself - // --- ContractRevision --- + // --- ContractRevision --- engine.register_type_with_name::("ContractRevision"); engine.register_fn( "new_contract_revision", - move |context: NativeCallContext, version_i64: i64, content: String, created_at_i64: i64, created_by: String| -> Result> { - let version = i64_to_u32(version_i64, context.position(), "version", "new_contract_revision")?; - let created_at = i64_to_u64(created_at_i64, context.position(), "created_at", "new_contract_revision")?; - Ok(ContractRevision::new(version, content, created_at, created_by)) - } + move |context: NativeCallContext, + version_i64: i64, + content: String, + created_at_i64: i64, + created_by: String| + -> Result> { + let version = i64_to_u32( + version_i64, + context.call_position(), + "version", + "new_contract_revision", + )?; + let created_at = i64_to_u64( + created_at_i64, + context.call_position(), + "created_at", + "new_contract_revision", + )?; + Ok(ContractRevision::new( + version, content, created_at, created_by, + )) + }, + ); + engine.register_fn( + "comments", + |mut revision: ContractRevision, comments: String| -> ContractRevision { + revision.comments = Some(comments); + revision + }, + ); + engine.register_get( + "version", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.version as i64) + }, + ); + engine.register_get( + "content", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.content.clone()) + }, + ); + engine.register_get( + "created_at", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.created_at as i64) + }, + ); + engine.register_get( + "created_by", + |revision: &mut ContractRevision| -> Result> { + Ok(revision.created_by.clone()) + }, + ); + engine.register_get( + "comments", + |revision: &mut ContractRevision| -> Result> { + Ok(revision + .comments + .clone() + .map_or(Dynamic::UNIT, Dynamic::from)) + }, ); - engine.register_fn("comments", |mut revision: ContractRevision, comments: String| -> ContractRevision { - revision.comments = Some(comments); - revision - }); - engine.register_get("version", |revision: &mut ContractRevision| -> Result> { Ok(revision.version as i64) }); - engine.register_get("content", |revision: &mut ContractRevision| -> Result> { Ok(revision.content.clone()) }); - engine.register_get("created_at", |revision: &mut ContractRevision| -> Result> { Ok(revision.created_at as i64) }); - engine.register_get("created_by", |revision: &mut ContractRevision| -> Result> { Ok(revision.created_by.clone()) }); - engine.register_get("comments", |revision: &mut ContractRevision| -> Result> { - Ok(revision.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) - }); - // --- ContractSigner --- + // --- ContractSigner --- engine.register_type_with_name::("ContractSigner"); engine.register_fn( "new_contract_signer", |id: String, name: String, email: String| -> ContractSigner { ContractSigner::new(id, name, email) - } + }, + ); + engine.register_fn( + "status", + |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) }, + ); + engine.register_fn( + "signed_at", + |context: NativeCallContext, + signer: ContractSigner, + signed_at_i64: i64| + -> Result> { + let signed_at_u64 = i64_to_u64( + signed_at_i64, + context.call_position(), + "signed_at", + "ContractSigner.signed_at", + )?; + Ok(signer.signed_at(signed_at_u64)) + }, + ); + engine.register_fn( + "clear_signed_at", + |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() }, + ); + engine.register_fn( + "comments", + |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) }, + ); + engine.register_fn( + "clear_comments", + |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }, ); - engine.register_fn("status", |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) }); - engine.register_fn("signed_at", |context: NativeCallContext, signer: ContractSigner, signed_at_i64: i64| -> Result> { - let signed_at_u64 = i64_to_u64(signed_at_i64, context.position(), "signed_at", "ContractSigner.signed_at")?; - Ok(signer.signed_at(signed_at_u64)) - }); - engine.register_fn("clear_signed_at", |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() }); - engine.register_fn("comments", |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) }); - engine.register_fn("clear_comments", |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }); - engine.register_get("id", |signer: &mut ContractSigner| -> Result> { Ok(signer.id.clone()) }); - engine.register_get("name", |signer: &mut ContractSigner| -> Result> { Ok(signer.name.clone()) }); - engine.register_get("email", |signer: &mut ContractSigner| -> Result> { Ok(signer.email.clone()) }); - engine.register_get("status", |signer: &mut ContractSigner| -> Result> { Ok(signer.status.clone()) }); - engine.register_get("signed_at_ts", |signer: &mut ContractSigner| -> Result> { - Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("comments", |signer: &mut ContractSigner| -> Result> { - Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) - }); - engine.register_get("signed_at", |signer: &mut ContractSigner| -> Result> { - Ok(signer.signed_at.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts))) - }); + engine.register_get( + "id", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.id.clone()) + }, + ); + engine.register_get( + "name", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.name.clone()) + }, + ); + engine.register_get( + "email", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.email.clone()) + }, + ); + engine.register_get( + "status", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.status.clone()) + }, + ); + engine.register_get( + "signed_at_ts", + |signer: &mut ContractSigner| -> Result> { + Ok(signer + .signed_at + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "comments", + |signer: &mut ContractSigner| -> Result> { + Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) + }, + ); + engine.register_get( + "signed_at", + |signer: &mut ContractSigner| -> Result> { + Ok(signer + .signed_at + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts))) + }, + ); - // --- Contract --- + // --- Contract --- engine.register_type_with_name::("Contract"); engine.register_fn( "new_contract", - move |context: NativeCallContext, base_id_i64: i64, contract_id: String| -> Result> { - let base_id = i64_to_u32(base_id_i64, context.position(), "base_id", "new_contract")?; + move |context: NativeCallContext, + base_id_i64: i64, + contract_id: String| + -> Result> { + let base_id = i64_to_u32( + base_id_i64, + context.call_position(), + "base_id", + "new_contract", + )?; Ok(Contract::new(base_id, contract_id)) - } + }, ); // Builder methods - engine.register_fn("title", |contract: Contract, title: String| -> Contract { contract.title(title) }); - engine.register_fn("description", |contract: Contract, description: String| -> Contract { contract.description(description) }); - engine.register_fn("contract_type", |contract: Contract, contract_type: String| -> Contract { contract.contract_type(contract_type) }); - engine.register_fn("status", |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) }); - engine.register_fn("created_by", |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }); - engine.register_fn("terms_and_conditions", |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }); - - engine.register_fn("start_date", |context: NativeCallContext, contract: Contract, start_date_i64: i64| -> Result> { - let start_date_u64 = i64_to_u64(start_date_i64, context.position(), "start_date", "Contract.start_date")?; - Ok(contract.start_date(start_date_u64)) + engine.register_fn("title", |contract: Contract, title: String| -> Contract { + contract.title(title) }); - engine.register_fn("clear_start_date", |contract: Contract| -> Contract { contract.clear_start_date() }); + engine.register_fn( + "description", + |contract: Contract, description: String| -> Contract { contract.description(description) }, + ); + engine.register_fn( + "contract_type", + |contract: Contract, contract_type: String| -> Contract { + contract.contract_type(contract_type) + }, + ); + engine.register_fn( + "status", + |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) }, + ); + engine.register_fn( + "created_by", + |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }, + ); + engine.register_fn( + "terms_and_conditions", + |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }, + ); - engine.register_fn("end_date", |context: NativeCallContext, contract: Contract, end_date_i64: i64| -> Result> { - let end_date_u64 = i64_to_u64(end_date_i64, context.position(), "end_date", "Contract.end_date")?; - Ok(contract.end_date(end_date_u64)) - }); - engine.register_fn("clear_end_date", |contract: Contract| -> Contract { contract.clear_end_date() }); - - engine.register_fn("renewal_period_days", |context: NativeCallContext, contract: Contract, days_i64: i64| -> Result> { - let days_i32 = i64_to_i32(days_i64, context.position(), "renewal_period_days", "Contract.renewal_period_days")?; - Ok(contract.renewal_period_days(days_i32)) - }); - engine.register_fn("clear_renewal_period_days", |contract: Contract| -> Contract { contract.clear_renewal_period_days() }); - - engine.register_fn("next_renewal_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result> { - let date_u64 = i64_to_u64(date_i64, context.position(), "next_renewal_date", "Contract.next_renewal_date")?; - Ok(contract.next_renewal_date(date_u64)) - }); - engine.register_fn("clear_next_renewal_date", |contract: Contract| -> Contract { contract.clear_next_renewal_date() }); - - engine.register_fn("add_signer", |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) }); - engine.register_fn("signers", |contract: Contract, signers_array: Array| -> Contract { - let signers_vec = signers_array.into_iter().filter_map(|s| s.try_cast::()).collect(); - contract.signers(signers_vec) + engine.register_fn( + "start_date", + |context: NativeCallContext, + contract: Contract, + start_date_i64: i64| + -> Result> { + let start_date_u64 = i64_to_u64( + start_date_i64, + context.call_position(), + "start_date", + "Contract.start_date", + )?; + Ok(contract.start_date(start_date_u64)) + }, + ); + engine.register_fn("clear_start_date", |contract: Contract| -> Contract { + contract.clear_start_date() }); - engine.register_fn("add_revision", |contract: Contract, revision: ContractRevision| -> Contract { contract.add_revision(revision) }); - engine.register_fn("revisions", |contract: Contract, revisions_array: Array| -> Contract { - let revisions_vec = revisions_array.into_iter().filter_map(|r| r.try_cast::()).collect(); - contract.revisions(revisions_vec) + engine.register_fn( + "end_date", + |context: NativeCallContext, + contract: Contract, + end_date_i64: i64| + -> Result> { + let end_date_u64 = i64_to_u64( + end_date_i64, + context.call_position(), + "end_date", + "Contract.end_date", + )?; + Ok(contract.end_date(end_date_u64)) + }, + ); + engine.register_fn("clear_end_date", |contract: Contract| -> Contract { + contract.clear_end_date() }); - engine.register_fn("current_version", |context: NativeCallContext, contract: Contract, version_i64: i64| -> Result> { - let version_u32 = i64_to_u32(version_i64, context.position(), "current_version", "Contract.current_version")?; - Ok(contract.current_version(version_u32)) - }); + engine.register_fn( + "renewal_period_days", + |context: NativeCallContext, + contract: Contract, + days_i64: i64| + -> Result> { + let days_i32 = i64_to_i32( + days_i64, + context.call_position(), + "renewal_period_days", + "Contract.renewal_period_days", + )?; + Ok(contract.renewal_period_days(days_i32)) + }, + ); + engine.register_fn( + "clear_renewal_period_days", + |contract: Contract| -> Contract { contract.clear_renewal_period_days() }, + ); - engine.register_fn("last_signed_date", |context: NativeCallContext, contract: Contract, date_i64: i64| -> Result> { - let date_u64 = i64_to_u64(date_i64, context.position(), "last_signed_date", "Contract.last_signed_date")?; - Ok(contract.last_signed_date(date_u64)) + engine.register_fn( + "next_renewal_date", + |context: NativeCallContext, + contract: Contract, + date_i64: i64| + -> Result> { + let date_u64 = i64_to_u64( + date_i64, + context.call_position(), + "next_renewal_date", + "Contract.next_renewal_date", + )?; + Ok(contract.next_renewal_date(date_u64)) + }, + ); + engine.register_fn( + "clear_next_renewal_date", + |contract: Contract| -> Contract { contract.clear_next_renewal_date() }, + ); + + engine.register_fn( + "add_signer", + |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) }, + ); + engine.register_fn( + "signers", + |contract: Contract, signers_array: Array| -> Contract { + let signers_vec = signers_array + .into_iter() + .filter_map(|s| s.try_cast::()) + .collect(); + contract.signers(signers_vec) + }, + ); + + engine.register_fn( + "add_revision", + |contract: Contract, revision: ContractRevision| -> Contract { + contract.add_revision(revision) + }, + ); + engine.register_fn( + "revisions", + |contract: Contract, revisions_array: Array| -> Contract { + let revisions_vec = revisions_array + .into_iter() + .filter_map(|r| r.try_cast::()) + .collect(); + contract.revisions(revisions_vec) + }, + ); + + engine.register_fn( + "current_version", + |context: NativeCallContext, + contract: Contract, + version_i64: i64| + -> Result> { + let version_u32 = i64_to_u32( + version_i64, + context.call_position(), + "current_version", + "Contract.current_version", + )?; + Ok(contract.current_version(version_u32)) + }, + ); + + engine.register_fn( + "last_signed_date", + |context: NativeCallContext, + contract: Contract, + date_i64: i64| + -> Result> { + let date_u64 = i64_to_u64( + date_i64, + context.call_position(), + "last_signed_date", + "Contract.last_signed_date", + )?; + Ok(contract.last_signed_date(date_u64)) + }, + ); + engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { + contract.clear_last_signed_date() }); - engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { contract.clear_last_signed_date() }); // Getters for Contract - engine.register_get("id", |contract: &mut Contract| -> Result> { Ok(contract.base_data.id as i64) }); - engine.register_get("created_at_ts", |contract: &mut Contract| -> Result> { Ok(contract.base_data.created_at as i64) }); - engine.register_get("updated_at_ts", |contract: &mut Contract| -> Result> { Ok(contract.base_data.modified_at as i64) }); - engine.register_get("contract_id", |contract: &mut Contract| -> Result> { Ok(contract.contract_id.clone()) }); - engine.register_get("title", |contract: &mut Contract| -> Result> { Ok(contract.title.clone()) }); - engine.register_get("description", |contract: &mut Contract| -> Result> { Ok(contract.description.clone()) }); - engine.register_get("contract_type", |contract: &mut Contract| -> Result> { Ok(contract.contract_type.clone()) }); - engine.register_get("status", |contract: &mut Contract| -> Result> { Ok(contract.status.clone()) }); - engine.register_get("created_by", |contract: &mut Contract| -> Result> { Ok(contract.created_by.clone()) }); - engine.register_get("terms_and_conditions", |contract: &mut Contract| -> Result> { Ok(contract.terms_and_conditions.clone()) }); - - engine.register_get("start_date", |contract: &mut Contract| -> Result> { - Ok(contract.start_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("end_date", |contract: &mut Contract| -> Result> { - Ok(contract.end_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("renewal_period_days", |contract: &mut Contract| -> Result> { - Ok(contract.renewal_period_days.map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64))) - }); - engine.register_get("next_renewal_date", |contract: &mut Contract| -> Result> { - Ok(contract.next_renewal_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); - engine.register_get("last_signed_date", |contract: &mut Contract| -> Result> { - Ok(contract.last_signed_date.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }); + engine.register_get( + "id", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.id as i64) + }, + ); + engine.register_get( + "created_at_ts", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.created_at as i64) + }, + ); + engine.register_get( + "updated_at_ts", + |contract: &mut Contract| -> Result> { + Ok(contract.base_data.modified_at as i64) + }, + ); + engine.register_get( + "contract_id", + |contract: &mut Contract| -> Result> { + Ok(contract.contract_id.clone()) + }, + ); + engine.register_get( + "title", + |contract: &mut Contract| -> Result> { + Ok(contract.title.clone()) + }, + ); + engine.register_get( + "description", + |contract: &mut Contract| -> Result> { + Ok(contract.description.clone()) + }, + ); + engine.register_get( + "contract_type", + |contract: &mut Contract| -> Result> { + Ok(contract.contract_type.clone()) + }, + ); + engine.register_get( + "status", + |contract: &mut Contract| -> Result> { + Ok(contract.status.clone()) + }, + ); + engine.register_get( + "created_by", + |contract: &mut Contract| -> Result> { + Ok(contract.created_by.clone()) + }, + ); + engine.register_get( + "terms_and_conditions", + |contract: &mut Contract| -> Result> { + Ok(contract.terms_and_conditions.clone()) + }, + ); - engine.register_get("current_version", |contract: &mut Contract| -> Result> { Ok(contract.current_version as i64) }); + engine.register_get( + "start_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .start_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "end_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .end_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "renewal_period_days", + |contract: &mut Contract| -> Result> { + Ok(contract + .renewal_period_days + .map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64))) + }, + ); + engine.register_get( + "next_renewal_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .next_renewal_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); + engine.register_get( + "last_signed_date", + |contract: &mut Contract| -> Result> { + Ok(contract + .last_signed_date + .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) + }, + ); - engine.register_get("signers", |contract: &mut Contract| -> Result> { - let rhai_array = contract.signers.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }); - engine.register_get("revisions", |contract: &mut Contract| -> Result> { - let rhai_array = contract.revisions.iter().cloned().map(Dynamic::from).collect::(); - Ok(rhai_array) - }); + engine.register_get( + "current_version", + |contract: &mut Contract| -> Result> { + Ok(contract.current_version as i64) + }, + ); + + engine.register_get( + "signers", + |contract: &mut Contract| -> Result> { + let rhai_array = contract + .signers + .iter() + .cloned() + .map(Dynamic::from) + .collect::(); + Ok(rhai_array) + }, + ); + engine.register_get( + "revisions", + |contract: &mut Contract| -> Result> { + let rhai_array = contract + .revisions + .iter() + .cloned() + .map(Dynamic::from) + .collect::(); + Ok(rhai_array) + }, + ); // Method set_status - engine.register_fn("set_contract_status", |contract: &mut Contract, status: ContractStatus| { - contract.set_status(status); - }); + engine.register_fn( + "set_contract_status", + |contract: &mut Contract, status: ContractStatus| { + contract.set_status(status); + }, + ); // --- Database Interaction --- let captured_db_for_set = Arc::clone(&db); - engine.register_fn("set_contract", + engine.register_fn( + "set_contract", move |contract: Contract| -> Result<(), Box> { captured_db_for_set.set(&contract).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to set Contract (ID: {}): {}", contract.base_data.id, e).into(), + format!( + "Failed to set Contract (ID: {}): {}", + contract.base_data.id, e + ) + .into(), Position::NONE, )) }) - }); + }, + ); let captured_db_for_get = Arc::clone(&db); - engine.register_fn("get_contract_by_id", + engine.register_fn( + "get_contract_by_id", move |context: NativeCallContext, id_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_contract_by_id")?; - - captured_db_for_get.get_by_id(id_u32) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( - format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), - Position::NONE, - )))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime( - format!("Contract with ID {} not found", id_u32).into(), - Position::NONE, - ))) - }); + let id_u32 = i64_to_u32(id_i64, context.call_position(), "id", "get_contract_by_id")?; + + captured_db_for_get + .get_by_id(id_u32) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Contract with ID {} not found", id_u32).into(), + Position::NONE, + )) + }) + }, + ); } diff --git a/heromodels/src/models/library/collection.rs b/heromodels/src/models/library/collection.rs index 220109c..c0acead 100644 --- a/heromodels/src/models/library/collection.rs +++ b/heromodels/src/models/library/collection.rs @@ -1,7 +1,7 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; /// Represents a collection of library items. #[model] diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs index 1bd2cdd..734f04b 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/heromodels/src/models/library/rhai.rs @@ -1,26 +1,28 @@ -use rhai::plugin::*; -use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder, Position, Module, Dynamic, Array}; -use std::sync::Arc; -use std::mem; use crate::db::Db; +use rhai::plugin::*; +use rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, Position, TypeBuilder}; use serde::Serialize; use serde_json; +use std::mem; +use std::sync::Arc; - -use super::collection::{Collection as RhaiCollection}; -use super::items::{Image as RhaiImage, Pdf as RhaiPdf, Markdown as RhaiMarkdown, Book as RhaiBook, Slides as RhaiSlides, TocEntry as RhaiTocEntry}; -use crate::db::hero::OurDB; +use super::collection::Collection as RhaiCollection; +use super::items::{ + Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, + Slides as RhaiSlides, TocEntry as RhaiTocEntry, +}; use crate::db::Collection as DbCollectionTrait; +use crate::db::hero::OurDB; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| + u32::try_from(id_i64).map_err(|_| { Box::new(EvalAltResult::ErrorMismatchDataType( "u32".to_string(), // Expected type format!("i64 value ({}) that cannot be represented as u32", id_i64), // Actual type/value description - Position::NONE + Position::NONE, )) - ) + }) } /// Registers a `.json()` method for any type `T` that implements the required traits. @@ -33,7 +35,7 @@ where let to_json_fn = |obj: &mut T| -> Result> { // Use serde_json to serialize the object to a pretty-formatted string. // The '?' will automatically convert any serialization error into a Rhai error. - serde_json::to_string(obj).map_err(|e| e.to_string().into()) + serde_json::to_string_pretty(obj).map_err(|e| e.to_string().into()) }; // Register the function as a method named "json" for the type 'T'. @@ -45,7 +47,6 @@ where #[rhai_type(name = "CollectionArray")] pub struct RhaiCollectionArray(pub Vec); - #[export_module] mod rhai_library_module { // --- Collection Functions --- @@ -55,29 +56,41 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn collection_title(collection: &mut RhaiCollection, title: String) -> Result> { + pub fn collection_title( + collection: &mut RhaiCollection, + title: String, + ) -> Result> { let owned = mem::take(collection); *collection = owned.title(title); Ok(collection.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn collection_description(collection: &mut RhaiCollection, description: String) -> Result> { + pub fn collection_description( + collection: &mut RhaiCollection, + description: String, + ) -> Result> { let owned = mem::take(collection); *collection = owned.description(description); Ok(collection.clone()) } #[rhai_fn(name = "add_image", return_raw, global, pure)] - pub fn collection_add_image(collection: &mut RhaiCollection, image_id: i64) -> Result> { + pub fn collection_add_image( + collection: &mut RhaiCollection, + image_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(image_id)?; let owned = mem::take(collection); *collection = owned.add_image(id); Ok(collection.clone()) } - + #[rhai_fn(name = "add_pdf", return_raw, global, pure)] - pub fn collection_add_pdf(collection: &mut RhaiCollection, pdf_id: i64) -> Result> { + pub fn collection_add_pdf( + collection: &mut RhaiCollection, + pdf_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(pdf_id)?; let owned = mem::take(collection); *collection = owned.add_pdf(id); @@ -85,7 +98,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_markdown", return_raw, global, pure)] - pub fn collection_add_markdown(collection: &mut RhaiCollection, markdown_id: i64) -> Result> { + pub fn collection_add_markdown( + collection: &mut RhaiCollection, + markdown_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(markdown_id)?; let owned = mem::take(collection); *collection = owned.add_markdown(id); @@ -93,7 +109,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_book", return_raw, global, pure)] - pub fn collection_add_book(collection: &mut RhaiCollection, book_id: i64) -> Result> { + pub fn collection_add_book( + collection: &mut RhaiCollection, + book_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(book_id)?; let owned = mem::take(collection); *collection = owned.add_book(id); @@ -101,7 +120,10 @@ mod rhai_library_module { } #[rhai_fn(name = "add_slides", return_raw, global, pure)] - pub fn collection_add_slides(collection: &mut RhaiCollection, slides_id: i64) -> Result> { + pub fn collection_add_slides( + collection: &mut RhaiCollection, + slides_id: i64, + ) -> Result> { let id = id_from_i64_to_u32(slides_id)?; let owned = mem::take(collection); *collection = owned.add_slides(id); @@ -109,34 +131,79 @@ mod rhai_library_module { } #[rhai_fn(get = "id", pure)] - pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { collection.base_data.id as i64 } - + pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { + collection.base_data.id as i64 + } + #[rhai_fn(get = "created_at", pure)] - pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.created_at } - + pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { + collection.base_data.created_at + } + #[rhai_fn(get = "modified_at", pure)] - pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.modified_at } - + pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { + collection.base_data.modified_at + } + #[rhai_fn(get = "title", pure)] - pub fn get_collection_title(collection: &mut RhaiCollection) -> String { collection.title.clone() } - + pub fn get_collection_title(collection: &mut RhaiCollection) -> String { + collection.title.clone() + } + #[rhai_fn(get = "description", pure)] - pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { collection.description.clone() } - + pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { + collection.description.clone() + } + #[rhai_fn(get = "images", pure)] - pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { collection.images.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { + collection + .images + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "pdfs", pure)] - pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { collection.pdfs.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { + collection + .pdfs + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "markdowns", pure)] - pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { collection.markdowns.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { + collection + .markdowns + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "books", pure)] - pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { collection.books.clone().into_iter().map(|id| id as i64).collect() } - + pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { + collection + .books + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } + #[rhai_fn(get = "slides", pure)] - pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { collection.slides.clone().into_iter().map(|id| id as i64).collect() } + pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { + collection + .slides + .clone() + .into_iter() + .map(|id| id as i64) + .collect() + } // --- Image Functions --- #[rhai_fn(name = "new_image")] @@ -145,14 +212,20 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn image_title(image: &mut RhaiImage, title: String) -> Result> { + pub fn image_title( + image: &mut RhaiImage, + title: String, + ) -> Result> { let owned = mem::take(image); *image = owned.title(title); Ok(image.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn image_description(image: &mut RhaiImage, description: String) -> Result> { + pub fn image_description( + image: &mut RhaiImage, + description: String, + ) -> Result> { let owned = mem::take(image); *image = owned.description(description); Ok(image.clone()) @@ -173,35 +246,54 @@ mod rhai_library_module { } #[rhai_fn(name = "height", return_raw, global, pure)] - pub fn image_height(image: &mut RhaiImage, height: i64) -> Result> { + pub fn image_height( + image: &mut RhaiImage, + height: i64, + ) -> Result> { let owned = mem::take(image); *image = owned.height(height as u32); Ok(image.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_image_id(image: &mut RhaiImage) -> i64 { image.base_data.id as i64 } + pub fn get_image_id(image: &mut RhaiImage) -> i64 { + image.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { image.base_data.created_at } + pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { + image.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { image.base_data.modified_at } + pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { + image.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_image_title(image: &mut RhaiImage) -> String { image.title.clone() } + pub fn get_image_title(image: &mut RhaiImage) -> String { + image.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_image_description(image: &mut RhaiImage) -> Option { image.description.clone() } + pub fn get_image_description(image: &mut RhaiImage) -> Option { + image.description.clone() + } #[rhai_fn(get = "url", pure)] - pub fn get_image_url(image: &mut RhaiImage) -> String { image.url.clone() } + pub fn get_image_url(image: &mut RhaiImage) -> String { + image.url.clone() + } #[rhai_fn(get = "width", pure)] - pub fn get_image_width(image: &mut RhaiImage) -> u32 { image.width } + pub fn get_image_width(image: &mut RhaiImage) -> u32 { + image.width + } #[rhai_fn(get = "height", pure)] - pub fn get_image_height(image: &mut RhaiImage) -> u32 { image.height } + pub fn get_image_height(image: &mut RhaiImage) -> u32 { + image.height + } // --- Pdf Functions --- #[rhai_fn(name = "new_pdf")] @@ -217,7 +309,10 @@ mod rhai_library_module { } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn pdf_description(pdf: &mut RhaiPdf, description: String) -> Result> { + pub fn pdf_description( + pdf: &mut RhaiPdf, + description: String, + ) -> Result> { let owned = mem::take(pdf); *pdf = owned.description(description); Ok(pdf.clone()) @@ -231,32 +326,49 @@ mod rhai_library_module { } #[rhai_fn(name = "page_count", return_raw, global, pure)] - pub fn pdf_page_count(pdf: &mut RhaiPdf, page_count: i64) -> Result> { + pub fn pdf_page_count( + pdf: &mut RhaiPdf, + page_count: i64, + ) -> Result> { let owned = mem::take(pdf); *pdf = owned.page_count(page_count as u32); Ok(pdf.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.id as i64 } + pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.created_at } + pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.modified_at } + pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { + pdf.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { pdf.title.clone() } + pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { + pdf.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { pdf.description.clone() } + pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { + pdf.description.clone() + } #[rhai_fn(get = "url", pure)] - pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { pdf.url.clone() } + pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { + pdf.url.clone() + } #[rhai_fn(get = "page_count", pure)] - pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { pdf.page_count } + pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { + pdf.page_count + } // --- Markdown Functions --- #[rhai_fn(name = "new_markdown")] @@ -265,43 +377,64 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn markdown_title(markdown: &mut RhaiMarkdown, title: String) -> Result> { + pub fn markdown_title( + markdown: &mut RhaiMarkdown, + title: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.title(title); Ok(markdown.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn markdown_description(markdown: &mut RhaiMarkdown, description: String) -> Result> { + pub fn markdown_description( + markdown: &mut RhaiMarkdown, + description: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.description(description); Ok(markdown.clone()) } #[rhai_fn(name = "content", return_raw, global, pure)] - pub fn markdown_content(markdown: &mut RhaiMarkdown, content: String) -> Result> { + pub fn markdown_content( + markdown: &mut RhaiMarkdown, + content: String, + ) -> Result> { let owned = mem::take(markdown); *markdown = owned.content(content); Ok(markdown.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.id as i64 } + pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.created_at } + pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.modified_at } + pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { + markdown.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { markdown.title.clone() } + pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { + markdown.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { markdown.description.clone() } + pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { + markdown.description.clone() + } #[rhai_fn(get = "content", pure)] - pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { markdown.content.clone() } + pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { + markdown.content.clone() + } // --- TocEntry Functions --- #[rhai_fn(name = "new_toc_entry")] @@ -310,34 +443,49 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn toc_entry_title(entry: &mut RhaiTocEntry, title: String) -> Result> { + pub fn toc_entry_title( + entry: &mut RhaiTocEntry, + title: String, + ) -> Result> { let owned = mem::take(entry); *entry = owned.title(title); Ok(entry.clone()) } #[rhai_fn(name = "page", return_raw, global, pure)] - pub fn toc_entry_page(entry: &mut RhaiTocEntry, page: i64) -> Result> { + pub fn toc_entry_page( + entry: &mut RhaiTocEntry, + page: i64, + ) -> Result> { let owned = mem::take(entry); *entry = owned.page(page as u32); Ok(entry.clone()) } #[rhai_fn(name = "add_subsection", return_raw, global, pure)] - pub fn toc_entry_add_subsection(entry: &mut RhaiTocEntry, subsection: RhaiTocEntry) -> Result> { + pub fn toc_entry_add_subsection( + entry: &mut RhaiTocEntry, + subsection: RhaiTocEntry, + ) -> Result> { let owned = mem::take(entry); *entry = owned.add_subsection(subsection); Ok(entry.clone()) } #[rhai_fn(get = "title", pure)] - pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { entry.title.clone() } + pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { + entry.title.clone() + } #[rhai_fn(get = "page", pure)] - pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { entry.page } + pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { + entry.page + } #[rhai_fn(get = "subsections", pure)] - pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { entry.subsections.clone() } + pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { + entry.subsections.clone() + } // --- Book Functions --- #[rhai_fn(name = "new_book")] @@ -353,46 +501,69 @@ mod rhai_library_module { } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn book_description(book: &mut RhaiBook, description: String) -> Result> { + pub fn book_description( + book: &mut RhaiBook, + description: String, + ) -> Result> { let owned = mem::take(book); *book = owned.description(description); Ok(book.clone()) } #[rhai_fn(name = "add_page", return_raw, global, pure)] - pub fn book_add_page(book: &mut RhaiBook, content: String) -> Result> { + pub fn book_add_page( + book: &mut RhaiBook, + content: String, + ) -> Result> { let owned = mem::take(book); *book = owned.add_page(content); Ok(book.clone()) } #[rhai_fn(name = "add_toc_entry", return_raw, global, pure)] - pub fn book_add_toc_entry(book: &mut RhaiBook, entry: RhaiTocEntry) -> Result> { + pub fn book_add_toc_entry( + book: &mut RhaiBook, + entry: RhaiTocEntry, + ) -> Result> { let owned = mem::take(book); *book = owned.add_toc_entry(entry); Ok(book.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_book_id(book: &mut RhaiBook) -> i64 { book.base_data.id as i64 } + pub fn get_book_id(book: &mut RhaiBook) -> i64 { + book.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { book.base_data.created_at } + pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { + book.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { book.base_data.modified_at } + pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { + book.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_book_title(book: &mut RhaiBook) -> String { book.title.clone() } + pub fn get_book_title(book: &mut RhaiBook) -> String { + book.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_book_description(book: &mut RhaiBook) -> Option { book.description.clone() } + pub fn get_book_description(book: &mut RhaiBook) -> Option { + book.description.clone() + } #[rhai_fn(get = "table_of_contents", pure)] - pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { book.table_of_contents.clone() } + pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { + book.table_of_contents.clone() + } #[rhai_fn(get = "pages", pure)] - pub fn get_book_pages(book: &mut RhaiBook) -> Vec { book.pages.clone() } + pub fn get_book_pages(book: &mut RhaiBook) -> Vec { + book.pages.clone() + } // --- Slides Functions --- #[rhai_fn(name = "new_slides")] @@ -401,21 +572,31 @@ mod rhai_library_module { } #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn slides_title(slides: &mut RhaiSlides, title: String) -> Result> { + pub fn slides_title( + slides: &mut RhaiSlides, + title: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.title(title); Ok(slides.clone()) } #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn slides_description(slides: &mut RhaiSlides, description: String) -> Result> { + pub fn slides_description( + slides: &mut RhaiSlides, + description: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.description(description); Ok(slides.clone()) } #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide(slides: &mut RhaiSlides, url: String, title: String) -> Result> { + pub fn slides_add_slide( + slides: &mut RhaiSlides, + url: String, + title: String, + ) -> Result> { let owned = mem::take(slides); let title_opt = if title.is_empty() { None } else { Some(title) }; *slides = owned.add_slide(url, title_opt); @@ -423,32 +604,49 @@ mod rhai_library_module { } #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide_no_title(slides: &mut RhaiSlides, url: String) -> Result> { + pub fn slides_add_slide_no_title( + slides: &mut RhaiSlides, + url: String, + ) -> Result> { let owned = mem::take(slides); *slides = owned.add_slide(url, None); Ok(slides.clone()) } #[rhai_fn(get = "id", pure)] - pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { slides.base_data.id as i64 } + pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { + slides.base_data.id as i64 + } #[rhai_fn(get = "created_at", pure)] - pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.created_at } + pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { + slides.base_data.created_at + } #[rhai_fn(get = "modified_at", pure)] - pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.modified_at } + pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { + slides.base_data.modified_at + } #[rhai_fn(get = "title", pure)] - pub fn get_slides_title(slides: &mut RhaiSlides) -> String { slides.title.clone() } + pub fn get_slides_title(slides: &mut RhaiSlides) -> String { + slides.title.clone() + } #[rhai_fn(get = "description", pure)] - pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { slides.description.clone() } + pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { + slides.description.clone() + } #[rhai_fn(get = "slide_urls", pure)] - pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { slides.slide_urls.clone() } + pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { + slides.slide_urls.clone() + } #[rhai_fn(get = "slide_titles", pure)] - pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { slides.slide_titles.clone() } + pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { + slides.slide_titles.clone() + } } pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { @@ -470,157 +668,362 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { // --- Collection DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_collection", move |collection: RhaiCollection| -> Result> { - let result = db_clone.set(&collection) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_collection", + move |collection: RhaiCollection| -> Result> { + let result = db_clone.set(&collection).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_collection", move |id: i64| -> Result> { - let collection_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(collection_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Collection with ID {} not found", collection_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_collection", + move |id: i64| -> Result> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(collection_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Collection with ID {} not found", collection_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone_list_collections = db.clone(); - db_module.set_native_fn("list_collections", move || -> Result> { - let collections_vec: Vec = db_clone_list_collections - .collection::() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - access): {:?}", e).into(), Position::NONE)))? - .get_all() - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - get_all): {:?}", e).into(), Position::NONE)))?; - Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray - }); + db_module.set_native_fn( + "list_collections", + move || -> Result> { + let collections_vec: Vec = db_clone_list_collections + .collection::() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error (list_collections - access): {:?}", e).into(), + Position::NONE, + )) + })? + .get_all() + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error (list_collections - get_all): {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_collection", move |id: i64| -> Result<(), Box> { - let collection_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(collection_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_collection", + move |id: i64| -> Result<(), Box> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(collection_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Image DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_image", move |image: RhaiImage| -> Result> { - let result = db_clone.set(&image) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_image", + move |image: RhaiImage| -> Result> { + let result = db_clone.set(&image).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_image", move |id: i64| -> Result> { - let image_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(image_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Image with ID {} not found", image_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_image", + move |id: i64| -> Result> { + let image_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(image_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Image with ID {} not found", image_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_image", move |id: i64| -> Result<(), Box> { - let image_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(image_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_image", + move |id: i64| -> Result<(), Box> { + let image_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(image_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Pdf DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_pdf", move |pdf: RhaiPdf| -> Result> { - let result = db_clone.set(&pdf) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_pdf", + move |pdf: RhaiPdf| -> Result> { + let result = db_clone.set(&pdf).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_pdf", move |id: i64| -> Result> { - let pdf_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(pdf_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Pdf with ID {} not found", pdf_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_pdf", + move |id: i64| -> Result> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(pdf_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Pdf with ID {} not found", pdf_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_pdf", move |id: i64| -> Result<(), Box> { - let pdf_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(pdf_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_pdf", + move |id: i64| -> Result<(), Box> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(pdf_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Markdown DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_markdown", move |markdown: RhaiMarkdown| -> Result> { - let result = db_clone.set(&markdown) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_markdown", + move |markdown: RhaiMarkdown| -> Result> { + let result = db_clone.set(&markdown).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_markdown", move |id: i64| -> Result> { - let markdown_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(markdown_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Markdown with ID {} not found", markdown_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_markdown", + move |id: i64| -> Result> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(markdown_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Markdown with ID {} not found", markdown_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_markdown", move |id: i64| -> Result<(), Box> { - let markdown_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(markdown_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_markdown", + move |id: i64| -> Result<(), Box> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(markdown_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Book DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_book", move |book: RhaiBook| -> Result> { - let result = db_clone.set(&book) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_book", + move |book: RhaiBook| -> Result> { + let result = db_clone.set(&book).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_book", move |id: i64| -> Result> { - let book_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(book_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Book with ID {} not found", book_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_book", + move |id: i64| -> Result> { + let book_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(book_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Book with ID {} not found", book_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_book", move |id: i64| -> Result<(), Box> { - let book_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(book_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_book", + move |id: i64| -> Result<(), Box> { + let book_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(book_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); // --- Slides DB Functions --- let db_clone = db.clone(); - db_module.set_native_fn("save_slides", move |slides: RhaiSlides| -> Result> { - let result = db_clone.set(&slides) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(result.1) - }); + db_module.set_native_fn( + "save_slides", + move |slides: RhaiSlides| -> Result> { + let result = db_clone.set(&slides).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("get_slides", move |id: i64| -> Result> { - let slides_id = id_from_i64_to_u32(id)?; - db_clone.get_by_id(slides_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? - .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Slides with ID {} not found", slides_id).into(), Position::NONE))) - }); + db_module.set_native_fn( + "get_slides", + move |id: i64| -> Result> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone + .get_by_id(slides_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })? + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Slides with ID {} not found", slides_id).into(), + Position::NONE, + )) + }) + }, + ); let db_clone = db.clone(); - db_module.set_native_fn("delete_slides", move |id: i64| -> Result<(), Box> { - let slides_id = id_from_i64_to_u32(id)?; - db_clone.collection::().unwrap().delete_by_id(slides_id) - .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; - Ok(()) - }); + db_module.set_native_fn( + "delete_slides", + move |id: i64| -> Result<(), Box> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone + .collection::() + .unwrap() + .delete_by_id(slides_id) + .map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(()) + }, + ); engine.register_global_module(db_module.into()); } diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 42cc76e..7763482 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -3,41 +3,41 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing pub mod access; -pub mod calendar; -pub mod contact; -pub mod circle; -pub mod governance; -pub mod finance; -pub mod library; -pub mod legal; -pub mod flow; pub mod biz; +pub mod calendar; +pub mod circle; +pub mod contact; +pub mod finance; +pub mod flow; +pub mod governance; +pub mod legal; +pub mod library; pub mod projects; // Re-export key types for convenience pub use core::Comment; pub use userexample::User; // pub use productexample::Product; // Temporarily remove -pub use calendar::{Calendar, Event, Attendee, AttendanceStatus}; -pub use circle::{Circle}; -pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile}; -pub use finance::{Account, Asset, AssetType}; -pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; -pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; -pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use biz::{Sale, SaleItem, SaleStatus}; -pub use library::items::{Image, Pdf, Markdown}; +pub use calendar::{AttendanceStatus, Attendee, Calendar, Event}; +pub use circle::Circle; +pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; +pub use finance::{Account, Asset, AssetType}; +pub use flow::{Flow, FlowStep, SignatureRequirement}; +pub use governance::{AttachedFile, Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption}; +pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; pub use library::collection::Collection; +pub use library::items::{Image, Markdown, Pdf}; -pub use flow::register_flow_rhai_module; +#[cfg(feature = "rhai")] +pub use biz::register_biz_rhai_module; #[cfg(feature = "rhai")] pub use calendar::register_calendar_rhai_module; #[cfg(feature = "rhai")] pub use circle::register_circle_rhai_module; +pub use flow::register_flow_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] -pub use biz::register_biz_rhai_module; +pub use library::register_library_rhai_module; #[cfg(feature = "rhai")] pub use projects::register_projects_rhai_module; -#[cfg(feature = "rhai")] -pub use library::register_library_rhai_module; diff --git a/heromodels/src/models/projects/base.rs b/heromodels/src/models/projects/base.rs index d3ffefd..adbc059 100644 --- a/heromodels/src/models/projects/base.rs +++ b/heromodels/src/models/projects/base.rs @@ -1,8 +1,8 @@ // heromodels/src/models/projects/base.rs -use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; +use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; #[cfg(feature = "rhai")] use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use strum_macros::Display; // Made unconditional as Display derive is used on non-rhai-gated enums // --- Enums --- @@ -50,7 +50,7 @@ pub enum ItemType { impl Default for ItemType { fn default() -> Self { ItemType::Task - } + } } // --- Structs --- @@ -178,7 +178,6 @@ impl Project { } } - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "rhai", derive(CustomType))] pub struct Label { @@ -226,4 +225,3 @@ impl Label { self } } - diff --git a/heromodels/src/models/projects/epic.rs b/heromodels/src/models/projects/epic.rs index d96b4d2..657cbee 100644 --- a/heromodels/src/models/projects/epic.rs +++ b/heromodels/src/models/projects/epic.rs @@ -3,8 +3,8 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::base::Status as ProjectStatus; // Using the generic project status for now @@ -15,17 +15,17 @@ pub struct Epic { pub name: String, pub description: Option, pub status: ProjectStatus, // Or a new EpicStatus enum if needed - + pub project_id: Option, // Link to a general project/board pub start_date: Option>, pub due_date: Option>, - + pub tags: Vec, - + // Explicitly list task IDs belonging to this epic // This helps in querying and avoids relying solely on tasks pointing to the epic. - pub child_task_ids: Vec, + pub child_task_ids: Vec, } impl Epic { diff --git a/heromodels/src/models/projects/mod.rs b/heromodels/src/models/projects/mod.rs index ca20fb8..afdab0e 100644 --- a/heromodels/src/models/projects/mod.rs +++ b/heromodels/src/models/projects/mod.rs @@ -1,11 +1,11 @@ // heromodels/src/models/projects/mod.rs pub mod base; -pub mod task_enums; -pub mod task; pub mod epic; -pub mod sprint_enums; pub mod sprint; +pub mod sprint_enums; +pub mod task; +pub mod task_enums; // pub mod epic; // pub mod issue; // pub mod kanban; @@ -13,11 +13,11 @@ pub mod sprint; // pub mod story; pub use base::*; -pub use task_enums::*; -pub use task::*; pub use epic::*; -pub use sprint_enums::*; pub use sprint::*; +pub use sprint_enums::*; +pub use task::*; +pub use task_enums::*; // pub use epic::*; // pub use issue::*; // pub use kanban::*; @@ -25,7 +25,7 @@ pub use sprint::*; // pub use story::*; #[cfg(feature = "rhai")] -pub mod rhai; +pub mod rhai; #[cfg(feature = "rhai")] pub use rhai::register_projects_rhai_module; diff --git a/heromodels/src/models/projects/rhai.rs b/heromodels/src/models/projects/rhai.rs index 27fd7ce..6f38196 100644 --- a/heromodels/src/models/projects/rhai.rs +++ b/heromodels/src/models/projects/rhai.rs @@ -1,14 +1,13 @@ // heromodels/src/models/projects/rhai.rs -use rhai::{Engine, EvalAltResult, Dynamic, Position}; -use std::sync::Arc; use crate::db::hero::OurDB; -use heromodels_core::{Model, BaseModelDataOps}; -use crate::db::{Db, Collection}; - +use crate::db::{Collection, Db}; +use heromodels_core::{BaseModelDataOps, Model}; +use rhai::{Dynamic, Engine, EvalAltResult, Position}; +use std::sync::Arc; // Import models from the projects::base module -use super::base::{Project, /* Label, */ Priority, Status, ItemType}; // Label commented out as it's unused for now +use super::base::{ItemType, /* Label, */ Priority, Project, Status}; // Label commented out as it's unused for now // Helper function for ID conversion (if needed, similar to other rhai.rs files) fn id_from_i64(val: i64) -> Result> { @@ -78,172 +77,325 @@ pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc) { }); // Multi-argument constructor (renamed) - engine.register_fn("new_project_with_details", |id_i64: i64, name: String, description: String, owner_id_i64: i64| -> Result> { - Ok(Project::new(id_from_i64(id_i64)?, name, description, id_from_i64(owner_id_i64)?)) - }); + engine.register_fn( + "new_project_with_details", + |id_i64: i64, + name: String, + description: String, + owner_id_i64: i64| + -> Result> { + Ok(Project::new( + id_from_i64(id_i64)?, + name, + description, + id_from_i64(owner_id_i64)?, + )) + }, + ); // Getters for Project - engine.register_get("id", |p: &mut Project| -> Result> { Ok(p.get_id() as i64) }); - engine.register_get("name", |p: &mut Project| -> Result> { Ok(p.name.clone()) }); - engine.register_get("description", |p: &mut Project| -> Result> { Ok(p.description.clone()) }); - engine.register_get("owner_id", |p: &mut Project| -> Result> { Ok(p.owner_id as i64) }); - engine.register_get("member_ids", |p: &mut Project| -> Result> { - Ok(p.member_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) + engine.register_get("id", |p: &mut Project| -> Result> { + Ok(p.get_id() as i64) }); - engine.register_get("board_ids", |p: &mut Project| -> Result> { - Ok(p.board_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("sprint_ids", |p: &mut Project| -> Result> { - Ok(p.sprint_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("epic_ids", |p: &mut Project| -> Result> { - Ok(p.epic_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("tags", |p: &mut Project| -> Result> { - Ok(p.tags.iter().map(|tag| rhai::Dynamic::from(tag.clone())).collect()) - }); - engine.register_get("created_at", |p: &mut Project| -> Result> { Ok(p.base_data.created_at) }); - engine.register_get("modified_at", |p: &mut Project| -> Result> { Ok(p.base_data.modified_at) }); - engine.register_get("comments", |p: &mut Project| -> Result> { - Ok(p.base_data.comments.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect()) - }); - engine.register_get("status", |p: &mut Project| -> Result> { Ok(p.status.clone()) }); - engine.register_get("priority", |p: &mut Project| -> Result> { Ok(p.priority.clone()) }); - engine.register_get("item_type", |p: &mut Project| -> Result> { Ok(p.item_type.clone()) }); + engine.register_get( + "name", + |p: &mut Project| -> Result> { Ok(p.name.clone()) }, + ); + engine.register_get( + "description", + |p: &mut Project| -> Result> { Ok(p.description.clone()) }, + ); + engine.register_get( + "owner_id", + |p: &mut Project| -> Result> { Ok(p.owner_id as i64) }, + ); + engine.register_get( + "member_ids", + |p: &mut Project| -> Result> { + Ok(p.member_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "board_ids", + |p: &mut Project| -> Result> { + Ok(p.board_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "sprint_ids", + |p: &mut Project| -> Result> { + Ok(p.sprint_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "epic_ids", + |p: &mut Project| -> Result> { + Ok(p.epic_ids + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "tags", + |p: &mut Project| -> Result> { + Ok(p.tags + .iter() + .map(|tag| rhai::Dynamic::from(tag.clone())) + .collect()) + }, + ); + engine.register_get( + "created_at", + |p: &mut Project| -> Result> { Ok(p.base_data.created_at) }, + ); + engine.register_get( + "modified_at", + |p: &mut Project| -> Result> { Ok(p.base_data.modified_at) }, + ); + engine.register_get( + "comments", + |p: &mut Project| -> Result> { + Ok(p.base_data + .comments + .iter() + .map(|&id| rhai::Dynamic::from(id as i64)) + .collect()) + }, + ); + engine.register_get( + "status", + |p: &mut Project| -> Result> { Ok(p.status.clone()) }, + ); + engine.register_get( + "priority", + |p: &mut Project| -> Result> { Ok(p.priority.clone()) }, + ); + engine.register_get( + "item_type", + |p: &mut Project| -> Result> { Ok(p.item_type.clone()) }, + ); // Builder methods for Project // let db_clone = db.clone(); // This was unused - engine.register_fn("name", |p: Project, name: String| -> Result> { Ok(p.name(name)) }); - engine.register_fn("description", |p: Project, description: String| -> Result> { Ok(p.description(description)) }); - engine.register_fn("owner_id", |p: Project, owner_id_i64: i64| -> Result> { Ok(p.owner_id(id_from_i64(owner_id_i64)?)) }); - engine.register_fn("add_member_id", |p: Project, member_id_i64: i64| -> Result> { Ok(p.add_member_id(id_from_i64(member_id_i64)?)) }); - engine.register_fn("member_ids", |p: Project, member_ids_i64: rhai::Array| -> Result> { - let ids = member_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.member_ids(ids)) - }); - engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) }); - engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result> { - let ids = board_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.board_ids(ids)) - }); - engine.register_fn("add_sprint_id", |p: Project, sprint_id_i64: i64| -> Result> { Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) }); - engine.register_fn("sprint_ids", |p: Project, sprint_ids_i64: rhai::Array| -> Result> { - let ids = sprint_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.sprint_ids(ids)) - }); - engine.register_fn("add_epic_id", |p: Project, epic_id_i64: i64| -> Result> { Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) }); - engine.register_fn("epic_ids", |p: Project, epic_ids_i64: rhai::Array| -> Result> { - let ids = epic_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.epic_ids(ids)) - }); - engine.register_fn("add_tag", |p: Project, tag: String| -> Result> { Ok(p.add_tag(tag)) }); - engine.register_fn("tags", |p: Project, tags_dyn: rhai::Array| -> Result> { - let tags_vec = tags_dyn - .into_iter() - .map(|tag_dyn: Dynamic| { - tag_dyn.clone().into_string().map_err(|_err| { // _err is Rhai's internal error, we create a new one - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected string for tag".to_string(), - tag_dyn.type_name().to_string(), - Position::NONE, - )) + engine.register_fn( + "name", + |p: Project, name: String| -> Result> { Ok(p.name(name)) }, + ); + engine.register_fn( + "description", + |p: Project, description: String| -> Result> { + Ok(p.description(description)) + }, + ); + engine.register_fn( + "owner_id", + |p: Project, owner_id_i64: i64| -> Result> { + Ok(p.owner_id(id_from_i64(owner_id_i64)?)) + }, + ); + engine.register_fn( + "add_member_id", + |p: Project, member_id_i64: i64| -> Result> { + Ok(p.add_member_id(id_from_i64(member_id_i64)?)) + }, + ); + engine.register_fn( + "member_ids", + |p: Project, member_ids_i64: rhai::Array| -> Result> { + let ids = member_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) }) - }) - .collect::, Box>>()?; - Ok(p.tags(tags_vec)) - }); + .collect::, Box>>()?; + Ok(p.member_ids(ids)) + }, + ); + engine.register_fn( + "add_board_id", + |p: Project, board_id_i64: i64| -> Result> { + Ok(p.add_board_id(id_from_i64(board_id_i64)?)) + }, + ); + engine.register_fn( + "board_ids", + |p: Project, board_ids_i64: rhai::Array| -> Result> { + let ids = board_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.board_ids(ids)) + }, + ); + engine.register_fn( + "add_sprint_id", + |p: Project, sprint_id_i64: i64| -> Result> { + Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) + }, + ); + engine.register_fn( + "sprint_ids", + |p: Project, sprint_ids_i64: rhai::Array| -> Result> { + let ids = sprint_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.sprint_ids(ids)) + }, + ); + engine.register_fn( + "add_epic_id", + |p: Project, epic_id_i64: i64| -> Result> { + Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) + }, + ); + engine.register_fn( + "epic_ids", + |p: Project, epic_ids_i64: rhai::Array| -> Result> { + let ids = epic_ids_i64 + .into_iter() + .map(|id_dyn: Dynamic| { + let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected integer for ID".to_string(), + id_dyn.type_name().to_string(), + Position::NONE, + )) + })?; + id_from_i64(val_i64) + }) + .collect::, Box>>()?; + Ok(p.epic_ids(ids)) + }, + ); + engine.register_fn( + "add_tag", + |p: Project, tag: String| -> Result> { Ok(p.add_tag(tag)) }, + ); + engine.register_fn( + "tags", + |p: Project, tags_dyn: rhai::Array| -> Result> { + let tags_vec = tags_dyn + .into_iter() + .map(|tag_dyn: Dynamic| { + tag_dyn.clone().into_string().map_err(|_err| { + // _err is Rhai's internal error, we create a new one + Box::new(EvalAltResult::ErrorMismatchDataType( + "Expected string for tag".to_string(), + tag_dyn.type_name().to_string(), + Position::NONE, + )) + }) + }) + .collect::, Box>>()?; + Ok(p.tags(tags_vec)) + }, + ); - engine.register_fn("status", |p: Project, status: Status| -> Result> { Ok(p.status(status)) }); - engine.register_fn("priority", |p: Project, priority: Priority| -> Result> { Ok(p.priority(priority)) }); - engine.register_fn("item_type", |p: Project, item_type: ItemType| -> Result> { Ok(p.item_type(item_type)) }); + engine.register_fn( + "status", + |p: Project, status: Status| -> Result> { + Ok(p.status(status)) + }, + ); + engine.register_fn( + "priority", + |p: Project, priority: Priority| -> Result> { + Ok(p.priority(priority)) + }, + ); + engine.register_fn( + "item_type", + |p: Project, item_type: ItemType| -> Result> { + Ok(p.item_type(item_type)) + }, + ); // Base ModelData builders - engine.register_fn("add_base_comment", |p: Project, comment_id_i64: i64| -> Result> { Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) }); + engine.register_fn( + "add_base_comment", + |p: Project, comment_id_i64: i64| -> Result> { + Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) + }, + ); // --- Database Interaction Functions --- let db_clone_set = db.clone(); - engine.register_fn("set_project", move |project: Project| -> Result<(), Box> { - let collection = db_clone_set.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; + engine.register_fn( + "set_project", + move |project: Project| -> Result<(), Box> { + let collection = db_clone_set.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to access project collection".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + })?; - collection.set(&project).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to save project".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - }) - }); + collection.set(&project).map(|_| ()).map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to save project".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + }) + }, + ); let db_clone_get = db.clone(); - engine.register_fn("get_project_by_id", move |id_i64: i64| -> Result> { - let id = id_from_i64(id_i64)?; - let collection = db_clone_get.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; + engine.register_fn( + "get_project_by_id", + move |id_i64: i64| -> Result> { + let id = id_from_i64(id_i64)?; + let collection = db_clone_get.collection::().map_err(|e| { + Box::new(EvalAltResult::ErrorSystem( + "Failed to access project collection".to_string(), + format!("DB operation failed: {:?}", e).into(), + )) + })?; - match collection.get_by_id(id) { - Ok(Some(project)) => Ok(Dynamic::from(project)), - Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai - Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( - "Failed to retrieve project by ID".to_string(), - format!("DB operation failed: {:?}", e).into(), - ))), - } - }); + match collection.get_by_id(id) { + Ok(Some(project)) => Ok(Dynamic::from(project)), + Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai + Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( + "Failed to retrieve project by ID".to_string(), + format!("DB operation failed: {:?}", e).into(), + ))), + } + }, + ); // TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import. // Register Label type and its methods/getters diff --git a/heromodels/src/models/projects/sprint.rs b/heromodels/src/models/projects/sprint.rs index 883857f..d73d1d6 100644 --- a/heromodels/src/models/projects/sprint.rs +++ b/heromodels/src/models/projects/sprint.rs @@ -3,8 +3,8 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; use super::sprint_enums::SprintStatus; // Import our new enum @@ -16,14 +16,14 @@ pub struct Sprint { pub description: Option, pub status: SprintStatus, pub goal: Option, // Sprint goal - + pub project_id: Option, // Link to a general project/board pub start_date: Option>, pub end_date: Option>, // Changed from due_date for sprints - + // Explicitly list task IDs belonging to this sprint - pub task_ids: Vec, + pub task_ids: Vec, } impl Sprint { diff --git a/heromodels/src/models/projects/task.rs b/heromodels/src/models/projects/task.rs index 4d008dc..c36e3bd 100644 --- a/heromodels/src/models/projects/task.rs +++ b/heromodels/src/models/projects/task.rs @@ -3,10 +3,10 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; -use rhai::{CustomType, TypeBuilder}; // Assuming rhai might be used +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; // Assuming rhai might be used -use super::task_enums::{TaskStatus, TaskPriority}; // Import our new enums +use super::task_enums::{TaskPriority, TaskStatus}; // Import our new enums #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] #[model] // This will provide id, created_at, updated_at via base_data @@ -16,10 +16,10 @@ pub struct Task { pub description: Option, pub status: TaskStatus, pub priority: TaskPriority, - + pub assignee_id: Option, // User ID pub reporter_id: Option, // User ID - + pub parent_task_id: Option, // For subtasks pub epic_id: Option, pub sprint_id: Option, diff --git a/heromodels/src/models/userexample/mod.rs b/heromodels/src/models/userexample/mod.rs index 66a4c00..2f9514d 100644 --- a/heromodels/src/models/userexample/mod.rs +++ b/heromodels/src/models/userexample/mod.rs @@ -2,4 +2,4 @@ pub mod user; // Re-export User for convenience -pub use user::User; \ No newline at end of file +pub use user::User; diff --git a/ourdb/Cargo.toml b/ourdb/Cargo.toml index becf1f3..6ff8e8e 100644 --- a/ourdb/Cargo.toml +++ b/ourdb/Cargo.toml @@ -15,9 +15,9 @@ rand = "0.8.5" criterion = "0.5.1" tempfile = "3.8.0" -[[bench]] -name = "ourdb_benchmarks" -harness = false +# [[bench]] +# name = "ourdb_benchmarks" +# harness = false [[example]] name = "basic_usage" diff --git a/ourdb/examples/advanced_usage.rs b/ourdb/examples/advanced_usage.rs index da15a3a..831a767 100644 --- a/ourdb/examples/advanced_usage.rs +++ b/ourdb/examples/advanced_usage.rs @@ -6,18 +6,18 @@ fn main() -> Result<(), ourdb::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_advanced_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Demonstrate key-value mode (non-incremental) key_value_mode_example(&db_path)?; - + // Demonstrate incremental mode incremental_mode_example(&db_path)?; - + // Demonstrate performance benchmarking performance_benchmark(&db_path)?; - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -25,16 +25,16 @@ fn main() -> Result<(), ourdb::Error> { } else { println!("Database kept at: {}", db_path.display()); } - + Ok(()) } fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Key-Value Mode Example ==="); - + let db_path = base_path.join("key_value"); std::fs::create_dir_all(&db_path)?; - + // Create a new database with key-value mode (non-incremental) let config = OurDBConfig { path: db_path, @@ -43,52 +43,62 @@ fn key_value_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(2), // Small key size for demonstration reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // In key-value mode, we must provide IDs explicitly let custom_ids = [100, 200, 300, 400, 500]; - + // Store data with custom IDs for (i, &id) in custom_ids.iter().enumerate() { let data = format!("Record with custom ID {}", id); - db.set(OurDBSetArgs { id: Some(id), data: data.as_bytes() })?; - println!("Stored record {} with custom ID: {}", i+1, id); + db.set(OurDBSetArgs { + id: Some(id), + data: data.as_bytes(), + })?; + println!("Stored record {} with custom ID: {}", i + 1, id); } - + // Retrieve data by custom IDs for &id in &custom_ids { let retrieved = db.get(id)?; - println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved)); + println!( + "Retrieved ID {}: {}", + id, + String::from_utf8_lossy(&retrieved) + ); } - + // Update and track history let id_to_update = custom_ids[2]; // ID 300 for i in 1..=3 { let updated_data = format!("Updated record {} (version {})", id_to_update, i); - db.set(OurDBSetArgs { id: Some(id_to_update), data: updated_data.as_bytes() })?; + db.set(OurDBSetArgs { + id: Some(id_to_update), + data: updated_data.as_bytes(), + })?; println!("Updated ID {} (version {})", id_to_update, i); } - + // Get history for the updated record let history = db.get_history(id_to_update, 5)?; println!("History for ID {} (most recent first):", id_to_update); for (i, entry) in history.iter().enumerate() { println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); } - + db.close()?; println!("Key-value mode example completed"); - + Ok(()) } fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Incremental Mode Example ==="); - + let db_path = base_path.join("incremental"); std::fs::create_dir_all(&db_path)?; - + // Create a new database with incremental mode let config = OurDBConfig { path: db_path, @@ -97,42 +107,49 @@ fn incremental_mode_example(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(3), // 3-byte keys reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // In incremental mode, IDs are auto-generated let mut assigned_ids = Vec::new(); - + // Store multiple records and collect assigned IDs for i in 1..=5 { let data = format!("Auto-increment record {}", i); - let id = db.set(OurDBSetArgs { id: None, data: data.as_bytes() })?; + let id = db.set(OurDBSetArgs { + id: None, + data: data.as_bytes(), + })?; assigned_ids.push(id); println!("Stored record {} with auto-assigned ID: {}", i, id); } - + // Check next ID let next_id = db.get_next_id()?; println!("Next ID to be assigned: {}", next_id); - + // Retrieve all records for &id in &assigned_ids { let retrieved = db.get(id)?; - println!("Retrieved ID {}: {}", id, String::from_utf8_lossy(&retrieved)); + println!( + "Retrieved ID {}: {}", + id, + String::from_utf8_lossy(&retrieved) + ); } - + db.close()?; println!("Incremental mode example completed"); - + Ok(()) } fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { println!("\n=== Performance Benchmark ==="); - + let db_path = base_path.join("benchmark"); std::fs::create_dir_all(&db_path)?; - + // Create a new database let config = OurDBConfig { path: db_path, @@ -141,62 +158,74 @@ fn performance_benchmark(base_path: &PathBuf) -> Result<(), ourdb::Error> { keysize: Some(4), // 4-byte keys reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // Number of operations for the benchmark let num_operations = 1000; let data_size = 100; // bytes per record - + // Prepare test data let test_data = vec![b'A'; data_size]; - + // Benchmark write operations println!("Benchmarking {} write operations...", num_operations); let start = Instant::now(); - + let mut ids = Vec::with_capacity(num_operations); for _ in 0..num_operations { - let id = db.set(OurDBSetArgs { id: None, data: &test_data })?; + let id = db.set(OurDBSetArgs { + id: None, + data: &test_data, + })?; ids.push(id); } - + let write_duration = start.elapsed(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Write performance: {:.2} ops/sec ({:.2} ms/op)", + writes_per_second, + write_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark read operations println!("Benchmarking {} read operations...", num_operations); let start = Instant::now(); - + for &id in &ids { let _ = db.get(id)?; } - + let read_duration = start.elapsed(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Read performance: {:.2} ops/sec ({:.2} ms/op)", + reads_per_second, + read_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark update operations println!("Benchmarking {} update operations...", num_operations); let start = Instant::now(); - + for &id in &ids { - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; } - + let update_duration = start.elapsed(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + println!( + "Update performance: {:.2} ops/sec ({:.2} ms/op)", + updates_per_second, + update_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + db.close()?; println!("Performance benchmark completed"); - + Ok(()) } diff --git a/ourdb/examples/basic_usage.rs b/ourdb/examples/basic_usage.rs index 5faf88c..6d160e7 100644 --- a/ourdb/examples/basic_usage.rs +++ b/ourdb/examples/basic_usage.rs @@ -4,9 +4,9 @@ fn main() -> Result<(), ourdb::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Create a new database with incremental mode enabled let config = OurDBConfig { path: db_path.clone(), @@ -15,51 +15,68 @@ fn main() -> Result<(), ourdb::Error> { keysize: None, // Use default (4 bytes) reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config)?; - + // Store some data with auto-generated IDs let data1 = b"First record"; - let id1 = db.set(OurDBSetArgs { id: None, data: data1 })?; + let id1 = db.set(OurDBSetArgs { + id: None, + data: data1, + })?; println!("Stored first record with ID: {}", id1); - + let data2 = b"Second record"; - let id2 = db.set(OurDBSetArgs { id: None, data: data2 })?; + let id2 = db.set(OurDBSetArgs { + id: None, + data: data2, + })?; println!("Stored second record with ID: {}", id2); - + // Retrieve and print the data let retrieved1 = db.get(id1)?; - println!("Retrieved ID {}: {}", id1, String::from_utf8_lossy(&retrieved1)); - + println!( + "Retrieved ID {}: {}", + id1, + String::from_utf8_lossy(&retrieved1) + ); + let retrieved2 = db.get(id2)?; - println!("Retrieved ID {}: {}", id2, String::from_utf8_lossy(&retrieved2)); - + println!( + "Retrieved ID {}: {}", + id2, + String::from_utf8_lossy(&retrieved2) + ); + // Update a record to demonstrate history tracking let updated_data = b"Updated first record"; - db.set(OurDBSetArgs { id: Some(id1), data: updated_data })?; + db.set(OurDBSetArgs { + id: Some(id1), + data: updated_data, + })?; println!("Updated record with ID: {}", id1); - + // Get history for the updated record let history = db.get_history(id1, 2)?; println!("History for ID {}:", id1); for (i, entry) in history.iter().enumerate() { println!(" Version {}: {}", i, String::from_utf8_lossy(entry)); } - + // Delete a record db.delete(id2)?; println!("Deleted record with ID: {}", id2); - + // Verify deletion match db.get(id2) { Ok(_) => println!("Record still exists (unexpected)"), Err(e) => println!("Verified deletion: {}", e), } - + // Close the database db.close()?; println!("Database closed successfully"); - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -67,6 +84,6 @@ fn main() -> Result<(), ourdb::Error> { } else { println!("Database kept at: {}", db_path.display()); } - + Ok(()) } diff --git a/ourdb/examples/benchmark.rs b/ourdb/examples/benchmark.rs index b670de8..1004dde 100644 --- a/ourdb/examples/benchmark.rs +++ b/ourdb/examples/benchmark.rs @@ -4,12 +4,12 @@ use std::time::Instant; fn main() -> Result<(), ourdb::Error> { // Parse command-line arguments let args: Vec = std::env::args().collect(); - + // Default values let mut incremental_mode = true; let mut keysize: u8 = 4; let mut num_operations = 10000; - + // Parse arguments for i in 1..args.len() { if args[i] == "--no-incremental" { @@ -20,13 +20,13 @@ fn main() -> Result<(), ourdb::Error> { num_operations = args[i + 1].parse().unwrap_or(10000); } } - + // Create a temporary directory for the database let db_path = std::env::temp_dir().join("ourdb_benchmark"); std::fs::create_dir_all(&db_path)?; - + println!("Database path: {}", db_path.display()); - + // Create a new database let config = OurDBConfig { path: db_path.clone(), @@ -35,73 +35,90 @@ fn main() -> Result<(), ourdb::Error> { keysize: Some(keysize), reset: Some(true), // Reset the database for benchmarking }; - + let mut db = OurDB::new(config)?; - + // Prepare test data (100 bytes per record) let test_data = vec![b'A'; 100]; - + // Benchmark write operations - println!("Benchmarking {} write operations (incremental: {}, keysize: {})...", - num_operations, incremental_mode, keysize); - + println!( + "Benchmarking {} write operations (incremental: {}, keysize: {})...", + num_operations, incremental_mode, keysize + ); + let start = Instant::now(); - + let mut ids = Vec::with_capacity(num_operations); for _ in 0..num_operations { let id = if incremental_mode { - db.set(OurDBSetArgs { id: None, data: &test_data })? + db.set(OurDBSetArgs { + id: None, + data: &test_data, + })? } else { // In non-incremental mode, we need to provide IDs let id = ids.len() as u32 + 1; - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; id }; ids.push(id); } - + let write_duration = start.elapsed(); let writes_per_second = num_operations as f64 / write_duration.as_secs_f64(); - - println!("Write performance: {:.2} ops/sec ({:.2} ms/op)", - writes_per_second, - write_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Write performance: {:.2} ops/sec ({:.2} ms/op)", + writes_per_second, + write_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark read operations println!("Benchmarking {} read operations...", num_operations); - + let start = Instant::now(); - + for &id in &ids { let _ = db.get(id)?; } - + let read_duration = start.elapsed(); let reads_per_second = num_operations as f64 / read_duration.as_secs_f64(); - - println!("Read performance: {:.2} ops/sec ({:.2} ms/op)", - reads_per_second, - read_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Read performance: {:.2} ops/sec ({:.2} ms/op)", + reads_per_second, + read_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Benchmark update operations println!("Benchmarking {} update operations...", num_operations); - + let start = Instant::now(); - + for &id in &ids { - db.set(OurDBSetArgs { id: Some(id), data: &test_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: &test_data, + })?; } - + let update_duration = start.elapsed(); let updates_per_second = num_operations as f64 / update_duration.as_secs_f64(); - - println!("Update performance: {:.2} ops/sec ({:.2} ms/op)", - updates_per_second, - update_duration.as_secs_f64() * 1000.0 / num_operations as f64); - + + println!( + "Update performance: {:.2} ops/sec ({:.2} ms/op)", + updates_per_second, + update_duration.as_secs_f64() * 1000.0 / num_operations as f64 + ); + // Clean up db.close()?; std::fs::remove_dir_all(&db_path)?; - + Ok(()) } diff --git a/ourdb/examples/main.rs b/ourdb/examples/main.rs index 6d1514e..546eff1 100644 --- a/ourdb/examples/main.rs +++ b/ourdb/examples/main.rs @@ -13,9 +13,9 @@ fn main() -> Result<(), Box> { .as_secs(); let db_path = temp_dir().join(format!("ourdb_example_{}", timestamp)); std::fs::create_dir_all(&db_path)?; - + println!("Creating database at: {}", db_path.display()); - + // Create a new OurDB instance let config = OurDBConfig { path: db_path.clone(), @@ -24,51 +24,60 @@ fn main() -> Result<(), Box> { keysize: None, reset: Some(false), }; - + let mut db = OurDB::new(config)?; println!("Database created successfully"); - + // Store some data let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data })?; + let id = db.set(OurDBSetArgs { + id: None, + data: test_data, + })?; println!("\nStored data with ID: {}", id); - + // Retrieve the data let retrieved = db.get(id)?; println!("Retrieved data: {}", String::from_utf8_lossy(&retrieved)); - + // Update the data let updated_data = b"Updated data in OurDB!"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data })?; + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + })?; println!("\nUpdated data with ID: {}", id); - + // Retrieve the updated data let retrieved = db.get(id)?; - println!("Retrieved updated data: {}", String::from_utf8_lossy(&retrieved)); - + println!( + "Retrieved updated data: {}", + String::from_utf8_lossy(&retrieved) + ); + // Get history let history = db.get_history(id, 2)?; println!("\nHistory for ID {}:", id); for (i, data) in history.iter().enumerate() { println!(" Version {}: {}", i + 1, String::from_utf8_lossy(data)); } - + // Delete the data db.delete(id)?; println!("\nDeleted data with ID: {}", id); - + // Try to retrieve the deleted data (should fail) match db.get(id) { Ok(_) => println!("Data still exists (unexpected)"), Err(e) => println!("Verified deletion: {}", e), } - + println!("\nExample completed successfully!"); - + // Clean up db.close()?; std::fs::remove_dir_all(&db_path)?; println!("Cleaned up database directory"); - + Ok(()) } diff --git a/ourdb/src/backend.rs b/ourdb/src/backend.rs index d7d94fe..0a8dbe2 100644 --- a/ourdb/src/backend.rs +++ b/ourdb/src/backend.rs @@ -1,7 +1,6 @@ use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; - use crc32fast::Hasher; use crate::error::Error; @@ -27,11 +26,8 @@ impl OurDB { } // Open the file fresh - let file = OpenOptions::new() - .read(true) - .write(true) - .open(&path)?; - + let file = OpenOptions::new().read(true).write(true).open(&path)?; + self.file = Some(file); self.file_nr = file_nr; @@ -42,10 +38,10 @@ impl OurDB { pub(crate) fn create_new_db_file(&mut self, file_nr: u16) -> Result<(), Error> { let new_file_path = self.path.join(format!("{}.db", file_nr)); let mut file = File::create(&new_file_path)?; - + // Write a single byte to make all positions start from 1 file.write_all(&[0u8])?; - + Ok(()) } @@ -54,17 +50,17 @@ impl OurDB { // For keysize 2, 3, or 4, we can only use file_nr 0 if self.lookup.keysize() <= 4 { let path = self.path.join("0.db"); - + if !path.exists() { self.create_new_db_file(0)?; } - + return Ok(0); } - + // For keysize 6, we can use multiple files let path = self.path.join(format!("{}.db", self.last_used_file_nr)); - + if !path.exists() { self.create_new_db_file(self.last_used_file_nr)?; return Ok(self.last_used_file_nr); @@ -80,30 +76,36 @@ impl OurDB { } /// Stores data at the specified ID with history tracking - pub(crate) fn set_(&mut self, id: u32, old_location: Location, data: &[u8]) -> Result<(), Error> { + pub(crate) fn set_( + &mut self, + id: u32, + old_location: Location, + data: &[u8], + ) -> Result<(), Error> { // Validate data size - maximum is u16::MAX (65535 bytes or ~64KB) if data.len() > u16::MAX as usize { - return Err(Error::InvalidOperation( - format!("Data size exceeds maximum allowed size of {} bytes", u16::MAX) - )); + return Err(Error::InvalidOperation(format!( + "Data size exceeds maximum allowed size of {} bytes", + u16::MAX + ))); } // Get file number to use let file_nr = self.get_file_nr()?; - + // Select the file self.db_file_select(file_nr)?; // Get current file position for lookup - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; file.seek(SeekFrom::End(0))?; let position = file.stream_position()? as u32; // Create new location - let new_location = Location { - file_nr, - position, - }; + let new_location = Location { file_nr, position }; // Calculate CRC of data let crc = calculate_crc(data); @@ -144,13 +146,19 @@ impl OurDB { /// Retrieves data at the specified location pub(crate) fn get_(&mut self, location: Location) -> Result, Error> { if location.position == 0 { - return Err(Error::NotFound(format!("Record not found, location: {:?}", location))); + return Err(Error::NotFound(format!( + "Record not found, location: {:?}", + location + ))); } // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Read header file.seek(SeekFrom::Start(location.position as u64))?; @@ -161,10 +169,10 @@ impl OurDB { let size = u16::from(header[0]) | (u16::from(header[1]) << 8); // Parse CRC (4 bytes) - let stored_crc = u32::from(header[2]) - | (u32::from(header[3]) << 8) - | (u32::from(header[4]) << 16) - | (u32::from(header[5]) << 24); + let stored_crc = u32::from(header[2]) + | (u32::from(header[3]) << 8) + | (u32::from(header[4]) << 16) + | (u32::from(header[5]) << 24); // Read data let mut data = vec![0u8; size as usize]; @@ -173,7 +181,9 @@ impl OurDB { // Verify CRC let calculated_crc = calculate_crc(&data); if calculated_crc != stored_crc { - return Err(Error::DataCorruption("CRC mismatch: data corruption detected".to_string())); + return Err(Error::DataCorruption( + "CRC mismatch: data corruption detected".to_string(), + )); } Ok(data) @@ -188,7 +198,10 @@ impl OurDB { // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Skip size and CRC (6 bytes) file.seek(SeekFrom::Start(location.position as u64 + 6))?; @@ -210,7 +223,10 @@ impl OurDB { // Select the file self.db_file_select(location.file_nr)?; - let file = self.file.as_mut().ok_or_else(|| Error::Other("No file open".to_string()))?; + let file = self + .file + .as_mut() + .ok_or_else(|| Error::Other("No file open".to_string()))?; // Read size first file.seek(SeekFrom::Start(location.position as u64))?; @@ -240,7 +256,7 @@ impl OurDB { for entry in fs::read_dir(&self.path)? { let entry = entry?; let path = entry.path(); - + if path.is_file() && path.extension().map_or(false, |ext| ext == "db") { if let Some(stem) = path.file_stem() { if let Ok(file_nr) = stem.to_string_lossy().parse::() { @@ -254,42 +270,42 @@ impl OurDB { for file_nr in file_numbers { let src_path = self.path.join(format!("{}.db", file_nr)); let temp_file_path = temp_path.join(format!("{}.db", file_nr)); - + // Create new file let mut temp_file = File::create(&temp_file_path)?; temp_file.write_all(&[0u8])?; // Initialize with a byte - + // Open source file let mut src_file = File::open(&src_path)?; - + // Read and process records let mut buffer = vec![0u8; 1024]; // Read in chunks let mut _position = 0; - + while let Ok(bytes_read) = src_file.read(&mut buffer) { if bytes_read == 0 { break; } - + // Process the chunk // This is a simplified version - in a real implementation, // you would need to handle records that span chunk boundaries - + _position += bytes_read; } - + // TODO: Implement proper record copying and position updating // This would involve: // 1. Reading each record from the source file // 2. If not deleted (all zeros), copy to temp file // 3. Update lookup table with new positions } - + // TODO: Replace original files with temp files - + // Clean up fs::remove_dir_all(&temp_path)?; - + Ok(()) } } @@ -304,7 +320,7 @@ fn calculate_crc(data: &[u8]) -> u32 { #[cfg(test)] mod tests { use std::path::PathBuf; - + use crate::{OurDB, OurDBConfig, OurDBSetArgs}; use std::env::temp_dir; use std::time::{SystemTime, UNIX_EPOCH}; @@ -320,26 +336,30 @@ mod tests { #[test] fn test_backend_operations() { let temp_dir = get_temp_dir(); - + let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: false, file_size: None, keysize: None, - reset: None, // Don't reset existing database + reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Test data for backend operations"; let id = 1; - - db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap(); - + + db.set(OurDBSetArgs { + id: Some(id), + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Clean up db.destroy().unwrap(); } diff --git a/ourdb/src/error.rs b/ourdb/src/error.rs index 9819066..5b240d2 100644 --- a/ourdb/src/error.rs +++ b/ourdb/src/error.rs @@ -6,23 +6,23 @@ pub enum Error { /// IO errors from file operations #[error("IO error: {0}")] Io(#[from] std::io::Error), - + /// Data corruption errors #[error("Data corruption: {0}")] DataCorruption(String), - + /// Invalid operation errors #[error("Invalid operation: {0}")] InvalidOperation(String), - + /// Lookup table errors #[error("Lookup error: {0}")] LookupError(String), - + /// Record not found errors #[error("Record not found: {0}")] NotFound(String), - + /// Other errors #[error("Error: {0}")] Other(String), diff --git a/ourdb/src/lib.rs b/ourdb/src/lib.rs index abdce97..aee3a4a 100644 --- a/ourdb/src/lib.rs +++ b/ourdb/src/lib.rs @@ -1,7 +1,7 @@ +mod backend; mod error; mod location; mod lookup; -mod backend; pub use error::Error; pub use location::Location; @@ -62,7 +62,7 @@ impl OurDB { if config.reset.unwrap_or(false) && config.path.exists() { std::fs::remove_dir_all(&config.path)?; } - + // Create directory if it doesn't exist std::fs::create_dir_all(&config.path)?; @@ -96,11 +96,11 @@ impl OurDB { } /// Sets a value in the database - /// + /// /// In incremental mode: /// - If ID is provided, it updates an existing record /// - If ID is not provided, it creates a new record with auto-generated ID - /// + /// /// In key-value mode: /// - ID must be provided pub fn set(&mut self, args: OurDBSetArgs) -> Result { @@ -110,7 +110,7 @@ impl OurDB { let location = self.lookup.get(id)?; if location.position == 0 { return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string() + "Cannot set ID for insertions when incremental mode is enabled".to_string(), )); } @@ -124,10 +124,12 @@ impl OurDB { } } else { // Using key-value mode - let id = args.id.ok_or_else(|| Error::InvalidOperation( - "ID must be provided when incremental is disabled".to_string() - ))?; - + let id = args.id.ok_or_else(|| { + Error::InvalidOperation( + "ID must be provided when incremental is disabled".to_string(), + ) + })?; + let location = self.lookup.get(id)?; self.set_(id, location, args.data)?; Ok(id) @@ -141,7 +143,7 @@ impl OurDB { } /// Retrieves a list of previous values for the specified key - /// + /// /// The depth parameter controls how many historical values to retrieve (maximum) pub fn get_history(&mut self, id: u32, depth: u8) -> Result>, Error> { let mut result = Vec::new(); @@ -179,7 +181,9 @@ impl OurDB { /// Returns the next ID which will be used when storing in incremental mode pub fn get_next_id(&mut self) -> Result { if !self.incremental_mode { - return Err(Error::InvalidOperation("Incremental mode is not enabled".to_string())); + return Err(Error::InvalidOperation( + "Incremental mode is not enabled".to_string(), + )); } self.lookup.get_next_id() } @@ -212,7 +216,8 @@ impl OurDB { } fn save(&mut self) -> Result<(), Error> { - self.lookup.export_sparse(&self.lookup_dump_path().to_string_lossy())?; + self.lookup + .export_sparse(&self.lookup_dump_path().to_string_lossy())?; Ok(()) } @@ -238,41 +243,50 @@ mod tests { #[test] fn test_basic_operations() { let temp_dir = get_temp_dir(); - + let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None, // Don't reset existing database + reset: None, // Don't reset existing database }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Test update let updated_data = b"Updated data"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, updated_data); - + // Test history let history = db.get_history(id, 2).unwrap(); assert_eq!(history.len(), 2); assert_eq!(history[0], updated_data); assert_eq!(history[1], test_data); - + // Test delete db.delete(id).unwrap(); assert!(db.get(id).is_err()); - + // Clean up db.destroy().unwrap(); } diff --git a/ourdb/src/location.rs b/ourdb/src/location.rs index 83af1e5..06a7a89 100644 --- a/ourdb/src/location.rs +++ b/ourdb/src/location.rs @@ -1,7 +1,7 @@ use crate::error::Error; /// Location represents a physical position in a database file -/// +/// /// It consists of a file number and a position within that file. /// This allows OurDB to span multiple files for large datasets. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -14,7 +14,7 @@ pub struct Location { impl Location { /// Creates a new Location from bytes based on keysize - /// + /// /// - keysize = 2: Only position (2 bytes), file_nr = 0 /// - keysize = 3: Only position (3 bytes), file_nr = 0 /// - keysize = 4: Only position (4 bytes), file_nr = 0 @@ -22,13 +22,18 @@ impl Location { pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result { // Validate keysize if ![2, 3, 4, 6].contains(&keysize) { - return Err(Error::InvalidOperation(format!("Invalid keysize: {}", keysize))); + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + keysize + ))); } // Create padded bytes let mut padded = vec![0u8; keysize as usize]; if bytes.len() > keysize as usize { - return Err(Error::InvalidOperation("Input bytes exceed keysize".to_string())); + return Err(Error::InvalidOperation( + "Input bytes exceed keysize".to_string(), + )); } let start_idx = keysize as usize - bytes.len(); @@ -49,34 +54,39 @@ impl Location { // Verify limits if location.position > 0xFFFF { return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=2 (max 65535)".to_string() + "Position exceeds max value for keysize=2 (max 65535)".to_string(), )); } - }, + } 3 => { // Only position, 3 bytes big endian - location.position = u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); + location.position = + u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); location.file_nr = 0; // Verify limits if location.position > 0xFFFFFF { return Err(Error::InvalidOperation( - "Position exceeds max value for keysize=3 (max 16777215)".to_string() + "Position exceeds max value for keysize=3 (max 16777215)".to_string(), )); } - }, + } 4 => { // Only position, 4 bytes big endian - location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16 - | u32::from(padded[2]) << 8 | u32::from(padded[3]); + location.position = u32::from(padded[0]) << 24 + | u32::from(padded[1]) << 16 + | u32::from(padded[2]) << 8 + | u32::from(padded[3]); location.file_nr = 0; - }, + } 6 => { // 2 bytes file_nr + 4 bytes position, all big endian location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]); - location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16 - | u32::from(padded[4]) << 8 | u32::from(padded[5]); - }, + location.position = u32::from(padded[2]) << 24 + | u32::from(padded[3]) << 16 + | u32::from(padded[4]) << 8 + | u32::from(padded[5]); + } _ => unreachable!(), } @@ -84,26 +94,26 @@ impl Location { } /// Converts the location to bytes (always 6 bytes) - /// + /// /// Format: [file_nr (2 bytes)][position (4 bytes)] pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(6); - + // Put file_nr first (2 bytes) bytes.push((self.file_nr >> 8) as u8); bytes.push(self.file_nr as u8); - + // Put position next (4 bytes) bytes.push((self.position >> 24) as u8); bytes.push((self.position >> 16) as u8); bytes.push((self.position >> 8) as u8); bytes.push(self.position as u8); - + bytes } /// Converts the location to a u64 value - /// + /// /// The file_nr is stored in the most significant bits pub fn to_u64(&self) -> u64 { (u64::from(self.file_nr) << 32) | u64::from(self.position) diff --git a/ourdb/src/lookup.rs b/ourdb/src/lookup.rs index a636fb0..34d4ed4 100644 --- a/ourdb/src/lookup.rs +++ b/ourdb/src/lookup.rs @@ -16,7 +16,7 @@ pub struct LookupConfig { /// - 2: For databases with < 65,536 records (single file) /// - 3: For databases with < 16,777,216 records (single file) /// - 4: For databases with < 4,294,967,296 records (single file) - /// - 6: For large databases requiring multiple files + /// - 6: For large databases requiring multiple files pub keysize: u8, /// Path for disk-based lookup pub lookuppath: String, @@ -46,7 +46,10 @@ impl LookupTable { pub fn new(config: LookupConfig) -> Result { // Verify keysize is valid if ![2, 3, 4, 6].contains(&config.keysize) { - return Err(Error::InvalidOperation(format!("Invalid keysize: {}", config.keysize))); + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + config.keysize + ))); } let incremental = if config.incremental_mode { @@ -90,7 +93,7 @@ impl LookupTable { if !self.lookuppath.is_empty() { // Disk-based lookup let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); - + // Check file size first let file_size = fs::metadata(&data_path)?.len(); let start_pos = id as u64 * entry_size as u64; @@ -98,7 +101,9 @@ impl LookupTable { if start_pos + entry_size as u64 > file_size { return Err(Error::LookupError(format!( "Invalid read for get in lut: {}: {} would exceed file size {}", - self.lookuppath, start_pos + entry_size as u64, file_size + self.lookuppath, + start_pos + entry_size as u64, + file_size ))); } @@ -108,14 +113,14 @@ impl LookupTable { let mut data = vec![0u8; entry_size]; let bytes_read = file.read(&mut data)?; - + if bytes_read < entry_size { return Err(Error::LookupError(format!( "Incomplete read: expected {} bytes but got {}", entry_size, bytes_read ))); } - + return Location::from_bytes(&data, self.keysize); } @@ -126,7 +131,7 @@ impl LookupTable { let start = (id * self.keysize as u32) as usize; let end = start + entry_size; - + Location::from_bytes(&self.data[start..end], self.keysize) } @@ -142,7 +147,7 @@ impl LookupTable { if id > incremental { return Err(Error::InvalidOperation( - "Cannot set ID for insertions when incremental mode is enabled".to_string() + "Cannot set ID for insertions when incremental mode is enabled".to_string(), )); } } @@ -151,53 +156,64 @@ impl LookupTable { let location_bytes = match self.keysize { 2 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=2".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=2".to_string(), + )); } if location.position > 0xFFFF { return Err(Error::InvalidOperation( - "position exceeds max value for keysize=2 (max 65535)".to_string() + "position exceeds max value for keysize=2 (max 65535)".to_string(), )); } vec![(location.position >> 8) as u8, location.position as u8] - }, + } 3 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=3".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=3".to_string(), + )); } if location.position > 0xFFFFFF { return Err(Error::InvalidOperation( - "position exceeds max value for keysize=3 (max 16777215)".to_string() + "position exceeds max value for keysize=3 (max 16777215)".to_string(), )); } vec![ (location.position >> 16) as u8, (location.position >> 8) as u8, - location.position as u8 + location.position as u8, ] - }, + } 4 => { if location.file_nr != 0 { - return Err(Error::InvalidOperation("file_nr must be 0 for keysize=4".to_string())); + return Err(Error::InvalidOperation( + "file_nr must be 0 for keysize=4".to_string(), + )); } vec![ (location.position >> 24) as u8, (location.position >> 16) as u8, (location.position >> 8) as u8, - location.position as u8 + location.position as u8, ] - }, + } 6 => { // Full location with file_nr and position location.to_bytes() - }, - _ => return Err(Error::InvalidOperation(format!("Invalid keysize: {}", self.keysize))), + } + _ => { + return Err(Error::InvalidOperation(format!( + "Invalid keysize: {}", + self.keysize + ))) + } }; if !self.lookuppath.is_empty() { // Disk-based lookup let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let mut file = OpenOptions::new().write(true).open(data_path)?; - + let start_pos = id as u64 * entry_size as u64; file.seek(SeekFrom::Start(start_pos))?; file.write_all(&location_bytes)?; @@ -207,7 +223,7 @@ impl LookupTable { if start + entry_size > self.data.len() { return Err(Error::LookupError("Index out of bounds".to_string())); } - + for (i, &byte) in location_bytes.iter().enumerate() { self.data[start + i] = byte; } @@ -224,9 +240,9 @@ impl LookupTable { /// Gets the next available ID in incremental mode pub fn get_next_id(&self) -> Result { - let incremental = self.incremental.ok_or_else(|| + let incremental = self.incremental.ok_or_else(|| { Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - )?; + })?; let table_size = if !self.lookuppath.is_empty() { let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); @@ -244,9 +260,9 @@ impl LookupTable { /// Increments the index in incremental mode pub fn increment_index(&mut self) -> Result<(), Error> { - let mut incremental = self.incremental.ok_or_else(|| + let mut incremental = self.incremental.ok_or_else(|| { Error::InvalidOperation("Lookup table not in incremental mode".to_string()) - )?; + })?; incremental += 1; self.incremental = Some(incremental); @@ -299,10 +315,10 @@ impl LookupTable { for id in 0..max_entries { file.seek(SeekFrom::Start(id * entry_size as u64))?; - + let mut buffer = vec![0u8; entry_size]; let bytes_read = file.read(&mut buffer)?; - + if bytes_read < entry_size { break; } @@ -317,11 +333,11 @@ impl LookupTable { } else { // For memory-based lookup let max_entries = self.data.len() / entry_size; - + for id in 0..max_entries { let start = id * entry_size; let entry = &self.data[start..start + entry_size]; - + // Check if entry is non-zero if entry.iter().any(|&b| b != 0) { // Write ID (4 bytes) + entry @@ -344,7 +360,7 @@ impl LookupTable { if data.len() % record_size != 0 { return Err(Error::DataCorruption( - "Invalid sparse data format: size mismatch".to_string() + "Invalid sparse data format: size mismatch".to_string(), )); } @@ -359,10 +375,10 @@ impl LookupTable { // Extract entry let entry = &data[chunk_start + 4..chunk_start + record_size]; - + // Create location from entry let location = Location::from_bytes(entry, self.keysize)?; - + // Set the entry self.set(id, location)?; } @@ -380,13 +396,13 @@ impl LookupTable { let data_path = Path::new(&self.lookuppath).join(DATA_FILE_NAME); let mut file = File::open(&data_path)?; let file_size = fs::metadata(&data_path)?.len(); - + let mut buffer = vec![0u8; entry_size]; let mut pos = 0u32; while (pos as u64 * entry_size as u64) < file_size { file.seek(SeekFrom::Start(pos as u64 * entry_size as u64))?; - + let bytes_read = file.read(&mut buffer)?; if bytes_read == 0 || bytes_read < entry_size { break; @@ -396,7 +412,7 @@ impl LookupTable { if location.position != 0 || location.file_nr != 0 { last_id = pos; } - + pos += 1; } } else { @@ -422,7 +438,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result { if !config.lookuppath.is_empty() { let inc_path = Path::new(&config.lookuppath).join(INCREMENTAL_FILE_NAME); - + if !inc_path.exists() { // Create a separate file for storing the incremental value fs::write(&inc_path, "1")?; @@ -437,7 +453,7 @@ fn get_incremental_info(config: &LookupConfig) -> Result { 1 } }; - + Ok(incremental) } else { // For memory-based lookup, start with 1 @@ -447,9 +463,9 @@ fn get_incremental_info(config: &LookupConfig) -> Result { #[cfg(test)] mod tests { - use std::path::PathBuf; use super::*; use std::env::temp_dir; + use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; fn get_temp_dir() -> PathBuf { @@ -468,25 +484,25 @@ mod tests { lookuppath: String::new(), incremental_mode: true, }; - + let mut lookup = LookupTable::new(config).unwrap(); - + // Test set and get let location = Location { file_nr: 0, position: 12345, }; - + lookup.set(1, location).unwrap(); let retrieved = lookup.get(1).unwrap(); - + assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.position, location.position); - + // Test incremental mode let next_id = lookup.get_next_id().unwrap(); assert_eq!(next_id, 2); - + lookup.increment_index().unwrap(); let next_id = lookup.get_next_id().unwrap(); assert_eq!(next_id, 3); @@ -496,28 +512,28 @@ mod tests { fn test_disk_lookup() { let temp_dir = get_temp_dir(); fs::create_dir_all(&temp_dir).unwrap(); - + let config = LookupConfig { size: 1000, keysize: 4, lookuppath: temp_dir.to_string_lossy().to_string(), incremental_mode: true, }; - + let mut lookup = LookupTable::new(config).unwrap(); - + // Test set and get let location = Location { file_nr: 0, position: 12345, }; - + lookup.set(1, location).unwrap(); let retrieved = lookup.get(1).unwrap(); - + assert_eq!(retrieved.file_nr, location.file_nr); assert_eq!(retrieved.position, location.position); - + // Clean up fs::remove_dir_all(temp_dir).unwrap(); } diff --git a/ourdb/tests/integration_tests.rs b/ourdb/tests/integration_tests.rs index 02d2930..f4e09f8 100644 --- a/ourdb/tests/integration_tests.rs +++ b/ourdb/tests/integration_tests.rs @@ -1,9 +1,9 @@ use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use rand; use std::env::temp_dir; use std::fs; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; -use rand; // Helper function to create a unique temporary directory for tests fn get_temp_dir() -> PathBuf { @@ -13,56 +13,64 @@ fn get_temp_dir() -> PathBuf { .as_nanos(); let random_part = rand::random::(); let dir = temp_dir().join(format!("ourdb_test_{}_{}", timestamp, random_part)); - + // Ensure the directory exists and is empty if dir.exists() { std::fs::remove_dir_all(&dir).unwrap(); } std::fs::create_dir_all(&dir).unwrap(); - + dir } #[test] fn test_basic_operations() { let temp_dir = get_temp_dir(); - + // Create a new database with incremental mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - - + let mut db = OurDB::new(config).unwrap(); - + // Test set and get let test_data = b"Hello, OurDB!"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Test update let updated_data = b"Updated data"; - db.set(OurDBSetArgs { id: Some(id), data: updated_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: updated_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, updated_data); - + // Test history let history = db.get_history(id, 2).unwrap(); assert_eq!(history.len(), 2); assert_eq!(history[0], updated_data); assert_eq!(history[1], test_data); - + // Test delete db.delete(id).unwrap(); assert!(db.get(id).is_err()); - + // Clean up db.destroy().unwrap(); } @@ -70,30 +78,33 @@ fn test_basic_operations() { #[test] fn test_key_value_mode() { let temp_dir = get_temp_dir(); - - + // Create a new database with key-value mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: false, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Test set with explicit ID let test_data = b"Key-value data"; let id = 42; - db.set(OurDBSetArgs { id: Some(id), data: test_data }).unwrap(); - + db.set(OurDBSetArgs { + id: Some(id), + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Verify next_id fails in key-value mode assert!(db.get_next_id().is_err()); - + // Clean up db.destroy().unwrap(); } @@ -101,33 +112,42 @@ fn test_key_value_mode() { #[test] fn test_incremental_mode() { let temp_dir = get_temp_dir(); - + // Create a new database with incremental mode let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - - + let mut db = OurDB::new(config).unwrap(); - + // Test auto-increment IDs let data1 = b"First record"; - let id1 = db.set(OurDBSetArgs { id: None, data: data1 }).unwrap(); - + let id1 = db + .set(OurDBSetArgs { + id: None, + data: data1, + }) + .unwrap(); + let data2 = b"Second record"; - let id2 = db.set(OurDBSetArgs { id: None, data: data2 }).unwrap(); - + let id2 = db + .set(OurDBSetArgs { + id: None, + data: data2, + }) + .unwrap(); + // IDs should be sequential assert_eq!(id2, id1 + 1); - + // Verify get_next_id works let next_id = db.get_next_id().unwrap(); assert_eq!(next_id, id2 + 1); - + // Clean up db.destroy().unwrap(); } @@ -135,8 +155,7 @@ fn test_incremental_mode() { #[test] fn test_persistence() { let temp_dir = get_temp_dir(); - - + // Create data in a new database { let config = OurDBConfig { @@ -144,21 +163,26 @@ fn test_persistence() { incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + let test_data = b"Persistent data"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + // Explicitly close the database db.close().unwrap(); - + // ID should be 1 in a new database assert_eq!(id, 1); } - + // Reopen the database and verify data persists { let config = OurDBConfig { @@ -166,19 +190,19 @@ fn test_persistence() { incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Verify data is still there let retrieved = db.get(1).unwrap(); assert_eq!(retrieved, b"Persistent data"); - + // Verify incremental counter persisted let next_id = db.get_next_id().unwrap(); assert_eq!(next_id, 2); - + // Clean up db.destroy().unwrap(); } @@ -188,28 +212,33 @@ fn test_persistence() { fn test_different_keysizes() { for keysize in [2, 3, 4, 6].iter() { let temp_dir = get_temp_dir(); - + // Ensure the directory exists std::fs::create_dir_all(&temp_dir).unwrap(); - + // Create a new database with specified keysize let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: Some(*keysize), - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Test basic operations let test_data = b"Keysize test data"; - let id = db.set(OurDBSetArgs { id: None, data: test_data }).unwrap(); - + let id = db + .set(OurDBSetArgs { + id: None, + data: test_data, + }) + .unwrap(); + let retrieved = db.get(id).unwrap(); assert_eq!(retrieved, test_data); - + // Clean up db.destroy().unwrap(); } @@ -218,28 +247,33 @@ fn test_different_keysizes() { #[test] fn test_large_data() { let temp_dir = get_temp_dir(); - + // Create a new database let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Create a large data set (60KB - within the 64KB limit) let large_data = vec![b'X'; 60 * 1024]; - + // Store and retrieve large data - let id = db.set(OurDBSetArgs { id: None, data: &large_data }).unwrap(); + let id = db + .set(OurDBSetArgs { + id: None, + data: &large_data, + }) + .unwrap(); let retrieved = db.get(id).unwrap(); - + assert_eq!(retrieved.len(), large_data.len()); assert_eq!(retrieved, large_data); - + // Clean up db.destroy().unwrap(); } @@ -247,27 +281,33 @@ fn test_large_data() { #[test] fn test_exceed_size_limit() { let temp_dir = get_temp_dir(); - + // Create a new database let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: None, keysize: None, - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Create data larger than the 64KB limit (70KB) let oversized_data = vec![b'X'; 70 * 1024]; - + // Attempt to store data that exceeds the size limit - let result = db.set(OurDBSetArgs { id: None, data: &oversized_data }); - + let result = db.set(OurDBSetArgs { + id: None, + data: &oversized_data, + }); + // Verify that an error is returned - assert!(result.is_err(), "Expected an error when storing data larger than 64KB"); - + assert!( + result.is_err(), + "Expected an error when storing data larger than 64KB" + ); + // Clean up db.destroy().unwrap(); } @@ -275,46 +315,55 @@ fn test_exceed_size_limit() { #[test] fn test_multiple_files() { let temp_dir = get_temp_dir(); - - + // Create a new database with small file size to force multiple files let config = OurDBConfig { path: temp_dir.clone(), incremental_mode: true, file_size: Some(1024), // Very small file size (1KB) keysize: Some(6), // 6-byte keysize for multiple files - reset: None + reset: None, }; - + let mut db = OurDB::new(config).unwrap(); - + // Store enough data to span multiple files let data_size = 500; // bytes per record let test_data = vec![b'A'; data_size]; - + let mut ids = Vec::new(); for _ in 0..10 { - let id = db.set(OurDBSetArgs { id: None, data: &test_data }).unwrap(); + let id = db + .set(OurDBSetArgs { + id: None, + data: &test_data, + }) + .unwrap(); ids.push(id); } - + // Verify all data can be retrieved for &id in &ids { let retrieved = db.get(id).unwrap(); assert_eq!(retrieved.len(), data_size); } - + // Verify multiple files were created - let files = fs::read_dir(&temp_dir).unwrap() + let files = fs::read_dir(&temp_dir) + .unwrap() .filter_map(Result::ok) .filter(|entry| { let path = entry.path(); path.is_file() && path.extension().map_or(false, |ext| ext == "db") }) .count(); - - assert!(files > 1, "Expected multiple database files, found {}", files); - + + assert!( + files > 1, + "Expected multiple database files, found {}", + files + ); + // Clean up db.destroy().unwrap(); } diff --git a/rhai_client_macros/src/lib.rs b/rhai_client_macros/src/lib.rs index 02a9d97..8b2a17c 100644 --- a/rhai_client_macros/src/lib.rs +++ b/rhai_client_macros/src/lib.rs @@ -1,23 +1,23 @@ use proc_macro::TokenStream; -use quote::{quote, format_ident}; -use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quote}; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, parse_quote, FnArg, ItemFn, Pat, PatType, ReturnType}; /// Procedural macro that generates a Rhai client function for a Rust function. -/// +/// /// When applied to a Rust function, it generates a corresponding function with a '_rhai_client' suffix /// that calls the original function through the Rhai engine. -/// +/// /// # Example -/// +/// /// ```rust /// #[rhai] /// fn hello(name: String) -> String { /// format!("Hello, {}!", name) /// } /// ``` -/// +/// /// This will generate: -/// +/// /// ```rust /// fn hello_rhai_client(engine: &rhai::Engine, name: String) -> String { /// let script = format!("hello(\"{}\")", name.replace("\"", "\\\"")); @@ -27,7 +27,7 @@ use syn::{parse_macro_input, ItemFn, FnArg, Pat, PatType, ReturnType, parse_quot /// }) /// } /// ``` -/// +/// /// Note: The macro handles type conversions between Rust and Rhai types, /// particularly for integer types (Rhai uses i64 internally). #[proc_macro_attribute] @@ -36,15 +36,15 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); - + // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); - + // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); - + for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { @@ -61,48 +61,55 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { } } } - + // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; - + // Generate parameter formatting for the Rhai script - let param_format_strings = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - let type_str = quote! { #ty }.to_string(); - - // Handle different parameter types - if type_str.contains("String") { - quote! { - format!("\"{}\"" , #name.replace("\"", "\\\"")) + let param_format_strings = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + let type_str = quote! { #ty }.to_string(); + + // Handle different parameter types + if type_str.contains("String") { + quote! { + format!("\"{}\"" , #name.replace("\"", "\\\"")) + } + } else if type_str.contains("bool") { + quote! { + format!("{}", #name) + } + } else if type_str.contains("i32") || type_str.contains("u32") { + // Convert smaller integer types to i64 for Rhai + quote! { + format!("{}", #name as i64) + } + } else if type_str.contains("i64") + || type_str.contains("u64") + || type_str.contains("f32") + || type_str.contains("f64") + { + // Other numeric types + quote! { + format!("{}", #name) + } + } else { + // For complex types, just pass the variable name + // The Rhai engine will handle the conversion + quote! { + #name.to_string() + } } - } else if type_str.contains("bool") { - quote! { - format!("{}", #name) - } - } else if type_str.contains("i32") || type_str.contains("u32") { - // Convert smaller integer types to i64 for Rhai - quote! { - format!("{}", #name as i64) - } - } else if type_str.contains("i64") || type_str.contains("u64") || type_str.contains("f32") || type_str.contains("f64") { - // Other numeric types - quote! { - format!("{}", #name) - } - } else { - // For complex types, just pass the variable name - // The Rhai engine will handle the conversion - quote! { - #name.to_string() - } - } - }); - + }); + // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); - + // Generate the client function with appropriate type conversions let client_fn = if return_type_str.contains("i32") || return_type_str.contains("u32") { // For integer return types that need conversion @@ -113,7 +120,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { @@ -132,7 +139,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -151,7 +158,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -170,7 +177,7 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_strings),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -181,19 +188,19 @@ pub fn rhai(_attr: TokenStream, item: TokenStream) -> TokenStream { } } }; - + // Combine the original function and the generated client function let output = quote! { #input_fn - + #client_fn }; - + output.into() } /// A more advanced version of the rhai macro that handles different parameter types better. -/// +/// /// This version properly escapes strings and handles different parameter types more accurately. /// It's recommended to use this version for more complex functions. #[proc_macro_attribute] @@ -202,15 +209,15 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { let input_fn = parse_macro_input!(item as ItemFn); let fn_name = &input_fn.sig.ident; let fn_name_str = fn_name.to_string(); - + // Create the client function name (original + _rhai_client) let client_fn_name = format_ident!("{}_rhai_client", fn_name); - + // Extract function parameters let mut param_names = Vec::new(); let mut param_types = Vec::new(); let mut param_declarations = Vec::new(); - + for arg in &input_fn.sig.inputs { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { @@ -227,48 +234,53 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { } } } - + // Determine return type let return_type = match &input_fn.sig.output { ReturnType::Default => parse_quote!(()), ReturnType::Type(_, ty) => ty.clone(), }; - + // Generate parameter formatting for the Rhai script - let param_format_expressions = param_names.iter().zip(param_types.iter()).map(|(name, ty)| { - let type_str = quote! { #ty }.to_string(); - - // Handle different parameter types - if type_str.contains("String") { - quote! { - format!("\"{}\"", #name.replace("\"", "\\\"")) + let param_format_expressions = param_names + .iter() + .zip(param_types.iter()) + .map(|(name, ty)| { + let type_str = quote! { #ty }.to_string(); + + // Handle different parameter types + if type_str.contains("String") { + quote! { + format!("\"{}\"", #name.replace("\"", "\\\"")) + } + } else if type_str.contains("bool") { + quote! { + format!("{}", #name) + } + } else if type_str.contains("i32") || type_str.contains("u32") { + // Convert smaller integer types to i64 for Rhai + quote! { + format!("{}", #name as i64) + } + } else if type_str.contains("i") || type_str.contains("u") || type_str.contains("f") { + // Other numeric types + quote! { + format!("{}", #name) + } + } else { + // Default for other types + quote! { + format!("{:?}", #name) + } } - } else if type_str.contains("bool") { - quote! { - format!("{}", #name) - } - } else if type_str.contains("i32") || type_str.contains("u32") { - // Convert smaller integer types to i64 for Rhai - quote! { - format!("{}", #name as i64) - } - } else if type_str.contains("i") || type_str.contains("u") || type_str.contains("f") { - // Other numeric types - quote! { - format!("{}", #name) - } - } else { - // Default for other types - quote! { - format!("{:?}", #name) - } - } - }).collect::>(); - + }) + .collect::>(); + // Determine if the return type needs conversion let return_type_str = quote! { #return_type }.to_string(); - let needs_return_conversion = return_type_str.contains("i32") || return_type_str.contains("u32"); - + let needs_return_conversion = + return_type_str.contains("i32") || return_type_str.contains("u32"); + // Generate the client function with appropriate type conversions let client_fn = if needs_return_conversion { quote! { @@ -278,7 +290,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); - + match engine.eval::(&script) { Ok(result) => result as #return_type, Err(err) => { @@ -296,7 +308,7 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { #fn_name_str, &[#(#param_format_expressions),*].join(", ") ); - + match engine.eval::<#return_type>(&script) { Ok(result) => result, Err(err) => { @@ -307,19 +319,19 @@ pub fn rhai_advanced(_attr: TokenStream, item: TokenStream) -> TokenStream { } } }; - + // Combine the original function and the generated client function let output = quote! { #input_fn - + #client_fn }; - + output.into() } /// Macro that generates a module with Rhai client functions for all functions in scope. -/// +/// /// This macro should be used at the module level to generate Rhai client functions for all /// functions marked with the #[rhai] attribute. #[proc_macro] @@ -329,20 +341,20 @@ pub fn generate_rhai_module(_item: TokenStream) -> TokenStream { // client functions for all of them. // // For simplicity, we'll just return a placeholder implementation - + let output = quote! { /// Register all functions marked with #[rhai] in this module with the Rhai engine. - /// + /// /// This function handles type conversions between Rust and Rhai types automatically. /// For example, it converts between Rust's i32 and Rhai's i64 types. pub fn register_rhai_functions(engine: &mut rhai::Engine) { // This would be generated based on the functions in the module println!("Registering Rhai functions..."); - + // In a real implementation, this would iterate through all functions // marked with #[rhai] and register them with the engine. } }; - + output.into() } diff --git a/tst/examples/basic_usage.rs b/tst/examples/basic_usage.rs index a9bea8a..3bdf6a7 100644 --- a/tst/examples/basic_usage.rs +++ b/tst/examples/basic_usage.rs @@ -1,16 +1,16 @@ -use tst::TST; use std::time::Instant; +use tst::TST; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Store some data println!("Inserting data..."); tree.set("hello", b"world".to_vec())?; @@ -19,50 +19,50 @@ fn main() -> Result<(), tst::Error> { tree.set("apple", b"fruit".to_vec())?; tree.set("application", b"software".to_vec())?; tree.set("banana", b"yellow".to_vec())?; - + // Retrieve and print the data let value = tree.get("hello")?; println!("hello: {}", String::from_utf8_lossy(&value)); - + // List keys with prefix println!("\nListing keys with prefix 'hel':"); let start = Instant::now(); let keys = tree.list("hel")?; let duration = start.elapsed(); - + for key in &keys { println!(" {}", key); } println!("Found {} keys in {:?}", keys.len(), duration); - + // Get all values with prefix println!("\nGetting all values with prefix 'app':"); let start = Instant::now(); let values = tree.getall("app")?; let duration = start.elapsed(); - + for (i, value) in values.iter().enumerate() { println!(" Value {}: {}", i + 1, String::from_utf8_lossy(value)); } println!("Found {} values in {:?}", values.len(), duration); - + // Delete a key println!("\nDeleting 'help'..."); tree.delete("help")?; - + // Verify deletion println!("Listing keys with prefix 'hel' after deletion:"); let keys_after = tree.list("hel")?; for key in &keys_after { println!(" {}", key); } - + // Try to get a deleted key match tree.get("help") { Ok(_) => println!("Unexpectedly found 'help' after deletion!"), Err(e) => println!("As expected, 'help' was not found: {}", e), } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -70,6 +70,6 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/examples/performance.rs b/tst/examples/performance.rs index 87523de..632b592 100644 --- a/tst/examples/performance.rs +++ b/tst/examples/performance.rs @@ -1,20 +1,20 @@ -use tst::TST; -use std::time::{Duration, Instant}; use std::io::{self, Write}; +use std::time::{Duration, Instant}; +use tst::TST; // Function to generate a test value of specified size fn generate_test_value(index: usize, size: usize) -> Vec { let base_value = format!("val{:08}", index); let mut value = Vec::with_capacity(size); - + // Fill with repeating pattern to reach desired size while value.len() < size { value.extend_from_slice(base_value.as_bytes()); } - + // Truncate to exact size value.truncate(size); - + value } @@ -28,39 +28,39 @@ const PERFORMANCE_SAMPLE_SIZE: usize = 100; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_performance_test"); - + // Completely remove and recreate the directory to ensure a clean start if db_path.exists() { std::fs::remove_dir_all(&db_path)?; } std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); println!("Will insert {} records and show progress...", TOTAL_RECORDS); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Track overall time let start_time = Instant::now(); - + // Track performance metrics let mut insertion_times = Vec::with_capacity(TOTAL_RECORDS / PROGRESS_INTERVAL); let mut last_batch_time = Instant::now(); let mut last_batch_records = 0; - + // Insert records and track progress for i in 0..TOTAL_RECORDS { let key = format!("key:{:08}", i); // Generate a 100-byte value let value = generate_test_value(i, 100); - + // Time the insertion of every Nth record for performance sampling if i % PERFORMANCE_SAMPLE_SIZE == 0 { let insert_start = Instant::now(); tree.set(&key, value)?; let insert_duration = insert_start.elapsed(); - + // Only print detailed timing for specific samples to avoid flooding output if i % (PERFORMANCE_SAMPLE_SIZE * 10) == 0 { println!("Record {}: Insertion took {:?}", i, insert_duration); @@ -68,76 +68,93 @@ fn main() -> Result<(), tst::Error> { } else { tree.set(&key, value)?; } - + // Show progress at intervals if (i + 1) % PROGRESS_INTERVAL == 0 || i == TOTAL_RECORDS - 1 { let records_in_batch = i + 1 - last_batch_records; let batch_duration = last_batch_time.elapsed(); let records_per_second = records_in_batch as f64 / batch_duration.as_secs_f64(); - + insertion_times.push((i + 1, batch_duration)); - - print!("\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", - i + 1, TOTAL_RECORDS, - (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, - records_per_second); + + print!( + "\rProgress: {}/{} records ({:.2}%) - {:.2} records/sec", + i + 1, + TOTAL_RECORDS, + (i + 1) as f64 / TOTAL_RECORDS as f64 * 100.0, + records_per_second + ); io::stdout().flush().unwrap(); - + last_batch_time = Instant::now(); last_batch_records = i + 1; } } - + let total_duration = start_time.elapsed(); println!("\n\nPerformance Summary:"); - println!("Total time to insert {} records: {:?}", TOTAL_RECORDS, total_duration); - println!("Average insertion rate: {:.2} records/second", - TOTAL_RECORDS as f64 / total_duration.as_secs_f64()); - + println!( + "Total time to insert {} records: {:?}", + TOTAL_RECORDS, total_duration + ); + println!( + "Average insertion rate: {:.2} records/second", + TOTAL_RECORDS as f64 / total_duration.as_secs_f64() + ); + // Show performance trend println!("\nPerformance Trend (records inserted vs. time per batch):"); for (i, (record_count, duration)) in insertion_times.iter().enumerate() { - if i % 10 == 0 || i == insertion_times.len() - 1 { // Only show every 10th point to avoid too much output - println!(" After {} records: {:?} for {} records ({:.2} records/sec)", - record_count, - duration, - PROGRESS_INTERVAL, - PROGRESS_INTERVAL as f64 / duration.as_secs_f64()); + if i % 10 == 0 || i == insertion_times.len() - 1 { + // Only show every 10th point to avoid too much output + println!( + " After {} records: {:?} for {} records ({:.2} records/sec)", + record_count, + duration, + PROGRESS_INTERVAL, + PROGRESS_INTERVAL as f64 / duration.as_secs_f64() + ); } } - + // Test access performance with distributed samples println!("\nTesting access performance with distributed samples..."); let mut total_get_time = Duration::new(0, 0); let num_samples = 1000; - + // Use a simple distribution pattern instead of random for i in 0..num_samples { // Distribute samples across the entire range let sample_id = (i * (TOTAL_RECORDS / num_samples)) % TOTAL_RECORDS; let key = format!("key:{:08}", sample_id); - + let get_start = Instant::now(); let _ = tree.get(&key)?; total_get_time += get_start.elapsed(); } - - println!("Average time to retrieve a record: {:?}", - total_get_time / num_samples as u32); - + + println!( + "Average time to retrieve a record: {:?}", + total_get_time / num_samples as u32 + ); + // Test prefix search performance println!("\nTesting prefix search performance..."); let prefixes = ["key:0", "key:1", "key:5", "key:9"]; - + for prefix in &prefixes { let list_start = Instant::now(); let keys = tree.list(prefix)?; let list_duration = list_start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", - keys.len(), prefix, list_duration); + + println!( + "Found {} keys with prefix '{}' in {:?}", + keys.len(), + prefix, + list_duration + ); } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -145,6 +162,6 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/examples/prefix_ops.rs b/tst/examples/prefix_ops.rs index 7d364ef..efbb870 100644 --- a/tst/examples/prefix_ops.rs +++ b/tst/examples/prefix_ops.rs @@ -1,82 +1,137 @@ -use tst::TST; use std::time::Instant; +use tst::TST; fn main() -> Result<(), tst::Error> { // Create a temporary directory for the database let db_path = std::env::temp_dir().join("tst_prefix_example"); std::fs::create_dir_all(&db_path)?; - + println!("Creating ternary search tree at: {}", db_path.display()); - + // Create a new TST let mut tree = TST::new(db_path.to_str().unwrap(), true)?; - + // Insert a variety of keys with different prefixes println!("Inserting data with various prefixes..."); - + // Names let names = [ - "Alice", "Alexander", "Amanda", "Andrew", "Amy", - "Bob", "Barbara", "Benjamin", "Brenda", "Brian", - "Charlie", "Catherine", "Christopher", "Cynthia", "Carl", - "David", "Diana", "Daniel", "Deborah", "Donald", - "Edward", "Elizabeth", "Eric", "Emily", "Ethan" + "Alice", + "Alexander", + "Amanda", + "Andrew", + "Amy", + "Bob", + "Barbara", + "Benjamin", + "Brenda", + "Brian", + "Charlie", + "Catherine", + "Christopher", + "Cynthia", + "Carl", + "David", + "Diana", + "Daniel", + "Deborah", + "Donald", + "Edward", + "Elizabeth", + "Eric", + "Emily", + "Ethan", ]; - + for (i, name) in names.iter().enumerate() { let value = format!("person-{}", i).into_bytes(); tree.set(name, value)?; } - + // Cities let cities = [ - "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", - "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose", - "Austin", "Jacksonville", "Fort Worth", "Columbus", "San Francisco", - "Charlotte", "Indianapolis", "Seattle", "Denver", "Washington" + "New York", + "Los Angeles", + "Chicago", + "Houston", + "Phoenix", + "Philadelphia", + "San Antonio", + "San Diego", + "Dallas", + "San Jose", + "Austin", + "Jacksonville", + "Fort Worth", + "Columbus", + "San Francisco", + "Charlotte", + "Indianapolis", + "Seattle", + "Denver", + "Washington", ]; - + for (i, city) in cities.iter().enumerate() { let value = format!("city-{}", i).into_bytes(); tree.set(city, value)?; } - + // Countries let countries = [ - "United States", "Canada", "Mexico", "Brazil", "Argentina", - "United Kingdom", "France", "Germany", "Italy", "Spain", - "China", "Japan", "India", "Australia", "Russia" + "United States", + "Canada", + "Mexico", + "Brazil", + "Argentina", + "United Kingdom", + "France", + "Germany", + "Italy", + "Spain", + "China", + "Japan", + "India", + "Australia", + "Russia", ]; - + for (i, country) in countries.iter().enumerate() { let value = format!("country-{}", i).into_bytes(); tree.set(country, value)?; } - - println!("Total items inserted: {}", names.len() + cities.len() + countries.len()); - + + println!( + "Total items inserted: {}", + names.len() + cities.len() + countries.len() + ); + // Test prefix operations test_prefix(&mut tree, "A")?; test_prefix(&mut tree, "B")?; test_prefix(&mut tree, "C")?; test_prefix(&mut tree, "San")?; test_prefix(&mut tree, "United")?; - + // Test non-existent prefix test_prefix(&mut tree, "Z")?; - + // Test empty prefix (should return all keys) println!("\nTesting empty prefix (should return all keys):"); let start = Instant::now(); let all_keys = tree.list("")?; let duration = start.elapsed(); - - println!("Found {} keys with empty prefix in {:?}", all_keys.len(), duration); + + println!( + "Found {} keys with empty prefix in {:?}", + all_keys.len(), + duration + ); println!("First 5 keys (alphabetically):"); for key in all_keys.iter().take(5) { println!(" {}", key); } - + // Clean up (optional) if std::env::var("KEEP_DB").is_err() { std::fs::remove_dir_all(&db_path)?; @@ -84,39 +139,46 @@ fn main() -> Result<(), tst::Error> { } else { println!("\nDatabase kept at: {}", db_path.display()); } - + Ok(()) } fn test_prefix(tree: &mut TST, prefix: &str) -> Result<(), tst::Error> { println!("\nTesting prefix '{}':", prefix); - + // Test list operation let start = Instant::now(); let keys = tree.list(prefix)?; let list_duration = start.elapsed(); - - println!("Found {} keys with prefix '{}' in {:?}", keys.len(), prefix, list_duration); - + + println!( + "Found {} keys with prefix '{}' in {:?}", + keys.len(), + prefix, + list_duration + ); + if !keys.is_empty() { println!("Keys:"); for key in &keys { println!(" {}", key); } - + // Test getall operation let start = Instant::now(); let values = tree.getall(prefix)?; let getall_duration = start.elapsed(); - + println!("Retrieved {} values in {:?}", values.len(), getall_duration); - println!("First value: {}", - if !values.is_empty() { - String::from_utf8_lossy(&values[0]) - } else { - "None".into() - }); + println!( + "First value: {}", + if !values.is_empty() { + String::from_utf8_lossy(&values[0]) + } else { + "None".into() + } + ); } - + Ok(()) -} \ No newline at end of file +} diff --git a/tst/src/error.rs b/tst/src/error.rs index cbb6e0f..e44ccaa 100644 --- a/tst/src/error.rs +++ b/tst/src/error.rs @@ -1,7 +1,7 @@ //! Error types for the TST module. -use thiserror::Error; use std::io; +use thiserror::Error; /// Error type for TST operations. #[derive(Debug, Error)] @@ -9,28 +9,28 @@ pub enum Error { /// Error from OurDB operations. #[error("OurDB error: {0}")] OurDB(#[from] ourdb::Error), - + /// Error when a key is not found. #[error("Key not found: {0}")] KeyNotFound(String), - + /// Error when a prefix is not found. #[error("Prefix not found: {0}")] PrefixNotFound(String), - + /// Error during serialization. #[error("Serialization error: {0}")] Serialization(String), - + /// Error during deserialization. #[error("Deserialization error: {0}")] Deserialization(String), - + /// Error for invalid operations. #[error("Invalid operation: {0}")] InvalidOperation(String), - + /// IO error. #[error("IO error: {0}")] IO(#[from] io::Error), -} \ No newline at end of file +} diff --git a/tst/src/lib.rs b/tst/src/lib.rs index 10a4e57..3943074 100644 --- a/tst/src/lib.rs +++ b/tst/src/lib.rs @@ -18,7 +18,7 @@ use ourdb::OurDB; pub struct TST { /// Database for persistent storage db: OurDB, - + /// Database ID of the root node root_id: Option, } @@ -119,4 +119,4 @@ impl TST { pub fn getall(&mut self, prefix: &str) -> Result>, Error> { operations::getall(self, prefix) } -} \ No newline at end of file +} diff --git a/tst/src/node.rs b/tst/src/node.rs index badb512..83294d0 100644 --- a/tst/src/node.rs +++ b/tst/src/node.rs @@ -5,19 +5,19 @@ pub struct TSTNode { /// The character stored at this node. pub character: char, - + /// Value stored at this node (empty if not end of key). pub value: Vec, - + /// Whether this node represents the end of a key. pub is_end_of_key: bool, - + /// Reference to the left child node (for characters < current character). pub left_id: Option, - + /// Reference to the middle child node (for next character in key). pub middle_id: Option, - + /// Reference to the right child node (for characters > current character). pub right_id: Option, } @@ -34,7 +34,7 @@ impl TSTNode { right_id: None, } } - + /// Creates a new root node. pub fn new_root() -> Self { Self { @@ -46,4 +46,4 @@ impl TSTNode { right_id: None, } } -} \ No newline at end of file +} diff --git a/tst/src/operations.rs b/tst/src/operations.rs index 49f98fc..a82b48d 100644 --- a/tst/src/operations.rs +++ b/tst/src/operations.rs @@ -9,19 +9,19 @@ use std::path::PathBuf; /// Creates a new TST with the specified database path. pub fn new_tst(path: &str, reset: bool) -> Result { let path_buf = PathBuf::from(path); - + // Create the configuration for OurDB with reset parameter let config = OurDBConfig { path: path_buf.clone(), incremental_mode: true, file_size: Some(1024 * 1024), // 1MB file size for better performance with large datasets - keysize: Some(4), // Use keysize=4 (default) - reset: Some(reset), // Use the reset parameter + keysize: Some(4), // Use keysize=4 (default) + reset: Some(reset), // Use the reset parameter }; - + // Create a new OurDB instance (it will handle reset internally) let mut db = OurDB::new(config)?; - + let root_id = if db.get_next_id()? == 1 || reset { // Create a new root node let root = TSTNode::new_root(); @@ -29,17 +29,14 @@ pub fn new_tst(path: &str, reset: bool) -> Result { id: None, data: &root.serialize(), })?; - + Some(root_id) } else { // Use existing root node Some(1) // Root node always has ID 1 }; - - Ok(TST { - db, - root_id, - }) + + Ok(TST { db, root_id }) } /// Sets a key-value pair in the tree. @@ -47,45 +44,51 @@ pub fn set(tree: &mut TST, key: &str, value: Vec) -> Result<(), Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); set_recursive(tree, root_id, &chars, 0, value)?; - + Ok(()) } /// Recursive helper function for setting a key-value pair. -fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value: Vec) -> Result { +fn set_recursive( + tree: &mut TST, + node_id: u32, + chars: &[char], + pos: usize, + value: Vec, +) -> Result { let mut node = tree.get_node(node_id)?; - + if pos >= chars.len() { // We've reached the end of the key node.is_end_of_key = true; node.value = value; return tree.save_node(Some(node_id), &node); } - + let current_char = chars[pos]; - + if node.character == '\0' { // Root node or empty node, set the character node.character = current_char; let node_id = tree.save_node(Some(node_id), &node)?; - + // Continue with the next character if pos + 1 < chars.len() { let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + let mut updated_node = tree.get_node(node_id)?; updated_node.middle_id = Some(new_id); tree.save_node(Some(node_id), &updated_node)?; - + return set_recursive(tree, new_id, chars, pos + 1, value); } else { // This is the last character @@ -95,7 +98,7 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value return tree.save_node(Some(node_id), &updated_node); } } - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -104,11 +107,11 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value // Create new left node let new_node = TSTNode::new(current_char, Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.left_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos, value); } } else if current_char > node.character { @@ -119,11 +122,11 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value // Create new right node let new_node = TSTNode::new(current_char, Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.right_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos, value); } } else { @@ -134,18 +137,18 @@ fn set_recursive(tree: &mut TST, node_id: u32, chars: &[char], pos: usize, value node.value = value; return tree.save_node(Some(node_id), &node); } - + if let Some(middle_id) = node.middle_id { return set_recursive(tree, middle_id, chars, pos + 1, value); } else { // Create new middle node let new_node = TSTNode::new(chars[pos + 1], Vec::new(), false); let new_id = tree.save_node(None, &new_node)?; - + // Update current node node.middle_id = Some(new_id); tree.save_node(Some(node_id), &node)?; - + return set_recursive(tree, new_id, chars, pos + 1, value); } } @@ -156,15 +159,15 @@ pub fn get(tree: &mut TST, key: &str) -> Result, Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); let node_id = find_node(tree, root_id, &chars, 0)?; - + let node = tree.get_node(node_id)?; if node.is_end_of_key { Ok(node.value.clone()) @@ -176,13 +179,13 @@ pub fn get(tree: &mut TST, key: &str) -> Result, Error> { /// Finds a node by key. fn find_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result { let node = tree.get_node(node_id)?; - + if pos >= chars.len() { return Ok(node_id); } - + let current_char = chars[pos]; - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -216,21 +219,21 @@ pub fn delete(tree: &mut TST, key: &str) -> Result<(), Error> { if key.is_empty() { return Err(Error::InvalidOperation("Empty key not allowed".to_string())); } - + let root_id = match tree.root_id { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let chars: Vec = key.chars().collect(); let node_id = find_node(tree, root_id, &chars, 0)?; - + let mut node = tree.get_node(node_id)?; - + if !node.is_end_of_key { return Err(Error::KeyNotFound(key.to_string())); } - + // If the node has a middle child, just mark it as not end of key if node.middle_id.is_some() || node.left_id.is_some() || node.right_id.is_some() { node.is_end_of_key = false; @@ -238,14 +241,14 @@ pub fn delete(tree: &mut TST, key: &str) -> Result<(), Error> { tree.save_node(Some(node_id), &node)?; return Ok(()); } - + // Otherwise, we need to remove the node and update its parent // This is more complex and would require tracking the path to the node // For simplicity, we'll just mark it as not end of key for now node.is_end_of_key = false; node.value = Vec::new(); tree.save_node(Some(node_id), &node)?; - + Ok(()) } @@ -255,46 +258,51 @@ pub fn list(tree: &mut TST, prefix: &str) -> Result, Error> { Some(id) => id, None => return Err(Error::InvalidOperation("Tree not initialized".to_string())), }; - + let mut result = Vec::new(); - + // Handle empty prefix case - will return all keys if prefix.is_empty() { collect_all_keys(tree, root_id, String::new(), &mut result)?; return Ok(result); } - + // Find the node corresponding to the prefix let chars: Vec = prefix.chars().collect(); let node_id = match find_prefix_node(tree, root_id, &chars, 0) { Ok(id) => id, Err(_) => return Ok(Vec::new()), // Prefix not found, return empty list }; - + // For empty prefix, we start with an empty string // For non-empty prefix, we start with the prefix minus the last character // (since the last character is in the node we found) let prefix_base = if chars.len() > 1 { - chars[0..chars.len()-1].iter().collect() + chars[0..chars.len() - 1].iter().collect() } else { String::new() }; - + // Collect all keys from the subtree collect_keys_with_prefix(tree, node_id, prefix_base, &mut result)?; - + Ok(result) } /// Finds the node corresponding to a prefix. -fn find_prefix_node(tree: &mut TST, node_id: u32, chars: &[char], pos: usize) -> Result { +fn find_prefix_node( + tree: &mut TST, + node_id: u32, + chars: &[char], + pos: usize, +) -> Result { if pos >= chars.len() { return Ok(node_id); } - + let node = tree.get_node(node_id)?; let current_char = chars[pos]; - + if current_char < node.character { // Go left if let Some(left_id) = node.left_id { @@ -331,32 +339,32 @@ fn collect_keys_with_prefix( result: &mut Vec, ) -> Result<(), Error> { let node = tree.get_node(node_id)?; - + let mut new_path = current_path.clone(); - + // For non-root nodes, add the character to the path if node.character != '\0' { new_path.push(node.character); } - + // If this node is an end of key, add it to the result if node.is_end_of_key { result.push(new_path.clone()); } - + // Recursively collect keys from all children if let Some(left_id) = node.left_id { collect_keys_with_prefix(tree, left_id, current_path.clone(), result)?; } - + if let Some(middle_id) = node.middle_id { collect_keys_with_prefix(tree, middle_id, new_path.clone(), result)?; } - + if let Some(right_id) = node.right_id { collect_keys_with_prefix(tree, right_id, current_path.clone(), result)?; } - + Ok(()) } @@ -368,32 +376,32 @@ fn collect_all_keys( result: &mut Vec, ) -> Result<(), Error> { let node = tree.get_node(node_id)?; - + let mut new_path = current_path.clone(); - + // Skip adding the character for the root node if node.character != '\0' { new_path.push(node.character); } - + // If this node is an end of key, add it to the result if node.is_end_of_key { result.push(new_path.clone()); } - + // Recursively collect keys from all children if let Some(left_id) = node.left_id { collect_all_keys(tree, left_id, current_path.clone(), result)?; } - + if let Some(middle_id) = node.middle_id { collect_all_keys(tree, middle_id, new_path.clone(), result)?; } - + if let Some(right_id) = node.right_id { collect_all_keys(tree, right_id, current_path.clone(), result)?; } - + Ok(()) } @@ -401,23 +409,23 @@ fn collect_all_keys( pub fn getall(tree: &mut TST, prefix: &str) -> Result>, Error> { // Get all matching keys let keys = list(tree, prefix)?; - + // Get values for each key let mut values = Vec::new(); let mut errors = Vec::new(); - + for key in keys { match get(tree, &key) { Ok(value) => values.push(value), - Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)) + Err(e) => errors.push(format!("Error getting value for key '{}': {:?}", key, e)), } } - + // If we couldn't get any values but had keys, return the first error if values.is_empty() && !errors.is_empty() { return Err(Error::InvalidOperation(errors.join("; "))); } - + Ok(values) } @@ -442,4 +450,4 @@ impl TST { Err(err) => Err(Error::OurDB(err)), } } -} \ No newline at end of file +} diff --git a/tst/src/serialize.rs b/tst/src/serialize.rs index 6c9c715..76e68b4 100644 --- a/tst/src/serialize.rs +++ b/tst/src/serialize.rs @@ -10,17 +10,17 @@ impl TSTNode { /// Serializes a node to bytes for storage. pub fn serialize(&self) -> Vec { let mut buffer = Vec::new(); - + // Version buffer.push(VERSION); - + // Character (as UTF-32) let char_bytes = (self.character as u32).to_le_bytes(); buffer.extend_from_slice(&char_bytes); - + // Is end of key buffer.push(if self.is_end_of_key { 1 } else { 0 }); - + // Value (only if is_end_of_key) if self.is_end_of_key { let value_len = (self.value.len() as u32).to_le_bytes(); @@ -30,88 +30,100 @@ impl TSTNode { // Zero length buffer.extend_from_slice(&[0, 0, 0, 0]); } - + // Child pointers let left_id = self.left_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&left_id); - + let middle_id = self.middle_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&middle_id); - + let right_id = self.right_id.unwrap_or(0).to_le_bytes(); buffer.extend_from_slice(&right_id); - + buffer } /// Deserializes bytes to a node. pub fn deserialize(data: &[u8]) -> Result { - if data.len() < 14 { // Minimum size: version + char + is_end + value_len + 3 child IDs + if data.len() < 14 { + // Minimum size: version + char + is_end + value_len + 3 child IDs return Err(Error::Deserialization("Data too short".to_string())); } - + let mut pos = 0; - + // Version let version = data[pos]; pos += 1; - + if version != VERSION { - return Err(Error::Deserialization(format!("Unsupported version: {}", version))); + return Err(Error::Deserialization(format!( + "Unsupported version: {}", + version + ))); } - + // Character - let char_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + let char_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let char_code = u32::from_le_bytes(char_bytes); let character = char::from_u32(char_code) .ok_or_else(|| Error::Deserialization("Invalid character".to_string()))?; pos += 4; - + // Is end of key let is_end_of_key = data[pos] != 0; pos += 1; - + // Value length - let value_len_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + let value_len_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let value_len = u32::from_le_bytes(value_len_bytes) as usize; pos += 4; - + // Value let value = if value_len > 0 { if pos + value_len > data.len() { - return Err(Error::Deserialization("Value length exceeds data".to_string())); + return Err(Error::Deserialization( + "Value length exceeds data".to_string(), + )); } - data[pos..pos+value_len].to_vec() + data[pos..pos + value_len].to_vec() } else { Vec::new() }; pos += value_len; - + // Child pointers if pos + 12 > data.len() { - return Err(Error::Deserialization("Data too short for child pointers".to_string())); + return Err(Error::Deserialization( + "Data too short for child pointers".to_string(), + )); } - - let left_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let left_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let left_id = u32::from_le_bytes(left_id_bytes); pos += 4; - - let middle_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let middle_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let middle_id = u32::from_le_bytes(middle_id_bytes); pos += 4; - - let right_id_bytes = [data[pos], data[pos+1], data[pos+2], data[pos+3]]; + + let right_id_bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]; let right_id = u32::from_le_bytes(right_id_bytes); - + Ok(TSTNode { character, value, is_end_of_key, left_id: if left_id == 0 { None } else { Some(left_id) }, - middle_id: if middle_id == 0 { None } else { Some(middle_id) }, + middle_id: if middle_id == 0 { + None + } else { + Some(middle_id) + }, right_id: if right_id == 0 { None } else { Some(right_id) }, }) } } -// Function removed as it was unused \ No newline at end of file +// Function removed as it was unused diff --git a/tst/tests/basic_test.rs b/tst/tests/basic_test.rs index 7d6c6cb..295836b 100644 --- a/tst/tests/basic_test.rs +++ b/tst/tests/basic_test.rs @@ -1,24 +1,24 @@ -use tst::TST; use std::env::temp_dir; use std::fs; use std::time::SystemTime; +use tst::TST; fn get_test_db_path() -> String { let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_nanos(); - + let path = temp_dir().join(format!("tst_test_{}", timestamp)); - + // If the path exists, remove it first if path.exists() { let _ = fs::remove_dir_all(&path); } - + // Create the directory fs::create_dir_all(&path).unwrap(); - + path.to_string_lossy().to_string() } @@ -30,44 +30,44 @@ fn cleanup_test_db(path: &str) { #[test] fn test_create_tst() { let path = get_test_db_path(); - + let result = TST::new(&path, true); match &result { Ok(_) => (), Err(e) => println!("Error creating TST: {:?}", e), } assert!(result.is_ok()); - + if let Ok(mut tst) = result { // Make sure we can perform a basic operation let set_result = tst.set("test_key", b"test_value".to_vec()); assert!(set_result.is_ok()); } - + cleanup_test_db(&path); } #[test] fn test_set_and_get() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Test setting and getting a key let key = "test_key"; let value = b"test_value".to_vec(); - + let set_result = tree.set(key, value.clone()); assert!(set_result.is_ok()); - + let get_result = tree.get(key); assert!(get_result.is_ok()); assert_eq!(get_result.unwrap(), value); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -75,45 +75,45 @@ fn test_set_and_get() { #[test] fn test_get_nonexistent_key() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Test getting a key that doesn't exist let get_result = tree.get("nonexistent_key"); assert!(get_result.is_err()); - + cleanup_test_db(&path); } #[test] fn test_delete() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Set a key let key = "delete_test"; let value = b"to_be_deleted".to_vec(); - + let set_result = tree.set(key, value); assert!(set_result.is_ok()); - + // Verify it exists let get_result = tree.get(key); assert!(get_result.is_ok()); - + // Delete it let delete_result = tree.delete(key); assert!(delete_result.is_ok()); - + // Verify it's gone let get_after_delete = tree.get(key); assert!(get_after_delete.is_err()); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -121,28 +121,28 @@ fn test_delete() { #[test] fn test_multiple_keys() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert multiple keys - use fewer keys to avoid filling the lookup table let keys = ["apple", "banana", "cherry"]; - + for (i, key) in keys.iter().enumerate() { let value = format!("value_{}", i).into_bytes(); let set_result = tree.set(key, value); - + // Print error if set fails if set_result.is_err() { println!("Error setting key '{}': {:?}", key, set_result); } - + assert!(set_result.is_ok()); } - + // Verify all keys exist for (i, key) in keys.iter().enumerate() { let expected_value = format!("value_{}", i).into_bytes(); @@ -150,7 +150,7 @@ fn test_multiple_keys() { assert!(get_result.is_ok()); assert_eq!(get_result.unwrap(), expected_value); } - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -158,56 +158,53 @@ fn test_multiple_keys() { #[test] fn test_list_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = [ - "apple", "application", "append", - "banana", "bandana" - ]; - + let keys = ["apple", "application", "append", "banana", "bandana"]; + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test prefix "app" let list_result = tree.list("app"); assert!(list_result.is_ok()); - + let app_keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'app':"); for key in &app_keys { println!(" {}", key); } - + // Check that each key is present assert!(app_keys.contains(&"apple".to_string())); assert!(app_keys.contains(&"application".to_string())); assert!(app_keys.contains(&"append".to_string())); - + // Test prefix "ban" let list_result = tree.list("ban"); assert!(list_result.is_ok()); - + let ban_keys = list_result.unwrap(); assert!(ban_keys.contains(&"banana".to_string())); assert!(ban_keys.contains(&"bandana".to_string())); - + // Test non-existent prefix let list_result = tree.list("z"); assert!(list_result.is_ok()); - + let z_keys = list_result.unwrap(); assert_eq!(z_keys.len(), 0); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -215,46 +212,44 @@ fn test_list_prefix() { #[test] fn test_getall_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert keys with common prefixes - use fewer keys to avoid filling the lookup table - let keys = [ - "apple", "application", "append" - ]; - + let keys = ["apple", "application", "append"]; + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test getall with prefix "app" let getall_result = tree.getall("app"); assert!(getall_result.is_ok()); - + let app_values = getall_result.unwrap(); - + // Convert values to strings for easier comparison let app_value_strings: Vec = app_values .iter() .map(|v| String::from_utf8_lossy(v).to_string()) .collect(); - + // Print the values for debugging println!("Values with prefix 'app':"); for value in &app_value_strings { println!(" {}", value); } - + // Check that each value is present assert!(app_value_strings.contains(&"apple".to_string())); assert!(app_value_strings.contains(&"application".to_string())); assert!(app_value_strings.contains(&"append".to_string())); - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -262,38 +257,38 @@ fn test_getall_prefix() { #[test] fn test_empty_prefix() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert some keys let keys = ["apple", "banana", "cherry"]; - + for key in &keys { let set_result = tree.set(key, key.as_bytes().to_vec()); assert!(set_result.is_ok()); } - + // Test list with empty prefix (should return all keys) let list_result = tree.list(""); assert!(list_result.is_ok()); - + let all_keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with empty prefix:"); for key in &all_keys { println!(" {}", key); } - + // Check that each key is present for key in &keys { assert!(all_keys.contains(&key.to_string())); } - + // Make sure to clean up properly cleanup_test_db(&path); -} \ No newline at end of file +} diff --git a/tst/tests/prefix_test.rs b/tst/tests/prefix_test.rs index 630ef19..b50c17d 100644 --- a/tst/tests/prefix_test.rs +++ b/tst/tests/prefix_test.rs @@ -1,24 +1,24 @@ -use tst::TST; use std::env::temp_dir; use std::fs; use std::time::SystemTime; +use tst::TST; fn get_test_db_path() -> String { let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_nanos(); - + let path = temp_dir().join(format!("tst_prefix_test_{}", timestamp)); - + // If the path exists, remove it first if path.exists() { let _ = fs::remove_dir_all(&path); } - + // Create the directory fs::create_dir_all(&path).unwrap(); - + path.to_string_lossy().to_string() } @@ -30,9 +30,9 @@ fn cleanup_test_db(path: &str) { #[test] fn test_prefix_with_common_prefixes() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with common prefixes let test_data = [ ("test", b"value1".to_vec()), @@ -41,34 +41,34 @@ fn test_prefix_with_common_prefixes() { ("tests", b"value4".to_vec()), ("tester", b"value5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "test" let keys = tree.list("test").unwrap(); assert_eq!(keys.len(), 5); - + for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + // Test prefix "teste" let keys = tree.list("teste").unwrap(); assert_eq!(keys.len(), 2); assert!(keys.contains(&"tested".to_string())); assert!(keys.contains(&"tester".to_string())); - + cleanup_test_db(&path); } #[test] fn test_prefix_with_different_prefixes() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with different prefixes let test_data = [ ("apple", b"fruit1".to_vec()), @@ -77,64 +77,64 @@ fn test_prefix_with_different_prefixes() { ("date", b"fruit4".to_vec()), ("elderberry", b"fruit5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test each prefix for (key, _) in &test_data { let prefix = &key[0..1]; // First character let keys = tree.list(prefix).unwrap(); assert!(keys.contains(&key.to_string())); } - + // Test non-existent prefix let keys = tree.list("z").unwrap(); assert_eq!(keys.len(), 0); - + cleanup_test_db(&path); } #[test] fn test_prefix_with_empty_string() { let path = get_test_db_path(); - + // Create a new TST with reset=true to ensure a clean state let result = TST::new(&path, true); assert!(result.is_ok()); - + let mut tree = result.unwrap(); - + // Insert some keys let test_data = [ ("apple", b"fruit1".to_vec()), ("banana", b"fruit2".to_vec()), ("cherry", b"fruit3".to_vec()), ]; - + for (key, value) in &test_data { let set_result = tree.set(key, value.clone()); assert!(set_result.is_ok()); } - + // Test empty prefix (should return all keys) let list_result = tree.list(""); assert!(list_result.is_ok()); - + let keys = list_result.unwrap(); - + // Print the keys for debugging println!("Keys with empty prefix:"); for key in &keys { println!(" {}", key); } - + // Check that each key is present for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + // Make sure to clean up properly cleanup_test_db(&path); } @@ -142,9 +142,9 @@ fn test_prefix_with_empty_string() { #[test] fn test_getall_with_prefix() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with common prefixes let test_data = [ ("test", b"value1".to_vec()), @@ -153,28 +153,28 @@ fn test_getall_with_prefix() { ("tests", b"value4".to_vec()), ("tester", b"value5".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test getall with prefix "test" let values = tree.getall("test").unwrap(); assert_eq!(values.len(), 5); - + for (_, value) in &test_data { assert!(values.contains(value)); } - + cleanup_test_db(&path); } #[test] fn test_prefix_with_unicode_characters() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert keys with Unicode characters let test_data = [ ("café", b"coffee".to_vec()), @@ -182,77 +182,86 @@ fn test_prefix_with_unicode_characters() { ("caffè", b"italian coffee".to_vec()), ("café au lait", b"coffee with milk".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "café" let keys = tree.list("café").unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'café':"); for key in &keys { println!(" {}", key); } - + // Check that the keys we expect are present assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café au lait".to_string())); - + // We don't assert on the exact count because Unicode handling can vary - + // Test prefix "caf" let keys = tree.list("caf").unwrap(); - + // Print the keys for debugging println!("Keys with prefix 'caf':"); for key in &keys { println!(" {}", key); } - + // Check that each key is present individually // Due to Unicode handling, we need to be careful with exact matching // The important thing is that we can find the keys we need - + // Check that we have at least the café and café au lait keys assert!(keys.contains(&"café".to_string())); assert!(keys.contains(&"café au lait".to_string())); - + // We don't assert on the exact count because Unicode handling can vary - + cleanup_test_db(&path); } #[test] fn test_prefix_with_long_keys() { let path = get_test_db_path(); - + let mut tree = TST::new(&path, true).unwrap(); - + // Insert long keys let test_data = [ - ("this_is_a_very_long_key_for_testing_purposes_1", b"value1".to_vec()), - ("this_is_a_very_long_key_for_testing_purposes_2", b"value2".to_vec()), - ("this_is_a_very_long_key_for_testing_purposes_3", b"value3".to_vec()), + ( + "this_is_a_very_long_key_for_testing_purposes_1", + b"value1".to_vec(), + ), + ( + "this_is_a_very_long_key_for_testing_purposes_2", + b"value2".to_vec(), + ), + ( + "this_is_a_very_long_key_for_testing_purposes_3", + b"value3".to_vec(), + ), ("this_is_another_long_key_for_testing", b"value4".to_vec()), ]; - + for (key, value) in &test_data { tree.set(key, value.clone()).unwrap(); } - + // Test prefix "this_is_a_very" let keys = tree.list("this_is_a_very").unwrap(); assert_eq!(keys.len(), 3); - + // Test prefix "this_is" let keys = tree.list("this_is").unwrap(); assert_eq!(keys.len(), 4); - + for (key, _) in &test_data { assert!(keys.contains(&key.to_string())); } - + cleanup_test_db(&path); -} \ No newline at end of file +} From e2640b942143e837103bd9365fee924497a6878e Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Sat, 21 Jun 2025 09:23:22 +0200 Subject: [PATCH 6/9] update to follow rhailib --- heromodels/Cargo.toml | 5 + heromodels/examples/gov_example.rhai | 222 +++++++++++++++++++ heromodels/examples/gov_rhai_example.rs | 57 +++++ heromodels/src/models/access/access.rs | 67 +++++- heromodels/src/models/access/rhai.rs | 14 +- heromodels/src/models/gov/committee.rs | 169 +++++++++++++++ heromodels/src/models/gov/company.rs | 191 ++++++++++++++++ heromodels/src/models/gov/meeting.rs | 227 ++++++++++++++++++++ heromodels/src/models/gov/mod.rs | 16 ++ heromodels/src/models/gov/resolution.rs | 143 ++++++++++++ heromodels/src/models/gov/shareholder.rs | 113 ++++++++++ heromodels/src/models/gov/user.rs | 74 +++++++ heromodels/src/models/gov/vote.rs | 191 ++++++++++++++++ heromodels/src/models/legal/rhai.rs | 24 +-- heromodels/src/models/library/collection.rs | 5 + heromodels/src/models/library/rhai.rs | 6 + 16 files changed, 1500 insertions(+), 24 deletions(-) create mode 100644 heromodels/examples/gov_example.rhai create mode 100644 heromodels/examples/gov_rhai_example.rs create mode 100644 heromodels/src/models/gov/committee.rs create mode 100644 heromodels/src/models/gov/company.rs create mode 100644 heromodels/src/models/gov/meeting.rs create mode 100644 heromodels/src/models/gov/mod.rs create mode 100644 heromodels/src/models/gov/resolution.rs create mode 100644 heromodels/src/models/gov/shareholder.rs create mode 100644 heromodels/src/models/gov/user.rs create mode 100644 heromodels/src/models/gov/vote.rs diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index e7ba96c..87b0ced 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -82,3 +82,8 @@ required-features = ["rhai"] name = "library_rhai" path = "examples/library_rhai/example.rs" required-features = ["rhai"] + +[[example]] +name = "rhai_auth_example" +path = "examples/rhai_auth_example.rs" +required-features = ["rhai", "authorization"] diff --git a/heromodels/examples/gov_example.rhai b/heromodels/examples/gov_example.rhai new file mode 100644 index 0000000..5187ea5 --- /dev/null +++ b/heromodels/examples/gov_example.rhai @@ -0,0 +1,222 @@ +// Governance Example - Demonstrates using the governance models with embedded types +// This example shows how to create, retrieve, and manipulate governance entities with embedded types + +// Get the database instance +let db = get_db(); +println("Hero Models - Governance Example"); +println("================================"); + +// Create a company with a business type using the fluent builder pattern +println("Creating a company..."); +// Create a business type directly +let business_type = #{ + type_name: "Corporation", + description: "A corporation is a legal entity that is separate and distinct from its owners" +}; + +let company = company__builder(1) + .name("Acme Corporation") + .registration_number("REG123456") + .incorporation_date(now()) + .fiscal_year_end("December 31") + .email("info@acme.com") + .phone("+1 555-123-4567") + .website("https://acme.com") + .address("123 Main St, Anytown, USA") + .business_type(business_type) + .industry("Technology") + .description("A leading technology company") + .status("Active") + .build(); + +// Save the company to the database +set_company(db, company); +println("Company created successfully!"); +println(""); + +// Create a committee with members using the fluent builder pattern +println("Creating a committee..."); +let committee = committee__builder(1) + .company_id(1) + .name("Board of Directors") + .description("The main governing body of the company") + .add_member( + committee_member() + .id(1) + .user_id(101) + .name("John Smith") + .role(CommitteeRole::Chair) + ) + .add_member( + committee_member() + .id(2) + .user_id(102) + .name("Jane Doe") + .role(CommitteeRole::Secretary) + ) + .add_member( + committee_member() + .id(3) + .user_id(103) + .name("Bob Johnson") + .role(CommitteeRole::Member) + ) + .build(); + +// Save the committee to the database +set_committee(db, committee); +println("Committee created successfully!"); +println(""); + +// Create a meeting with attendees using the fluent builder pattern +println("Creating a meeting..."); +let meeting = meeting__builder(1) + .company_id(1) + .title("Q2 Board Meeting") + .date(now() + days(7)) + .location("Conference Room A") + .description("Quarterly board meeting to review financial performance") + .add_attendee( + attendee() + .id(1) + .user_id(101) + .name("John Smith") + .role(AttendeeRole::Coordinator) + .status(AttendeeStatus::Confirmed) + ) + .add_attendee( + attendee() + .id(2) + .user_id(102) + .name("Jane Doe") + .role(AttendeeRole::Secretary) + .status(AttendeeStatus::Confirmed) + ) + .add_attendee( + attendee() + .id(3) + .user_id(103) + .name("Bob Johnson") + .role(AttendeeRole::Member) + .status(AttendeeStatus::Pending) + ) + .build(); + +// Save the meeting to the database +set_meeting(db, meeting); +println("Meeting created successfully!"); +println(""); + +// Create a resolution with approvals using the fluent builder pattern +println("Creating a resolution..."); +let resolution = resolution__builder(1) + .company_id(1) + .title("New Product Line Resolution") + .description("Resolution to approve the development of a new product line") + .resolution_type(ResolutionType::Ordinary) + .status(ResolutionStatus::Proposed) + .proposed_date(now()) + .effective_date(now() + days(30)) + .expiry_date(now() + days(365)) + .add_approval("John Smith") + .add_approval("Jane Doe") + .build(); + +// Save the resolution to the database +set_resolution(db, resolution); +println("Resolution created successfully!"); +println(""); + +// Create a vote with ballots using the fluent builder pattern +println("Creating a vote..."); +let vote = vote__builder(1) + .company_id(1) + .resolution_id(1) + .title("Vote on New Product Line") + .description("Vote to approve the development of a new product line") + .status(VoteStatus::Open) + .add_ballot( + ballot() + .id(1) + .user_id(101) + .user_name("John Smith") + .option(VoteOption::Yes) + .comments("Strongly support this initiative") + ) + .add_ballot( + ballot() + .id(2) + .user_id(102) + .user_name("Jane Doe") + .option(VoteOption::Yes) + .comments("Agree with the proposal") + ) + .add_ballot( + ballot() + .id(3) + .user_id(103) + .user_name("Bob Johnson") + .option(VoteOption::Abstain) + .comments("Need more information") + ) + .start_date(now()) + .end_date(now() + days(7)) + .build(); + +// Save the vote to the database +set_vote(db, vote); +println("Vote created successfully!"); +println(""); + +// Retrieve and display the company +println("Retrieving company..."); +let retrieved_company = get_company_by_id(db, 1); +println("Company: " + retrieved_company.name); +println("Business Type: " + retrieved_company.business_type.type_name); +println(""); + +// Retrieve and display the committee +println("Retrieving committee..."); +let retrieved_committee = get_committee_by_id(db, 1); +println("Committee: " + retrieved_committee.name); +println("Members: " + retrieved_committee.members.len()); +for member in retrieved_committee.members { + println("- " + member.name + " (" + member.role + ")"); +} +println(""); + +// Retrieve and display the meeting +println("Retrieving meeting..."); +let retrieved_meeting = get_meeting_by_id(db, 1); +println("Meeting: " + retrieved_meeting.title); +println("Date: " + retrieved_meeting.date); +println("Attendees: " + retrieved_meeting.attendees.len()); +for attendee in retrieved_meeting.attendees { + println("- " + attendee.name + " (" + attendee.role + ", " + attendee.status + ")"); +} +println(""); + +// Retrieve and display the resolution +println("Retrieving resolution..."); +let retrieved_resolution = get_resolution_by_id(db, 1); +println("Resolution: " + retrieved_resolution.title); +println("Status: " + retrieved_resolution.status); +println("Approvals: " + retrieved_resolution.approvals.len()); +for approval in retrieved_resolution.approvals { + println("- " + approval); +} +println(""); + +// Retrieve and display the vote +println("Retrieving vote..."); +let retrieved_vote = get_vote_by_id(db, 1); +println("Vote: " + retrieved_vote.title); +println("Status: " + retrieved_vote.status); +println("Ballots: " + retrieved_vote.ballots.len()); +for ballot in retrieved_vote.ballots { + println("- " + ballot.user_name + ": " + ballot.option); +} +println(""); + +println("Governance example completed successfully!"); +() diff --git a/heromodels/examples/gov_rhai_example.rs b/heromodels/examples/gov_rhai_example.rs new file mode 100644 index 0000000..0f09b78 --- /dev/null +++ b/heromodels/examples/gov_rhai_example.rs @@ -0,0 +1,57 @@ +use heromodels::db::hero::OurDB; +use heromodels::register_db_functions; +use rhai::Engine; +use std::sync::Arc; +use std::fs; +use std::path::Path; +use chrono::{DateTime, Utc, Duration}; + +fn main() -> Result<(), Box> { + // Create a temporary directory for the database + let temp_dir = std::env::temp_dir().join("heromodels_gov_example"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir)?; + } + fs::create_dir_all(&temp_dir)?; + + println!("Using temporary database at: {}", temp_dir.display()); + + // Create a new OurDB instance + let db = OurDB::new(&temp_dir, true)?; + let db = Arc::new(db); + + // Create a new Rhai engine + let mut engine = Engine::new(); + + // Register the auto-generated DB functions with the engine + register_db_functions(&mut engine); + + // Register print functions + engine.register_fn("println", |s: &str| println!("{}", s)); + engine.register_fn("print", |s: &str| print!("{}", s)); + + // Register date/time functions + engine.register_fn("now", || Utc::now()); + engine.register_fn("days", |n: i64| Duration::days(n)); + engine.register_fn("+", |dt: DateTime, duration: Duration| dt + duration); + + // Add the DB instance to the engine's scope + engine.register_fn("get_db", move || -> Arc { db.clone() }); + + // Load and execute the Rhai script + let script_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join("gov_example.rhai"); + + println!("Loading Rhai script from: {}", script_path.display()); + + match engine.eval_file::<()>(script_path) { + Ok(_) => println!("Script executed successfully"), + Err(e) => eprintln!("Error executing script: {}", e), + } + + // Clean up the temporary directory + fs::remove_dir_all(&temp_dir)?; + + Ok(()) +} diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs index bdc4907..b5b8050 100644 --- a/heromodels/src/models/access/access.rs +++ b/heromodels/src/models/access/access.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; + +use crate::db::{hero::OurDB, Collection, Db}; use heromodels_core::BaseModelData; use heromodels_derive::model; // Temporarily removed to fix compilation issues @@ -7,14 +10,19 @@ use serde::{Deserialize, Serialize}; /// Represents an event in a contact #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Access { /// Base model data pub base_data: BaseModelData, #[index] + pub object_type: String, + #[index] pub object_id: u32, - pub circle_id: u32, + #[index] + pub circle_pk: String, + #[index] pub contact_id: u32, + #[index] pub group_id: u32, pub expires_at: Option, } @@ -24,13 +32,19 @@ impl Access { Access { base_data: BaseModelData::new(), object_id: 0, - circle_id: 0, + object_type: String::new(), + circle_pk: String::new(), contact_id: 0, group_id: 0, expires_at: None, } } + pub fn object_type(mut self, object_type: String) -> Self { + self.object_type = object_type; + self + } + pub fn object_id(mut self, object_id: u32) -> Self { self.object_id = object_id; self @@ -46,8 +60,8 @@ impl Access { self } - pub fn circle_id(mut self, circle_id: u32) -> Self { - self.circle_id = circle_id; + pub fn circle_pk(mut self, circle_pk: String) -> Self { + self.circle_pk = circle_pk; self } @@ -56,3 +70,46 @@ impl Access { self } } + + +/// Checks if a caller has permission to access a specific resource. +/// Access is granted if the caller is a super admin or if an `Access` record exists +/// granting them `can_access = true` for the given resource type and ID. +/// +/// # Arguments +/// * `db`: An `Arc` for database interaction. +/// * `public_key`: The public key of the caller. +/// * `_resource_id_to_check`: The ID of the resource being accessed (now unused). +/// * `_resource_type_to_check`: The type of the resource (e.g., "Collection", "Image") (now unused). +/// +/// # Errors +/// Returns `Err(EvalAltResult::ErrorRuntime)` if there's a database error during the check. +pub fn can_access_resource( + db: Arc, + public_key: &str, + _resource_id_to_check: u32, + _resource_type_to_check: &str, +) -> bool { + // Query for Access records matching the public key. + // Note: This fetches all access records for the user. For performance with many records, + // consider a more specific query if your DB supports it, or caching. + let access_records = match db + .collection::() + .expect("Failed to get Access collection") + .get::(public_key) + { + Ok(records) => records, + Err(_e) => { + // Optionally log the error for debugging purposes. + // For example: log::warn!("Error fetching access records for public key {}: {:?}", public_key, e); + // If database query fails, assume access is not granted. + return false; + } + }; + + if !access_records.is_empty() { + return true; + } + + false // Default to deny if no grant is found +} diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs index 307b417..fff6411 100644 --- a/heromodels/src/models/access/rhai.rs +++ b/heromodels/src/models/access/rhai.rs @@ -43,17 +43,17 @@ mod rhai_access_module { Ok(access.clone()) } - #[rhai_fn(name = "circle_id", return_raw, global, pure)] - pub fn access_circle_id( + #[rhai_fn(name = "circle_pk", return_raw, global, pure)] + pub fn access_circle_pk( access: &mut RhaiAccess, - circle_id: u32, + circle_pk: String, ) -> Result> { // Create a default Access to replace the taken one let default_access = Access::new(); // Take ownership of the access, apply the builder method, then put it back let owned_access = std::mem::replace(access, default_access); - *access = owned_access.circle_id(circle_id); + *access = owned_access.circle_pk(circle_pk); Ok(access.clone()) } @@ -110,9 +110,9 @@ mod rhai_access_module { access.object_id as i64 } - #[rhai_fn(get = "circle_id", pure)] - pub fn get_access_circle_id(access: &mut RhaiAccess) -> i64 { - access.circle_id as i64 + #[rhai_fn(get = "circle_pk", pure)] + pub fn get_access_circle_pk(access: &mut RhaiAccess) -> String { + access.circle_pk.clone() } #[rhai_fn(get = "group_id", pure)] diff --git a/heromodels/src/models/gov/committee.rs b/heromodels/src/models/gov/committee.rs new file mode 100644 index 0000000..6189791 --- /dev/null +++ b/heromodels/src/models/gov/committee.rs @@ -0,0 +1,169 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// CommitteeRole represents the role of a committee member +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CommitteeRole { + Chair, + ViceChair, + Secretary, + Treasurer, + Member, + Observer, + Advisor, +} + +/// CommitteeMember represents a member of a committee +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CommitteeMember { + pub id: u32, + pub user_id: u32, + pub name: String, + pub role: CommitteeRole, + pub joined_date: DateTime, + pub notes: String, +} + +impl CommitteeMember { + /// Create a new committee member + pub fn new() -> Self { + Self { + id: 0, + user_id: 0, + name: String::new(), + role: CommitteeRole::Member, + joined_date: Utc::now(), + notes: String::new(), + } + } + + /// Set the ID + pub fn id(mut self, id: u32) -> Self { + self.id = id; + self + } + + /// Set the user ID + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the role + pub fn role(mut self, role: CommitteeRole) -> Self { + self.role = role; + self + } + + /// Set the joined date + pub fn joined_date(mut self, joined_date: DateTime) -> Self { + self.joined_date = joined_date; + self + } + + /// Set the notes + pub fn notes(mut self, notes: impl ToString) -> Self { + self.notes = notes.to_string(); + self + } + + /// Build the committee member + pub fn build(self) -> Self { + self + } +} + +/// Committee represents a committee in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Committee { + pub base_data: BaseModelData, + pub company_id: u32, + pub name: String, + pub description: String, + pub created_date: DateTime, + pub members: Vec, +} + +impl Committee { + /// Create a new committee + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + company_id: 0, + name: String::new(), + description: String::new(), + created_date: Utc::now(), + members: Vec::new(), + } + } + + /// Set the company ID + pub fn company_id(mut self, company_id: u32) -> Self { + self.company_id = company_id; + self + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the created date + pub fn created_date(mut self, created_date: DateTime) -> Self { + self.created_date = created_date; + self + } + + /// Add a member + pub fn add_member(mut self, member: CommitteeMember) -> Self { + self.members.push(member); + self + } + + /// Build the committee + pub fn build(self) -> Self { + self + } +} + +impl Model for Committee { + fn db_prefix() -> &'static str { + "committee" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "company_id", + value: self.company_id.to_string(), + }, + IndexKey { + name: "name", + value: self.name.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/company.rs b/heromodels/src/models/gov/company.rs new file mode 100644 index 0000000..c1701fe --- /dev/null +++ b/heromodels/src/models/gov/company.rs @@ -0,0 +1,191 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// CompanyStatus represents the status of a company +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CompanyStatus { + Active, + Inactive, + Dissolved, + Suspended, + Pending, +} + +/// BusinessType represents the type of a business +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BusinessType { + pub type_name: String, + pub description: String, +} + +impl BusinessType { + /// Create a new business type + pub fn new() -> Self { + Self { + type_name: String::new(), + description: String::new(), + } + } + + /// Set the type name + pub fn type_name(mut self, type_name: impl ToString) -> Self { + self.type_name = type_name.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Build the business type + pub fn build(self) -> Self { + self + } +} + +/// Company represents a company in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Company { + pub base_data: BaseModelData, + pub name: String, + pub registration_number: String, + pub incorporation_date: DateTime, + pub fiscal_year_end: String, + pub email: String, + pub phone: String, + pub website: String, + pub address: String, + pub business_type: BusinessType, + pub industry: String, + pub description: String, + pub status: CompanyStatus, +} + +impl Company { + /// Create a new company + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + name: String::new(), + registration_number: String::new(), + incorporation_date: Utc::now(), + fiscal_year_end: String::new(), + email: String::new(), + phone: String::new(), + website: String::new(), + address: String::new(), + business_type: BusinessType::new(), + industry: String::new(), + description: String::new(), + status: CompanyStatus::Pending, + } + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the registration number + pub fn registration_number(mut self, registration_number: impl ToString) -> Self { + self.registration_number = registration_number.to_string(); + self + } + + /// Set the incorporation date + pub fn incorporation_date(mut self, incorporation_date: DateTime) -> Self { + self.incorporation_date = incorporation_date; + self + } + + /// Set the fiscal year end + pub fn fiscal_year_end(mut self, fiscal_year_end: impl ToString) -> Self { + self.fiscal_year_end = fiscal_year_end.to_string(); + self + } + + /// Set the email + pub fn email(mut self, email: impl ToString) -> Self { + self.email = email.to_string(); + self + } + + /// Set the phone + pub fn phone(mut self, phone: impl ToString) -> Self { + self.phone = phone.to_string(); + self + } + + /// Set the website + pub fn website(mut self, website: impl ToString) -> Self { + self.website = website.to_string(); + self + } + + /// Set the address + pub fn address(mut self, address: impl ToString) -> Self { + self.address = address.to_string(); + self + } + + /// Set the business type + pub fn business_type(mut self, business_type: BusinessType) -> Self { + self.business_type = business_type; + self + } + + /// Set the industry + pub fn industry(mut self, industry: impl ToString) -> Self { + self.industry = industry.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the status + pub fn status(mut self, status: CompanyStatus) -> Self { + self.status = status; + self + } + + /// Build the company + pub fn build(self) -> Self { + self + } +} + +impl Model for Company { + fn db_prefix() -> &'static str { + "company" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "name", + value: self.name.clone(), + }, + IndexKey { + name: "registration_number", + value: self.registration_number.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/meeting.rs b/heromodels/src/models/gov/meeting.rs new file mode 100644 index 0000000..4caa11b --- /dev/null +++ b/heromodels/src/models/gov/meeting.rs @@ -0,0 +1,227 @@ +use heromodels_core::{Model, BaseModelData, IndexKey}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// MeetingStatus represents the status of a meeting +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MeetingStatus { + Scheduled, + InProgress, + Completed, + Cancelled, +} + +/// MeetingType represents the type of a meeting +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MeetingType { + BoardMeeting, + CommitteeMeeting, + GeneralAssembly, + AnnualGeneralMeeting, + ExtraordinaryGeneralMeeting, + Other, +} + +/// AttendanceStatus represents the status of an attendee +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum AttendanceStatus { + Invited, + Confirmed, + Declined, + Attended, + Absent, +} + +/// Attendee represents an attendee of a meeting +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Attendee { + pub user_id: u32, + pub name: String, + pub role: String, + pub status: AttendanceStatus, + pub notes: String, +} + +impl Attendee { + /// Create a new attendee + pub fn new() -> Self { + Self { + user_id: 0, + name: String::new(), + role: String::new(), + status: AttendanceStatus::Invited, + notes: String::new(), + } + } + + /// Set the user ID + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the role + pub fn role(mut self, role: impl ToString) -> Self { + self.role = role.to_string(); + self + } + + /// Set the status + pub fn status(mut self, status: AttendanceStatus) -> Self { + self.status = status; + self + } + + /// Set the notes + pub fn notes(mut self, notes: impl ToString) -> Self { + self.notes = notes.to_string(); + self + } + + /// Build the attendee + pub fn build(self) -> Self { + self + } +} + +/// Meeting represents a meeting in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Meeting { + pub base_data: BaseModelData, + pub company_id: u32, + pub title: String, + pub description: String, + pub meeting_type: MeetingType, + pub status: MeetingStatus, + pub start_time: DateTime, + pub end_time: DateTime, + pub location: String, + pub agenda: String, + pub minutes: String, + pub attendees: Vec, +} + +impl Meeting { + /// Create a new meeting + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + company_id: 0, + title: String::new(), + description: String::new(), + meeting_type: MeetingType::Other, + status: MeetingStatus::Scheduled, + start_time: Utc::now(), + end_time: Utc::now(), + location: String::new(), + agenda: String::new(), + minutes: String::new(), + attendees: Vec::new(), + } + } + + /// Set the company ID + pub fn company_id(mut self, company_id: u32) -> Self { + self.company_id = company_id; + self + } + + /// Set the title + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the meeting type + pub fn meeting_type(mut self, meeting_type: MeetingType) -> Self { + self.meeting_type = meeting_type; + self + } + + /// Set the status + pub fn status(mut self, status: MeetingStatus) -> Self { + self.status = status; + self + } + + /// Set the start time + pub fn start_time(mut self, start_time: DateTime) -> Self { + self.start_time = start_time; + self + } + + /// Set the end time + pub fn end_time(mut self, end_time: DateTime) -> Self { + self.end_time = end_time; + self + } + + /// Set the location + pub fn location(mut self, location: impl ToString) -> Self { + self.location = location.to_string(); + self + } + + /// Set the agenda + pub fn agenda(mut self, agenda: impl ToString) -> Self { + self.agenda = agenda.to_string(); + self + } + + /// Set the minutes + pub fn minutes(mut self, minutes: impl ToString) -> Self { + self.minutes = minutes.to_string(); + self + } + + /// Add an attendee + pub fn add_attendee(mut self, attendee: Attendee) -> Self { + self.attendees.push(attendee); + self + } + + /// Build the meeting + pub fn build(self) -> Self { + self + } +} + +impl Model for Meeting { + fn db_prefix() -> &'static str { + "meeting" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "company_id", + value: self.company_id.to_string(), + }, + IndexKey { + name: "title", + value: self.title.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/mod.rs b/heromodels/src/models/gov/mod.rs new file mode 100644 index 0000000..e3feb21 --- /dev/null +++ b/heromodels/src/models/gov/mod.rs @@ -0,0 +1,16 @@ +pub mod company; +pub mod shareholder; +pub mod meeting; +pub mod user; +pub mod vote; +pub mod resolution; +pub mod committee; + +// Re-export all model types for convenience +pub use company::{Company, CompanyStatus, BusinessType}; +pub use shareholder::{Shareholder, ShareholderType}; +pub use meeting::{Meeting, Attendee, MeetingStatus, MeetingType, AttendanceStatus}; +pub use user::User; +pub use vote::{Vote, VoteOption, Ballot, VoteStatus}; +pub use resolution::{Resolution, ResolutionStatus}; +pub use committee::{Committee, CommitteeMember, CommitteeRole}; diff --git a/heromodels/src/models/gov/resolution.rs b/heromodels/src/models/gov/resolution.rs new file mode 100644 index 0000000..10ed792 --- /dev/null +++ b/heromodels/src/models/gov/resolution.rs @@ -0,0 +1,143 @@ +use heromodels_core::{Model, BaseModelData, BaseModelDataBuilder, IndexKey}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// ResolutionStatus represents the status of a resolution +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ResolutionStatus { + Draft, + Proposed, + Approved, + Rejected, + Expired, +} + +/// ResolutionType represents the type of a resolution +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ResolutionType { + Ordinary, + Special, + Unanimous, + Written, + Other, +} + +/// Resolution represents a resolution in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Resolution { + pub base_data: BaseModelData, + pub company_id: u32, + pub title: String, + pub description: String, + pub resolution_type: ResolutionType, + pub status: ResolutionStatus, + pub proposed_date: DateTime, + pub effective_date: Option>, + pub expiry_date: Option>, + pub approvals: Vec, +} + +impl Resolution { + /// Create a new resolution + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + company_id: 0, + title: String::new(), + description: String::new(), + resolution_type: ResolutionType::Ordinary, + status: ResolutionStatus::Draft, + proposed_date: Utc::now(), + effective_date: None, + expiry_date: None, + approvals: Vec::new(), + } + } + + /// Set the company ID + pub fn company_id(mut self, company_id: u32) -> Self { + self.company_id = company_id; + self + } + + /// Set the title + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the resolution type + pub fn resolution_type(mut self, resolution_type: ResolutionType) -> Self { + self.resolution_type = resolution_type; + self + } + + /// Set the status + pub fn status(mut self, status: ResolutionStatus) -> Self { + self.status = status; + self + } + + /// Set the proposed date + pub fn proposed_date(mut self, proposed_date: DateTime) -> Self { + self.proposed_date = proposed_date; + self + } + + /// Set the effective date + pub fn effective_date(mut self, effective_date: Option>) -> Self { + self.effective_date = effective_date; + self + } + + /// Set the expiry date + pub fn expiry_date(mut self, expiry_date: Option>) -> Self { + self.expiry_date = expiry_date; + self + } + + /// Add an approval + pub fn add_approval(mut self, approval: impl ToString) -> Self { + self.approvals.push(approval.to_string()); + self + } + + /// Build the resolution + pub fn build(self) -> Self { + self + } +} + +impl Model for Resolution { + fn db_prefix() -> &'static str { + "resolution" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "company_id", + value: self.company_id.to_string(), + }, + IndexKey { + name: "title", + value: self.title.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/shareholder.rs b/heromodels/src/models/gov/shareholder.rs new file mode 100644 index 0000000..e20864a --- /dev/null +++ b/heromodels/src/models/gov/shareholder.rs @@ -0,0 +1,113 @@ +use heromodels_core::{Model, BaseModelData, BaseModelDataBuilder, IndexKey}; +use serde::{Deserialize, Serialize}; + +/// ShareholderType represents the type of a shareholder +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ShareholderType { + Individual, + Corporate, + Trust, + Partnership, + Government, + Other, +} + +/// Shareholder represents a shareholder in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Shareholder { + pub base_data: BaseModelData, + pub company_id: u32, + pub name: String, + pub shareholder_type: ShareholderType, + pub contact_info: String, + pub shares: u32, + pub percentage: f64, +} + +impl Shareholder { + /// Create a new shareholder + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + company_id: 0, + name: String::new(), + shareholder_type: ShareholderType::Individual, + contact_info: String::new(), + shares: 0, + percentage: 0.0, + } + } + + /// Set the company ID + pub fn company_id(mut self, company_id: u32) -> Self { + self.company_id = company_id; + self + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the shareholder type + pub fn shareholder_type(mut self, shareholder_type: ShareholderType) -> Self { + self.shareholder_type = shareholder_type; + self + } + + /// Set the contact info + pub fn contact_info(mut self, contact_info: impl ToString) -> Self { + self.contact_info = contact_info.to_string(); + self + } + + /// Set the shares + pub fn shares(mut self, shares: u32) -> Self { + self.shares = shares; + self + } + + /// Set the percentage + pub fn percentage(mut self, percentage: f64) -> Self { + self.percentage = percentage; + self + } + + /// Build the shareholder + pub fn build(self) -> Self { + self + } +} + +impl Model for Shareholder { + fn db_prefix() -> &'static str { + "shareholder" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "company_id", + value: self.company_id.to_string(), + }, + IndexKey { + name: "name", + value: self.name.clone(), + }, + IndexKey { + name: "contact_info", + value: self.contact_info.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/user.rs b/heromodels/src/models/gov/user.rs new file mode 100644 index 0000000..8d6be0e --- /dev/null +++ b/heromodels/src/models/gov/user.rs @@ -0,0 +1,74 @@ +use heromodels_core::{Model, BaseModelData, BaseModelDataBuilder, IndexKey}; +use serde::{Deserialize, Serialize}; + +/// User represents a user in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct User { + pub base_data: BaseModelData, + pub name: String, + pub email: String, + pub role: String, +} + +impl User { + /// Create a new user + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + name: String::new(), + email: String::new(), + role: String::new(), + } + } + + /// Set the name + pub fn name(mut self, name: impl ToString) -> Self { + self.name = name.to_string(); + self + } + + /// Set the email + pub fn email(mut self, email: impl ToString) -> Self { + self.email = email.to_string(); + self + } + + /// Set the role + pub fn role(mut self, role: impl ToString) -> Self { + self.role = role.to_string(); + self + } + + /// Build the user + pub fn build(self) -> Self { + self + } +} + +impl Model for User { + fn db_prefix() -> &'static str { + "gov_user" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "name", + value: self.name.clone(), + }, + IndexKey { + name: "email", + value: self.email.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/gov/vote.rs b/heromodels/src/models/gov/vote.rs new file mode 100644 index 0000000..3c8e808 --- /dev/null +++ b/heromodels/src/models/gov/vote.rs @@ -0,0 +1,191 @@ +use heromodels_core::{Model, BaseModelData, BaseModelDataBuilder, IndexKey}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// VoteStatus represents the status of a vote +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum VoteStatus { + Draft, + Open, + Closed, + Cancelled, +} + +/// VoteOption represents an option in a vote +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum VoteOption { + Yes, + No, + Abstain, + Custom(String), +} + +/// Ballot represents a ballot cast in a vote +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Ballot { + pub user_id: u32, + pub option: VoteOption, + pub weight: f64, + pub cast_at: DateTime, + pub notes: String, +} + +impl Ballot { + /// Create a new ballot + pub fn new() -> Self { + Self { + user_id: 0, + option: VoteOption::Abstain, + weight: 1.0, + cast_at: Utc::now(), + notes: String::new(), + } + } + + /// Set the user ID + pub fn user_id(mut self, user_id: u32) -> Self { + self.user_id = user_id; + self + } + + /// Set the option + pub fn option(mut self, option: VoteOption) -> Self { + self.option = option; + self + } + + /// Set the weight + pub fn weight(mut self, weight: f64) -> Self { + self.weight = weight; + self + } + + /// Set the cast time + pub fn cast_at(mut self, cast_at: DateTime) -> Self { + self.cast_at = cast_at; + self + } + + /// Set the notes + pub fn notes(mut self, notes: impl ToString) -> Self { + self.notes = notes.to_string(); + self + } + + /// Build the ballot + pub fn build(self) -> Self { + self + } +} + +/// Vote represents a vote in the governance system +#[derive(Debug, Clone, Serialize, Deserialize)] +#[model] +pub struct Vote { + pub base_data: BaseModelData, + pub company_id: u32, + pub resolution_id: u32, + pub title: String, + pub description: String, + pub status: VoteStatus, + pub start_date: DateTime, + pub end_date: DateTime, + pub ballots: Vec, +} + +impl Vote { + /// Create a new vote + pub fn new(id: u32) -> Self { + Self { + base_data: BaseModelData::new(id), + company_id: 0, + resolution_id: 0, + title: String::new(), + description: String::new(), + status: VoteStatus::Draft, + start_date: Utc::now(), + end_date: Utc::now(), + ballots: Vec::new(), + } + } + + /// Set the company ID + pub fn company_id(mut self, company_id: u32) -> Self { + self.company_id = company_id; + self + } + + /// Set the resolution ID + pub fn resolution_id(mut self, resolution_id: u32) -> Self { + self.resolution_id = resolution_id; + self + } + + /// Set the title + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Set the description + pub fn description(mut self, description: impl ToString) -> Self { + self.description = description.to_string(); + self + } + + /// Set the status + pub fn status(mut self, status: VoteStatus) -> Self { + self.status = status; + self + } + + /// Set the start date + pub fn start_date(mut self, start_date: DateTime) -> Self { + self.start_date = start_date; + self + } + + /// Set the end date + pub fn end_date(mut self, end_date: DateTime) -> Self { + self.end_date = end_date; + self + } + + /// Add a ballot + pub fn add_ballot(mut self, ballot: Ballot) -> Self { + self.ballots.push(ballot); + self + } + + /// Build the vote + pub fn build(self) -> Self { + self + } +} + +impl Model for Vote { + fn db_prefix() -> &'static str { + "vote" + } + + fn get_id(&self) -> u32 { + self.base_data.id + } + + fn base_data_mut(&mut self) -> &mut BaseModelData { + &mut self.base_data + } + + fn db_keys(&self) -> Vec { + vec![ + IndexKey { + name: "company_id", + value: self.company_id.to_string(), + }, + IndexKey { + name: "title", + value: self.title.clone(), + }, + ] + } +} diff --git a/heromodels/src/models/legal/rhai.rs b/heromodels/src/models/legal/rhai.rs index afb83e8..279fa0a 100644 --- a/heromodels/src/models/legal/rhai.rs +++ b/heromodels/src/models/legal/rhai.rs @@ -94,13 +94,13 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let version = i64_to_u32( version_i64, - context.call_position(), + context.position(), "version", "new_contract_revision", )?; let created_at = i64_to_u64( created_at_i64, - context.call_position(), + context.position(), "created_at", "new_contract_revision", )?; @@ -170,7 +170,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let signed_at_u64 = i64_to_u64( signed_at_i64, - context.call_position(), + context.position(), "signed_at", "ContractSigner.signed_at", )?; @@ -247,7 +247,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let base_id = i64_to_u32( base_id_i64, - context.call_position(), + context.position(), "base_id", "new_contract", )?; @@ -290,7 +290,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let start_date_u64 = i64_to_u64( start_date_i64, - context.call_position(), + context.position(), "start_date", "Contract.start_date", )?; @@ -309,7 +309,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let end_date_u64 = i64_to_u64( end_date_i64, - context.call_position(), + context.position(), "end_date", "Contract.end_date", )?; @@ -328,7 +328,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let days_i32 = i64_to_i32( days_i64, - context.call_position(), + context.position(), "renewal_period_days", "Contract.renewal_period_days", )?; @@ -348,7 +348,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let date_u64 = i64_to_u64( date_i64, - context.call_position(), + context.position(), "next_renewal_date", "Contract.next_renewal_date", )?; @@ -400,7 +400,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let version_u32 = i64_to_u32( version_i64, - context.call_position(), + context.position(), "current_version", "Contract.current_version", )?; @@ -416,7 +416,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { -> Result> { let date_u64 = i64_to_u64( date_i64, - context.call_position(), + context.position(), "last_signed_date", "Contract.last_signed_date", )?; @@ -578,7 +578,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { captured_db_for_set.set(&contract).map(|_| ()).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!( - "Failed to set Contract (ID: {}): {}", + "Failed to set Contract (ID: {}): {:?}", contract.base_data.id, e ) .into(), @@ -592,7 +592,7 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { engine.register_fn( "get_contract_by_id", move |context: NativeCallContext, id_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.call_position(), "id", "get_contract_by_id")?; + let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_contract_by_id")?; captured_db_for_get .get_by_id(id_u32) diff --git a/heromodels/src/models/library/collection.rs b/heromodels/src/models/library/collection.rs index c0acead..48d7a45 100644 --- a/heromodels/src/models/library/collection.rs +++ b/heromodels/src/models/library/collection.rs @@ -47,6 +47,11 @@ impl Collection { Self::default() } + /// Returns the ID of the collection. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the collection. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs index 734f04b..57a606a 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/heromodels/src/models/library/rhai.rs @@ -47,6 +47,12 @@ where #[rhai_type(name = "CollectionArray")] pub struct RhaiCollectionArray(pub Vec); +impl From> for RhaiCollectionArray { + fn from(collections: Vec) -> Self { + RhaiCollectionArray(collections) + } +} + #[export_module] mod rhai_library_module { // --- Collection Functions --- From cd2557c1c33e5b7eb5f84a8c56293ea083c1a0d9 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:23:27 +0200 Subject: [PATCH 7/9] move dsl's to rhailib wip --- .../src/models/library => _archive}/rhai.rs | 2 +- heromodels/Cargo.toml | 2 +- heromodels/src/models/access/access.rs | 19 +++-- heromodels/src/models/library/items.rs | 78 +++++++------------ heromodels/src/models/library/mod.rs | 2 - heromodels/src/models/log/README.md | 3 + heromodels/src/models/log/log.rs | 60 ++++++++++++++ heromodels/src/models/log/mod.rs | 5 ++ heromodels/src/models/mod.rs | 3 +- heromodels/src/models/object/README.md | 3 + heromodels/src/models/object/mod.rs | 5 ++ heromodels/src/models/object/object.rs | 44 +++++++++++ 12 files changed, 161 insertions(+), 65 deletions(-) rename {heromodels/src/models/library => _archive}/rhai.rs (99%) create mode 100644 heromodels/src/models/log/README.md create mode 100644 heromodels/src/models/log/log.rs create mode 100644 heromodels/src/models/log/mod.rs create mode 100644 heromodels/src/models/object/README.md create mode 100644 heromodels/src/models/object/mod.rs create mode 100644 heromodels/src/models/object/object.rs diff --git a/heromodels/src/models/library/rhai.rs b/_archive/rhai.rs similarity index 99% rename from heromodels/src/models/library/rhai.rs rename to _archive/rhai.rs index 57a606a..e33d8a4 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/_archive/rhai.rs @@ -655,7 +655,7 @@ mod rhai_library_module { } } -pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { +pub fn register_library_rhai_module(engine: &mut Engine) { let module = exported_module!(rhai_library_module); engine.register_global_module(module.into()); diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 87b0ced..4cd485a 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -86,4 +86,4 @@ required-features = ["rhai"] [[example]] name = "rhai_auth_example" path = "examples/rhai_auth_example.rs" -required-features = ["rhai", "authorization"] +required-features = ["rhai", "macros"] diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs index b5b8050..5aede48 100644 --- a/heromodels/src/models/access/access.rs +++ b/heromodels/src/models/access/access.rs @@ -87,16 +87,16 @@ impl Access { pub fn can_access_resource( db: Arc, public_key: &str, - _resource_id_to_check: u32, - _resource_type_to_check: &str, + object_id: u32, + _object_type: &str, ) -> bool { - // Query for Access records matching the public key. - // Note: This fetches all access records for the user. For performance with many records, - // consider a more specific query if your DB supports it, or caching. + println!("Checking access for public key: {}", public_key); + + // get all access records for object let access_records = match db .collection::() .expect("Failed to get Access collection") - .get::(public_key) + .get::(&object_id) { Ok(records) => records, Err(_e) => { @@ -107,9 +107,8 @@ pub fn can_access_resource( } }; - if !access_records.is_empty() { - return true; - } + println!("Access records: {:#?}", access_records); - false // Default to deny if no grant is found + // if circle_pk is in access records true + return access_records.iter().any(|record| record.circle_pk == public_key) } diff --git a/heromodels/src/models/library/items.rs b/heromodels/src/models/library/items.rs index aacd182..02056b1 100644 --- a/heromodels/src/models/library/items.rs +++ b/heromodels/src/models/library/items.rs @@ -41,6 +41,11 @@ impl Image { Self::default() } + /// Gets the ID of the image. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the image. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); @@ -107,6 +112,11 @@ impl Pdf { Self::default() } + /// Gets the ID of the image. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the PDF. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); @@ -134,7 +144,7 @@ impl Pdf { /// Represents a Markdown document library item. #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Markdown { /// Base model data pub base_data: BaseModelData, @@ -147,23 +157,17 @@ pub struct Markdown { pub content: String, } -impl Default for Markdown { - fn default() -> Self { - Self { - base_data: BaseModelData::new(), - title: String::new(), - description: None, - content: String::new(), - } - } -} - impl Markdown { /// Creates a new `Markdown` document with default values. pub fn new() -> Self { Self::default() } + /// Gets the ID of the image. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the document. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); @@ -184,7 +188,7 @@ impl Markdown { } /// Represents a table of contents entry for a book. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct TocEntry { /// Title of the chapter/section pub title: String, @@ -194,16 +198,6 @@ pub struct TocEntry { pub subsections: Vec, } -impl Default for TocEntry { - fn default() -> Self { - Self { - title: String::new(), - page: 0, - subsections: Vec::new(), - } - } -} - impl TocEntry { /// Creates a new `TocEntry` with default values. pub fn new() -> Self { @@ -231,7 +225,7 @@ impl TocEntry { /// Represents a Book library item (collection of markdown pages with TOC). #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Book { /// Base model data pub base_data: BaseModelData, @@ -246,24 +240,17 @@ pub struct Book { pub pages: Vec, } -impl Default for Book { - fn default() -> Self { - Self { - base_data: BaseModelData::new(), - title: String::new(), - description: None, - table_of_contents: Vec::new(), - pages: Vec::new(), - } - } -} - impl Book { /// Creates a new `Book` with default values. pub fn new() -> Self { Self::default() } + /// Gets the ID of the book. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the book. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); @@ -303,7 +290,7 @@ impl Book { /// Represents a Slides library item (collection of images for slideshow). #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Slides { /// Base model data pub base_data: BaseModelData, @@ -318,24 +305,17 @@ pub struct Slides { pub slide_titles: Vec>, } -impl Default for Slides { - fn default() -> Self { - Self { - base_data: BaseModelData::new(), - title: String::new(), - description: None, - slide_urls: Vec::new(), - slide_titles: Vec::new(), - } - } -} - impl Slides { /// Creates a new `Slides` with default values. pub fn new() -> Self { Self::default() } + /// Gets the ID of the slideshow. + pub fn id(&self) -> u32 { + self.base_data.id + } + /// Sets the title of the slideshow. pub fn title(mut self, title: impl Into) -> Self { self.title = title.into(); diff --git a/heromodels/src/models/library/mod.rs b/heromodels/src/models/library/mod.rs index a9bcd44..f13c08e 100644 --- a/heromodels/src/models/library/mod.rs +++ b/heromodels/src/models/library/mod.rs @@ -1,4 +1,2 @@ pub mod collection; pub mod items; -pub mod rhai; -pub use rhai::register_library_rhai_module; diff --git a/heromodels/src/models/log/README.md b/heromodels/src/models/log/README.md new file mode 100644 index 0000000..472a8e7 --- /dev/null +++ b/heromodels/src/models/log/README.md @@ -0,0 +1,3 @@ +## Object Model + +This is a generic object model mostly used for testing purposes. \ No newline at end of file diff --git a/heromodels/src/models/log/log.rs b/heromodels/src/models/log/log.rs new file mode 100644 index 0000000..1dc2c3b --- /dev/null +++ b/heromodels/src/models/log/log.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use crate::db::{hero::OurDB, Collection, Db}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an event in a contact +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +pub struct Log { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub title: String, + pub description: String, + #[index] + pub subject_pk: String, + #[index] + pub object_id: u32, +} + +impl Log { + pub fn new() -> Self { + Log { + title: String::new(), + base_data: BaseModelData::new(), + description: String::new(), + subject_pk: String::new(), + object_id: 0, + } + } + + pub fn id(&self) -> u32 { + self.base_data.id + } + + pub fn title(mut self, title: String) -> Self { + self.title = title; + self + } + + pub fn description(mut self, description: String) -> Self { + self.description = description; + self + } + + pub fn subject_pk(mut self, subject_pk: String) -> Self { + self.subject_pk = subject_pk; + self + } + + pub fn object_id(mut self, object_id: u32) -> Self { + self.object_id = object_id; + self + } +} \ No newline at end of file diff --git a/heromodels/src/models/log/mod.rs b/heromodels/src/models/log/mod.rs new file mode 100644 index 0000000..77f230e --- /dev/null +++ b/heromodels/src/models/log/mod.rs @@ -0,0 +1,5 @@ +// Export contact module +pub mod log; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::log::Log; diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 7763482..4689378 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -12,6 +12,7 @@ pub mod flow; pub mod governance; pub mod legal; pub mod library; +pub mod object; pub mod projects; // Re-export key types for convenience @@ -38,6 +39,4 @@ pub use circle::register_circle_rhai_module; pub use flow::register_flow_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] -pub use library::register_library_rhai_module; -#[cfg(feature = "rhai")] pub use projects::register_projects_rhai_module; diff --git a/heromodels/src/models/object/README.md b/heromodels/src/models/object/README.md new file mode 100644 index 0000000..472a8e7 --- /dev/null +++ b/heromodels/src/models/object/README.md @@ -0,0 +1,3 @@ +## Object Model + +This is a generic object model mostly used for testing purposes. \ No newline at end of file diff --git a/heromodels/src/models/object/mod.rs b/heromodels/src/models/object/mod.rs new file mode 100644 index 0000000..16a1ca7 --- /dev/null +++ b/heromodels/src/models/object/mod.rs @@ -0,0 +1,5 @@ +// Export contact module +pub mod object; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::object::Object; diff --git a/heromodels/src/models/object/object.rs b/heromodels/src/models/object/object.rs new file mode 100644 index 0000000..8c5bcd5 --- /dev/null +++ b/heromodels/src/models/object/object.rs @@ -0,0 +1,44 @@ +use std::sync::Arc; + +use crate::db::{hero::OurDB, Collection, Db}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an event in a contact +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +pub struct Object { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub title: String, + pub description: String +} + +impl Object { + pub fn new() -> Self { + Object { + title: String::new(), + base_data: BaseModelData::new(), + description: String::new(), + } + } + + pub fn id(&self) -> u32 { + self.base_data.id + } + + pub fn title(mut self, title: String) -> Self { + self.title = title; + self + } + + pub fn description(mut self, description: String) -> Self { + self.description = description; + self + } +} \ No newline at end of file From 41406f2d1ef54d1c11a926fb01c162f5b1add1d3 Mon Sep 17 00:00:00 2001 From: timurgordon Date: Wed, 25 Jun 2025 03:54:49 +0300 Subject: [PATCH 8/9] add theme data to circle and fix slideshow --- heromodels/src/models/circle/circle.rs | 41 +++-- heromodels/src/models/circle/mod.rs | 2 +- heromodels/src/models/circle/rhai.rs | 201 ++++++++++++++++++++----- heromodels/src/models/library/items.rs | 65 ++++---- heromodels/src/models/library/rhai.rs | 68 +++++---- heromodels/src/models/mod.rs | 2 +- 6 files changed, 270 insertions(+), 109 deletions(-) diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs index 450444b..b9a0696 100644 --- a/heromodels/src/models/circle/circle.rs +++ b/heromodels/src/models/circle/circle.rs @@ -1,10 +1,33 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; -// Temporarily removed to fix compilation issues -// use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; + +/// Represents the visual theme for a circle. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct ThemeData { + pub primary_color: String, + pub background_color: String, + pub background_pattern: String, + pub logo_symbol: String, + pub logo_url: String, + pub nav_dashboard_visible: bool, + pub nav_timeline_visible: bool, +} + +impl Default for ThemeData { + fn default() -> Self { + Self { + primary_color: "#3b82f6".to_string(), + background_color: "#0a0a0a".to_string(), + background_pattern: "none".to_string(), + logo_symbol: "â—¯".to_string(), + logo_url: "".to_string(), + nav_dashboard_visible: true, + nav_timeline_visible: true, + } + } +} /// Represents an event in a calendar #[model] @@ -24,7 +47,7 @@ pub struct Circle { /// Logo URL or symbol for the circle pub logo: Option, /// Theme settings for the circle (colors, styling, etc.) - pub theme: HashMap, + pub theme: ThemeData, } impl Circle { @@ -38,7 +61,7 @@ impl Circle { circles: Vec::new(), logo: None, members: Vec::new(), - theme: HashMap::new(), + theme: ThemeData::default(), } } @@ -66,14 +89,8 @@ impl Circle { self } - /// Sets a theme property for the circle - pub fn theme_property(mut self, key: impl ToString, value: impl ToString) -> Self { - self.theme.insert(key.to_string(), value.to_string()); - self - } - /// Sets the entire theme for the circle - pub fn theme(mut self, theme: HashMap) -> Self { + pub fn theme(mut self, theme: ThemeData) -> Self { self.theme = theme; self } diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs index cb5c353..864b982 100644 --- a/heromodels/src/models/circle/mod.rs +++ b/heromodels/src/models/circle/mod.rs @@ -3,5 +3,5 @@ pub mod circle; pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::circle::Circle; +pub use self::circle::{Circle, ThemeData}; pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs index 3bab892..d51c1cb 100644 --- a/heromodels/src/models/circle/rhai.rs +++ b/heromodels/src/models/circle/rhai.rs @@ -4,28 +4,23 @@ use rhai::{Array, CustomType, Dynamic, Engine, EvalAltResult, INT, Module, Posit use std::mem; use std::sync::Arc; -use super::circle::Circle; +use super::circle::{Circle, ThemeData}; type RhaiCircle = Circle; +type RhaiThemeData = ThemeData; + use crate::db::Collection; use crate::db::hero::OurDB; use serde::Serialize; use serde_json; -use std::collections::HashMap; /// Registers a `.json()` method for any type `T` that implements the required traits. fn register_json_method(engine: &mut Engine) where - // The type must be: - T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai + T: CustomType + Clone + Serialize, { - // This is the function that will be called when a script runs '.json()' let to_json_fn = |obj: &mut T| -> Result> { - // Use serde_json to serialize the object to a pretty-formatted string. - // The '?' will automatically convert any serialization error into a Rhai error. serde_json::to_string(obj).map_err(|e| e.to_string().into()) }; - - // Register the function as a method named "json" for the type 'T'. engine.build_type::().register_fn("json", to_json_fn); } @@ -39,6 +34,128 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { }) } +#[export_module] +mod rhai_theme_data_module { + #[rhai_fn(name = "new_theme_data")] + pub fn new_theme_data() -> RhaiThemeData { + ThemeData::default() + } + + // --- Setters for ThemeData --- + #[rhai_fn(name = "primary_color", return_raw, global, pure)] + pub fn set_primary_color( + theme: &mut RhaiThemeData, + color: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.primary_color = color; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "background_color", return_raw, global, pure)] + pub fn set_background_color( + theme: &mut RhaiThemeData, + color: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.background_color = color; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "background_pattern", return_raw, global, pure)] + pub fn set_background_pattern( + theme: &mut RhaiThemeData, + pattern: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.background_pattern = pattern; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "logo_symbol", return_raw, global, pure)] + pub fn set_logo_symbol( + theme: &mut RhaiThemeData, + symbol: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.logo_symbol = symbol; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "logo_url", return_raw, global, pure)] + pub fn set_logo_url( + theme: &mut RhaiThemeData, + url: String, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.logo_url = url; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "nav_dashboard_visible", return_raw, global, pure)] + pub fn set_nav_dashboard_visible( + theme: &mut RhaiThemeData, + visible: bool, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.nav_dashboard_visible = visible; + *theme = owned_theme; + Ok(theme.clone()) + } + + #[rhai_fn(name = "nav_timeline_visible", return_raw, global, pure)] + pub fn set_nav_timeline_visible( + theme: &mut RhaiThemeData, + visible: bool, + ) -> Result> { + let mut owned_theme = mem::take(theme); + owned_theme.nav_timeline_visible = visible; + *theme = owned_theme; + Ok(theme.clone()) + } + + // --- Getters for ThemeData --- + #[rhai_fn(name = "get_primary_color", pure)] + pub fn get_primary_color(theme: &mut RhaiThemeData) -> String { + theme.primary_color.clone() + } + + #[rhai_fn(name = "get_background_color", pure)] + pub fn get_background_color(theme: &mut RhaiThemeData) -> String { + theme.background_color.clone() + } + + #[rhai_fn(name = "get_background_pattern", pure)] + pub fn get_background_pattern(theme: &mut RhaiThemeData) -> String { + theme.background_pattern.clone() + } + + #[rhai_fn(name = "get_logo_symbol", pure)] + pub fn get_logo_symbol(theme: &mut RhaiThemeData) -> String { + theme.logo_symbol.clone() + } + + #[rhai_fn(name = "get_logo_url", pure)] + pub fn get_logo_url(theme: &mut RhaiThemeData) -> String { + theme.logo_url.clone() + } + + #[rhai_fn(name = "get_nav_dashboard_visible", pure)] + pub fn get_nav_dashboard_visible(theme: &mut RhaiThemeData) -> bool { + theme.nav_dashboard_visible + } + + #[rhai_fn(name = "get_nav_timeline_visible", pure)] + pub fn get_nav_timeline_visible(theme: &mut RhaiThemeData) -> bool { + theme.nav_timeline_visible + } +} + #[export_module] mod rhai_circle_module { // --- Circle Functions --- @@ -95,7 +212,7 @@ mod rhai_circle_module { #[rhai_fn(name = "theme", return_raw, global, pure)] pub fn circle_theme( circle: &mut RhaiCircle, - theme: HashMap, + theme: RhaiThemeData, ) -> Result> { let owned_circle = mem::take(circle); *circle = owned_circle.theme(theme); @@ -108,7 +225,6 @@ mod rhai_circle_module { circle: &mut RhaiCircle, added_circle: String, ) -> Result> { - // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_circle(added_circle); Ok(circle.clone()) @@ -120,93 +236,89 @@ mod rhai_circle_module { circle: &mut RhaiCircle, added_member: String, ) -> Result> { - // Use take to get ownership of the circle let owned_circle = mem::take(circle); *circle = owned_circle.add_member(added_member); Ok(circle.clone()) } // Circle Getters - #[rhai_fn(get = "id", pure)] + #[rhai_fn(name = "get_id", pure)] pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } - #[rhai_fn(get = "created_at", pure)] + #[rhai_fn(name = "get_created_at", pure)] pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } - #[rhai_fn(get = "modified_at", pure)] + #[rhai_fn(name = "get_modified_at", pure)] pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } - #[rhai_fn(get = "title", pure)] + #[rhai_fn(name = "get_title", pure)] pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } - #[rhai_fn(get = "description", pure)] + #[rhai_fn(name = "get_description", pure)] pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } - #[rhai_fn(get = "circles", pure)] + #[rhai_fn(name = "get_circles", pure)] pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } - #[rhai_fn(get = "ws_url", pure)] + #[rhai_fn(name = "get_ws_url", pure)] pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } - #[rhai_fn(get = "logo", pure)] + #[rhai_fn(name = "get_logo", pure)] pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } - #[rhai_fn(get = "theme", pure)] - pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { + #[rhai_fn(name = "get_theme", pure)] + pub fn get_circle_theme(circle: &mut RhaiCircle) -> RhaiThemeData { circle.theme.clone() } } pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_circle_module); - engine.register_global_module(module.into()); + engine.build_type::(); + engine.build_type::(); - // Create a module for database functions let mut db_module = Module::new(); + let circle_module = exported_module!(rhai_circle_module); + let theme_data_module = exported_module!(rhai_theme_data_module); + + engine.register_global_module(circle_module.into()); + engine.register_global_module(theme_data_module.into()); + + register_json_method::(engine); + register_json_method::(engine); // Manually register database functions as they need to capture 'db' let db_clone_set_circle = db.clone(); db_module.set_native_fn( "save_circle", move |circle: Circle| -> Result> { - // Use the Collection trait method directly let result = db_clone_set_circle.set(&circle).map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("DB Error set_circle: {}", e).into(), Position::NONE, )) })?; - - // Return the updated circle with the correct ID Ok(result.1) }, ); - register_json_method::(engine); - - // Manually register database functions as they need to capture 'db' let db_clone_delete_circle = db.clone(); db_module.set_native_fn( "delete_circle", move |circle: Circle| -> Result<(), Box> { - // Use the Collection trait method directly let result = db_clone_delete_circle .collection::() .expect("can open circle collection") .delete_by_id(circle.base_data.id) .expect("can delete circle"); - - // Return the updated circle with the correct ID Ok(result) }, ); @@ -215,7 +327,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { db_module.set_native_fn( "get_circle", move || -> Result> { - // Use the Collection trait method directly let all_circles: Vec = db_clone_get_circle.get_all().map_err(|e| { Box::new(EvalAltResult::ErrorRuntime( format!("DB Error get_circle: {}", e).into(), @@ -234,12 +345,26 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); + // --- Collection DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn( + "save_circle", + move |circle: RhaiCircle| -> Result> { + let result = db_clone.set(&circle).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error: {:?}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); + let db_clone_get_circle_by_id = db.clone(); db_module.set_native_fn( "get_circle_by_id", move |id_i64: INT| -> Result> { let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly db_clone_get_circle_by_id .get_by_id(id_u32) .map_err(|e| { @@ -257,7 +382,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // Add list_circles function to get all circles let db_clone_list_circles = db.clone(); db_module.set_native_fn( "list_circles", @@ -282,7 +406,6 @@ pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // Register the database module globally engine.register_global_module(db_module.into()); println!("Successfully registered circle Rhai module using export_module approach."); diff --git a/heromodels/src/models/library/items.rs b/heromodels/src/models/library/items.rs index aacd182..0743be4 100644 --- a/heromodels/src/models/library/items.rs +++ b/heromodels/src/models/library/items.rs @@ -301,10 +301,10 @@ impl Book { } } -/// Represents a Slides library item (collection of images for slideshow). +/// Represents a Slideshow library item (collection of images for slideshow). #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] -pub struct Slides { +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Slideshow { /// Base model data pub base_data: BaseModelData, /// Title of the slideshow @@ -312,26 +312,44 @@ pub struct Slides { pub title: String, /// Optional description of the slideshow pub description: Option, - /// List of slide image URLs - pub slide_urls: Vec, - /// Optional slide titles/captions - pub slide_titles: Vec>, + /// List of slides + pub slides: Vec, } -impl Default for Slides { - fn default() -> Self { +#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, CustomType)] +pub struct Slide { + pub image_url: String, + pub title: Option, + pub description: Option, +} + +impl Slide { + pub fn new() -> Self { Self { - base_data: BaseModelData::new(), - title: String::new(), + image_url: String::new(), + title: None, description: None, - slide_urls: Vec::new(), - slide_titles: Vec::new(), } } + + pub fn url(mut self, url: impl Into) -> Self { + self.image_url = url.into(); + self + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } } -impl Slides { - /// Creates a new `Slides` with default values. +impl Slideshow { + /// Creates a new `Slideshow` with default values. pub fn new() -> Self { Self::default() } @@ -349,21 +367,8 @@ impl Slides { } /// Adds a slide with URL and optional title. - pub fn add_slide(mut self, url: impl Into, title: Option) -> Self { - self.slide_urls.push(url.into()); - self.slide_titles.push(title); - self - } - - /// Sets all slide URLs at once. - pub fn slide_urls(mut self, urls: Vec) -> Self { - self.slide_urls = urls; - self - } - - /// Sets all slide titles at once. - pub fn slide_titles(mut self, titles: Vec>) -> Self { - self.slide_titles = titles; + pub fn add_slide(mut self, slide: Slide) -> Self { + self.slides.push(slide); self } } diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs index 734f04b..192e98e 100644 --- a/heromodels/src/models/library/rhai.rs +++ b/heromodels/src/models/library/rhai.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use super::collection::Collection as RhaiCollection; use super::items::{ Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, - Slides as RhaiSlides, TocEntry as RhaiTocEntry, + Slide as RhaiSlide, Slideshow as RhaiSlides, TocEntry as RhaiTocEntry, }; use crate::db::Collection as DbCollectionTrait; use crate::db::hero::OurDB; @@ -565,7 +565,40 @@ mod rhai_library_module { book.pages.clone() } - // --- Slides Functions --- + // --- Slideshow Functions --- + #[rhai_fn(name = "new_slide")] + pub fn new_slide() -> RhaiSlide { + RhaiSlide::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn slide_title( + slide: &mut RhaiSlide, + title: String, + ) -> Result> { + let owned = mem::take(slide); + *slide = owned.title(title); + Ok(slide.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn slide_description( + slide: &mut RhaiSlide, + description: String, + ) -> Result> { + let owned = mem::take(slide); + *slide = owned.description(description); + Ok(slide.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn slide_url(slide: &mut RhaiSlide, url: String) -> Result> { + let owned = mem::take(slide); + *slide = owned.url(url); + Ok(slide.clone()) + } + + // --- Slideshow Functions --- #[rhai_fn(name = "new_slides")] pub fn new_slides() -> RhaiSlides { RhaiSlides::new() @@ -594,22 +627,10 @@ mod rhai_library_module { #[rhai_fn(name = "add_slide", return_raw, global, pure)] pub fn slides_add_slide( slides: &mut RhaiSlides, - url: String, - title: String, + slide: RhaiSlide, ) -> Result> { let owned = mem::take(slides); - let title_opt = if title.is_empty() { None } else { Some(title) }; - *slides = owned.add_slide(url, title_opt); - Ok(slides.clone()) - } - - #[rhai_fn(name = "add_slide", return_raw, global, pure)] - pub fn slides_add_slide_no_title( - slides: &mut RhaiSlides, - url: String, - ) -> Result> { - let owned = mem::take(slides); - *slides = owned.add_slide(url, None); + *slides = owned.add_slide(slide); Ok(slides.clone()) } @@ -638,14 +659,9 @@ mod rhai_library_module { slides.description.clone() } - #[rhai_fn(get = "slide_urls", pure)] - pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { - slides.slide_urls.clone() - } - - #[rhai_fn(get = "slide_titles", pure)] - pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { - slides.slide_titles.clone() + #[rhai_fn(get = "slides", pure)] + pub fn get_slides_slides(slides: &mut RhaiSlides) -> Vec { + slides.slides.clone() } } @@ -969,7 +985,7 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { }, ); - // --- Slides DB Functions --- + // --- Slideshow DB Functions --- let db_clone = db.clone(); db_module.set_native_fn( "save_slides", @@ -999,7 +1015,7 @@ pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { })? .ok_or_else(|| { Box::new(EvalAltResult::ErrorRuntime( - format!("Slides with ID {} not found", slides_id).into(), + format!("Slideshow with ID {} not found", slides_id).into(), Position::NONE, )) }) diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 7763482..9a121ff 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -20,7 +20,7 @@ pub use userexample::User; // pub use productexample::Product; // Temporarily remove pub use biz::{Sale, SaleItem, SaleStatus}; pub use calendar::{AttendanceStatus, Attendee, Calendar, Event}; -pub use circle::Circle; +pub use circle::{Circle, ThemeData}; pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; pub use finance::{Account, Asset, AssetType}; pub use flow::{Flow, FlowStep, SignatureRequirement}; From 149c177def65fe855490fe5844e74025241aaf6e Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Wed, 25 Jun 2025 03:27:22 +0200 Subject: [PATCH 9/9] remove rhai dsls --- heromodels/Cargo.lock | 42 +- heromodels/Cargo.toml | 1 + heromodels/examples/biz_rhai/biz.rhai | 275 ----- heromodels/examples/biz_rhai/example.rs | 45 - .../examples/calendar_rhai/calendar.rhai | 74 -- heromodels/examples/calendar_rhai/example.rs | 98 -- heromodels/examples/finance_rhai/example.rs | 121 --- heromodels/examples/finance_rhai/finance.rhai | 143 --- heromodels/examples/flow_rhai/example.rs | 41 - heromodels/examples/flow_rhai/flow.rs | 107 -- heromodels/examples/gov_example.rhai | 222 ---- heromodels/examples/gov_rhai_example.rs | 57 - .../examples/governance_rhai/example.rs | 290 ------ .../examples/governance_rhai/governance.rhai | 85 -- .../governance_rhai_client/example.rs | 457 -------- heromodels/examples/legal_rhai/example.rs | 49 - heromodels/examples/legal_rhai/legal.rhai | 119 --- heromodels/examples/library_rhai/example.rs | 37 - heromodels/examples/library_rhai/library.rhai | 78 -- heromodels/examples/project_rhai/example.rs | 43 - .../examples/project_rhai/project_test.rhai | 69 -- .../project_rhai_wasm/.cargo/config.toml | 9 - .../examples/project_rhai_wasm/Cargo.toml | 20 - .../examples/project_rhai_wasm/index.html | 52 - .../examples/project_rhai_wasm/src/lib.rs | 126 --- heromodels/src/models/access/access.rs | 31 +- heromodels/src/models/access/mod.rs | 2 - heromodels/src/models/access/rhai.rs | 238 ----- heromodels/src/models/biz/company.rs | 22 +- heromodels/src/models/biz/mod.rs | 9 +- heromodels/src/models/biz/product.rs | 3 +- heromodels/src/models/biz/rhai.rs | 794 -------------- heromodels/src/models/biz/sale.rs | 5 +- heromodels/src/models/calendar/mod.rs | 2 - heromodels/src/models/calendar/rhai.rs | 484 --------- heromodels/src/models/circle/circle.rs | 16 +- heromodels/src/models/contact/contact.rs | 15 + heromodels/src/models/contact/mod.rs | 2 - heromodels/src/models/contact/rhai.rs | 336 ------ heromodels/src/models/finance/mod.rs | 1 - heromodels/src/models/finance/rhai.rs | 973 ------------------ heromodels/src/models/flow/flow.rs | 4 +- heromodels/src/models/flow/flow_step.rs | 4 +- heromodels/src/models/flow/mod.rs | 4 +- heromodels/src/models/flow/rhai.rs | 535 ---------- .../src/models/flow/signature_requirement.rs | 4 +- heromodels/src/models/legal/mod.rs | 2 - heromodels/src/models/legal/rhai.rs | 613 ----------- heromodels/src/models/mod.rs | 13 +- heromodels/src/models/object/object.rs | 11 +- heromodels/src/models/projects/mod.rs | 6 - heromodels/src/models/projects/rhai.rs | 408 -------- 52 files changed, 120 insertions(+), 7077 deletions(-) delete mode 100644 heromodels/examples/biz_rhai/biz.rhai delete mode 100644 heromodels/examples/biz_rhai/example.rs delete mode 100644 heromodels/examples/calendar_rhai/calendar.rhai delete mode 100644 heromodels/examples/calendar_rhai/example.rs delete mode 100644 heromodels/examples/finance_rhai/example.rs delete mode 100644 heromodels/examples/finance_rhai/finance.rhai delete mode 100644 heromodels/examples/flow_rhai/example.rs delete mode 100644 heromodels/examples/flow_rhai/flow.rs delete mode 100644 heromodels/examples/gov_example.rhai delete mode 100644 heromodels/examples/gov_rhai_example.rs delete mode 100644 heromodels/examples/governance_rhai/example.rs delete mode 100644 heromodels/examples/governance_rhai/governance.rhai delete mode 100644 heromodels/examples/governance_rhai_client/example.rs delete mode 100644 heromodels/examples/legal_rhai/example.rs delete mode 100644 heromodels/examples/legal_rhai/legal.rhai delete mode 100644 heromodels/examples/library_rhai/example.rs delete mode 100644 heromodels/examples/library_rhai/library.rhai delete mode 100644 heromodels/examples/project_rhai/example.rs delete mode 100644 heromodels/examples/project_rhai/project_test.rhai delete mode 100644 heromodels/examples/project_rhai_wasm/.cargo/config.toml delete mode 100644 heromodels/examples/project_rhai_wasm/Cargo.toml delete mode 100644 heromodels/examples/project_rhai_wasm/index.html delete mode 100644 heromodels/examples/project_rhai_wasm/src/lib.rs delete mode 100644 heromodels/src/models/access/rhai.rs delete mode 100644 heromodels/src/models/biz/rhai.rs delete mode 100644 heromodels/src/models/calendar/rhai.rs delete mode 100644 heromodels/src/models/contact/rhai.rs delete mode 100644 heromodels/src/models/finance/rhai.rs delete mode 100644 heromodels/src/models/flow/rhai.rs delete mode 100644 heromodels/src/models/legal/rhai.rs delete mode 100644 heromodels/src/models/projects/rhai.rs diff --git a/heromodels/Cargo.lock b/heromodels/Cargo.lock index 4d7c0f9..2bf5b50 100644 --- a/heromodels/Cargo.lock +++ b/heromodels/Cargo.lock @@ -146,6 +146,14 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "derive" +version = "0.1.0" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -181,6 +189,7 @@ version = "0.1.0" dependencies = [ "bincode", "chrono", + "derive", "heromodels-derive", "heromodels_core", "ourdb", @@ -200,7 +209,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -410,7 +419,7 @@ dependencies = [ "proc-macro2", "quote", "rhai", - "syn", + "syn 2.0.101", ] [[package]] @@ -421,7 +430,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -463,7 +472,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -529,7 +538,18 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.101", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -566,7 +586,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -658,7 +678,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -680,7 +700,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -715,7 +735,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -726,7 +746,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -779,5 +799,5 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 4cd485a..c113a97 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -14,6 +14,7 @@ ourdb = { path = "../ourdb" } tst = { path = "../tst" } heromodels-derive = { path = "../heromodels-derive" } heromodels_core = { path = "../heromodels_core" } +rhailib_derive = { package = "derive", path = "../../rhailib/src/derive" } rhai = { version = "1.21.0", features = ["std", "sync", "decimal", "internals"] } # Added "decimal" feature, sync for Arc> rhai_client_macros = { path = "../rhai_client_macros" } strum = "0.26" diff --git a/heromodels/examples/biz_rhai/biz.rhai b/heromodels/examples/biz_rhai/biz.rhai deleted file mode 100644 index a044707..0000000 --- a/heromodels/examples/biz_rhai/biz.rhai +++ /dev/null @@ -1,275 +0,0 @@ -// Hero Models - Biz Rhai Example - -print("Hero Models - Biz Rhai Example"); -print("==============================="); -print("DB instance will be implicitly passed to DB functions."); - -// --- Enum Constants --- -print("\n--- Enum Constants ---"); -print(`CompanyStatus Active: ${CompanyStatusConstants::Active}`); -print(`CompanyStatus Inactive: ${CompanyStatusConstants::Inactive}`); -print(`BusinessType Coop: ${BusinessTypeConstants::Coop}`); -print(`BusinessType Global: ${BusinessTypeConstants::Global}`); - -// --- Testing Company Model --- -print("\n--- Testing Company Model ---"); - -let company1_uuid = "comp-uuid-alpha-001"; -let company1_name = "Innovatech Solutions Ltd."; -let company1_reg = "REG-ISL-2024"; -let company1_inc_date = 1672531200; // Jan 1, 2023 - -print(`Creating a new company (UUID: ${company1_uuid})...`); -let company1 = new_company(company1_name, company1_reg, company1_inc_date) - .email("contact@innovatech.com") - .phone("+1-555-0100") - .website("https://innovatech.com") - .address("123 Innovation Drive, Tech City, TC 54321") - .business_type(BusinessTypeConstants::Global) - .industry("Technology") - .description("Leading provider of innovative tech solutions.") - .status(CompanyStatusConstants::Active) - .fiscal_year_end("12-31") - .set_base_created_at(1672531200) - .set_base_modified_at(1672531205); - -print(`Company 1 Name: ${company1.name}, Status: ${company1.status}`); -print(`Company 1 Email: ${company1.email}, Industry: ${company1.industry}`); -// Save the company to the database -print("\nSaving company1 to database..."); -company1 = set_company(company1); // Capture the company with the DB-assigned ID -print("Company1 saved."); - -// Retrieve the company -print(`\nRetrieving company by ID (${company1.id})...`); -let retrieved_company = get_company_by_id(company1.id); -print(`Retrieved Company: ${retrieved_company.name}, Status: ${retrieved_company.status}`); -print(`Retrieved Company Reg No: ${retrieved_company.registration_number}, Website: ${retrieved_company.website}`); - -// --- Testing Shareholder Model --- -print("\n--- Testing Shareholder Model ---"); - - -let sh1_user_id = 3001; // Example user ID -let sh1_name = "Alice Wonderland"; -let sh1_since = 1672617600; // Example timestamp (Jan 2, 2023) - -print(`Creating shareholder 1 for company ${company1.id}...`); -let shareholder1 = new_shareholder() - .company_id(company1.id) - .user_id(sh1_user_id) - .name(sh1_name) - .shares(1000.0) - .percentage(10.0) // CALCULATED - .type_("Individual") - .since(sh1_since) - .set_base_created_at(1672617600); - -shareholder1 = set_shareholder(shareholder1); -print("Shareholder 1 saved."); - -let retrieved_sh1 = get_shareholder_by_id(shareholder1.id); -print(`Retrieved Shareholder 1: ${retrieved_sh1.name}, Type: ${retrieved_sh1.type_}, Shares: ${retrieved_sh1.shares}`); - - -let sh2_entity_id = 4001; // Example corporate entity ID -let sh2_name = "Mad Hatter Inc."; -let sh2_since = 1672704000; // Example timestamp (Jan 3, 2023) - -print(`\nCreating shareholder 2 for company ${company1.id}...`); -let shareholder2 = new_shareholder() - .company_id(company1.id) - .user_id(sh2_entity_id) // Using user_id field for entity_id for simplicity in example - .name(sh2_name) - .shares(5000.0) - .percentage(50.0) - .type_(ShareholderTypeConstants::Corporate) - .since(sh2_since) - .set_base_created_at(1672704000); - -shareholder2 = set_shareholder(shareholder2); -print("Shareholder 2 saved."); - -let retrieved_sh2 = get_shareholder_by_id(shareholder2.id); -print(`Retrieved Shareholder 2: ${retrieved_sh2.name}, Type: ${retrieved_sh2.type_}, Percentage: ${retrieved_sh2.percentage}`); - -// --- Testing Update for Company (Example - if setters were fully implemented for complex updates) --- -print("\n--- Testing Update for Company ---"); -let updated_company = retrieved_company - .description("Leading global provider of cutting-edge technology solutions and services.") - .status(CompanyStatusConstants::Active) - .phone("+1-555-0199"); // Assume modified_at would be updated by set_company - -print(`Updated Company - Name: ${updated_company.name}, New Phone: ${updated_company.phone}`); -set_company(updated_company); -print("Updated Company saved."); - -let final_retrieved_company = get_company_by_id(company1.id); -print(`Final Retrieved Company - Description: '${final_retrieved_company.description}', Phone: ${final_retrieved_company.phone}`); - -print("\n--- Testing Product Model ---"); - -// Print ProductType constants -print("\n--- ProductType Constants ---"); -print(`Product Type Product: ${ProductTypeConstants::Product}`); -print(`Product Type Service: ${ProductTypeConstants::Service}`); - -// Print ProductStatus constants -print("\n--- ProductStatus Constants ---"); -print(`Product Status Available: ${ProductStatusConstants::Available}`); -print(`Product Status Unavailable: ${ProductStatusConstants::Unavailable}`); - -// Create a product component -let component1 = new_product_component("Super Capacitor") - .description("High-capacity energy storage unit") - .quantity(2); -print(`\nCreated Product Component: ${component1.name}, Qty: ${component1.quantity}`); - -// Create Product 1 (a physical product with a component) - -let product1_name = "Advanced Gadget X"; -let product1_creation_time = 1672876800; // Example timestamp (Jan 5, 2023) - -print(`\nCreating Product 1: ${product1_name}...`); -let product1 = new_product() - .name(product1_name) - .description("A revolutionary gadget with cutting-edge features.") - .price(299.99) - .type_(ProductTypeConstants::Product) - .category("Electronics") - .status(ProductStatusConstants::Available) - .max_amount(1000) - .purchase_till(1704067199) // Dec 31, 2023, 23:59:59 - .active_till(1735689599) // Dec 31, 2024, 23:59:59 - .add_component(component1) - .set_base_created_at(product1_creation_time); - -print("Saving Product 1..."); -product1 = set_product(product1); -print("Product 1 saved."); - -print(`\nRetrieving Product 1 (ID: ${product1.id})...`); -let retrieved_product1 = get_product_by_id(product1.id); -print(`Retrieved Product 1: ${retrieved_product1.name}, Price: ${retrieved_product1.price}, Type: ${retrieved_product1.type_}`); -if retrieved_product1.components.len() > 0 { - print(`Product 1 Component 1: ${retrieved_product1.components[0].name}, Desc: ${retrieved_product1.components[0].description}`); -} else { - print("Product 1 has no components."); -} - -// Create Product 2 (a service) - -let product2_name = "Cloud Backup Service - Pro Plan"; -let product2_creation_time = 1672963200; // Example timestamp (Jan 6, 2023) - -print(`\nCreating Product 2: ${product2_name}...`); -let product2 = new_product() - .name(product2_name) - .description("Unlimited cloud backup with 24/7 support.") - .price(19.99) // Monthly price - .type_(ProductTypeConstants::Service) - .category("Cloud Services") - .status(ProductStatusConstants::Available) - .active_till(1735689599) // Valid for a long time, or represents subscription cycle end - .set_base_created_at(product2_creation_time); - -print("Saving Product 2..."); -product2 = set_product(product2); -print("Product 2 saved."); - -print(`\nRetrieving Product 2 (ID: ${product2.id})...`); -let retrieved_product2 = get_product_by_id(product2.id); -print(`Retrieved Product 2: ${retrieved_product2.name}, Category: ${retrieved_product2.category}, Status: ${retrieved_product2.status}`); -if retrieved_product2.components.len() > 0 { - print(`Product 2 has ${retrieved_product2.components.len()} components.`); -} else { - print("Product 2 has no components (as expected for a service)."); -} - - -// --- Testing Sale Model --- -print("\n--- Testing Sale Model ---"); - -// Print SaleStatus constants -print("\n--- SaleStatus Constants ---"); -print(`Sale Status Pending: ${SaleStatusConstants::Pending}`); -print(`Sale Status Completed: ${SaleStatusConstants::Completed}`); -print(`Sale Status Cancelled: ${SaleStatusConstants::Cancelled}`); - -// Create SaleItem 1 (using product1 from above) -let sale_item1_product_id = product1.id; // Using product1.id after it's set // Using product1_id from product example -let sale_item1_name = retrieved_product1.name; // Using name from retrieved product1 -let sale_item1_qty = 2; -let sale_item1_unit_price = retrieved_product1.price; -let sale_item1_subtotal = sale_item1_qty * sale_item1_unit_price; - -print(`\nCreating SaleItem 1 for Product ID: ${sale_item1_product_id}, Name: ${sale_item1_name}...`); -let sale_item1 = new_sale_item(sale_item1_product_id, sale_item1_name, sale_item1_qty, sale_item1_unit_price, sale_item1_subtotal); -print(`SaleItem 1: Product ID ${sale_item1.product_id}, Qty: ${sale_item1.quantity}, Subtotal: ${sale_item1.subtotal}`); - -// Create SaleItem 2 (using product2 from above) -let sale_item2_product_id = product2.id; // Using product2.id after it's set // Using product2_id from product example -let sale_item2_name = retrieved_product2.name; -let sale_item2_qty = 1; -let sale_item2_unit_price = retrieved_product2.price; -let sale_item2_subtotal = sale_item2_qty * sale_item2_unit_price; - -print(`\nCreating SaleItem 2 for Product ID: ${sale_item2_product_id}, Name: ${sale_item2_name}...`); -let sale_item2 = new_sale_item(sale_item2_product_id, sale_item2_name, sale_item2_qty, sale_item2_unit_price, sale_item2_subtotal); -print(`SaleItem 2: Product ID ${sale_item2.product_id}, Qty: ${sale_item2.quantity}, Subtotal: ${sale_item2.subtotal}`); - -// Create a Sale - -let sale1_customer_id = company1.id; // Example: company1 is the customer -let sale1_date = 1673049600; // Example timestamp (Jan 7, 2023) -let sale1_total_amount = sale_item1.subtotal + sale_item2.subtotal; - -print(`\nCreating Sale 1 for Customer ID: ${sale1_customer_id}...`); -let sale1 = new_sale( - sale1_customer_id, // for company_id_i64 in Rhai registration - "Temp Buyer Name", // for buyer_name in Rhai registration - "temp@buyer.com", // for buyer_email in Rhai registration - 0.0, // for total_amount (will be overridden by builder) - SaleStatusConstants::Pending, // for status (will be overridden by builder) - 0 // for sale_date (will be overridden by builder) -) - .customer_id(sale1_customer_id) // Actual field on Sale struct - .status(SaleStatusConstants::Pending) - .sale_date(sale1_date) - .add_item(sale_item1) // Add item one by one - .add_item(sale_item2) - // Alternatively, to set all items at once (if items were already in an array): - // .items([sale_item1, sale_item2]) - .total_amount(sale1_total_amount) - .notes("First major sale of the year. Includes Advanced Gadget X and Cloud Backup Pro.") - .set_base_created_at(sale1_date) - .set_base_modified_at(sale1_date + 300) // 5 mins later - .add_base_comment(1) // Example comment ID - .add_base_comment(2); - -print(`Sale 1 Created: ID ${sale1.id}, Customer ID: ${sale1.customer_id}, Status: ${sale1.status}, Total: ${sale1.total_amount}`); -print(`Sale 1 Notes: ${sale1.notes}`); -print(`Sale 1 Item Count: ${sale1.items.len()}`); -if sale1.items.len() > 0 { - print(`Sale 1 Item 1: ${sale1.items[0].name}, Qty: ${sale1.items[0].quantity}`); -} -if sale1.items.len() > 1 { - print(`Sale 1 Item 2: ${sale1.items[1].name}, Qty: ${sale1.items[1].quantity}`); -} -print(`Sale 1 Base Comments: ${sale1.comments}`); - -// Save Sale 1 to database -print("\nSaving Sale 1 to database..."); -sale1 = set_sale(sale1); -print("Sale 1 saved."); - -// Retrieve Sale 1 from database -print(`\nRetrieving Sale 1 by ID (${sale1.id})...`); -let retrieved_sale1 = get_sale_by_id(sale1.id); -print(`Retrieved Sale 1: ID ${retrieved_sale1.id}, Customer: ${retrieved_sale1.customer_id}, Status: ${retrieved_sale1.status}`); -print(`Retrieved Sale 1 Total: ${retrieved_sale1.total_amount}, Notes: '${retrieved_sale1.notes}'`); -if retrieved_sale1.items.len() > 0 { - print(`Retrieved Sale 1 Item 1: ${retrieved_sale1.items[0].name} (Product ID: ${retrieved_sale1.items[0].product_id})`); -} - -print("\nBiz Rhai example script finished."); diff --git a/heromodels/examples/biz_rhai/example.rs b/heromodels/examples/biz_rhai/example.rs deleted file mode 100644 index f316fae..0000000 --- a/heromodels/examples/biz_rhai/example.rs +++ /dev/null @@ -1,45 +0,0 @@ -use heromodels::db::hero::OurDB; // Corrected path for OurDB -use heromodels::models::biz::register_biz_rhai_module; // Corrected path -use rhai::{Engine, EvalAltResult, Scope}; -use std::fs; -use std::sync::Arc; - -fn main() -> Result<(), Box> { - println!("Executing Rhai script: examples/biz_rhai/biz.rhai"); - - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Create an Arc> instance - // For this example, we'll use an in-memory DB. - // The actual DB path or configuration might come from elsewhere in a real app. - let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB")); // Corrected OurDB::new args and removed Mutex - - // Register the biz module with the engine - register_biz_rhai_module(&mut engine, Arc::clone(&db_instance)); - - // Read the Rhai script from file - let script_path = "examples/biz_rhai/biz.rhai"; - let script_content = fs::read_to_string(script_path).map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - format!("Cannot read script file: {}", script_path), - e.into(), - )) - })?; - - // Create a new scope - let mut scope = Scope::new(); - - // Execute the script - match engine.run_with_scope(&mut scope, &script_content) { - Ok(_) => { - println!("Rhai script executed successfully!"); - Ok(()) - } - Err(e) => { - println!("Rhai script execution failed: {}", e); - println!("Details: {:?}", e); - Err(e) - } - } -} diff --git a/heromodels/examples/calendar_rhai/calendar.rhai b/heromodels/examples/calendar_rhai/calendar.rhai deleted file mode 100644 index 08f79bc..0000000 --- a/heromodels/examples/calendar_rhai/calendar.rhai +++ /dev/null @@ -1,74 +0,0 @@ -// Get the database instance -let db = get_db(); - -// Create a new calendar using the constructor and builder methods -print("Creating a new calendar (ID will be DB-assigned) via registered constructor..."); -let calendar = new_calendar("My first calendar"). // ID removed - name("My First Calendar"). - description("A calendar for testing Rhai integration"); - -let event = new_event(). // ID removed - title("My First Event"). - description("An event for testing Rhai integration") - .add_attendee(new_attendee(1)); // new_attendee(contact_id), not Attendee ID - -// Add event's ID to calendar. event.id will be 0 if not saved separately. -// Calendar::add_event returns the modified calendar, so we re-assign. -calendar = calendar.add_event(event.id); - -print("Type of calendar object: " + type_of(calendar)); -print("Created calendar: " + calendar.name); - -// Save the calendar to the database and capture the result with DB-assigned ID -let calendar = set_calendar(db, calendar); // Capture result -print("Calendar saved to database"); - -// Check if calendar exists and retrieve it -if calendar_exists(db, calendar.id) { // Use calendar.id from the saved object - let retrieved_calendar = get_calendar_by_id(db, calendar.id); // Use calendar.id - print("Retrieved calendar ID: " + retrieved_calendar.id + ", Name: " + retrieved_calendar.name); - // Access the 'description' field directly. - // Note: 'description' is Option. Rhai handles options. - // You might want to check for 'is_some()' or 'is_none()' or use 'unwrap_or()' pattern if needed. - let desc = retrieved_calendar.description; - if desc != () && desc != "" { // Check against '()' for None and empty string - print("Description: " + desc); - } else { - print("No description available or it's None"); - } -} else { - print("Failed to retrieve calendar with ID " + calendar.id); -} - -// Create another calendar -print("Creating another new calendar (ID will be DB-assigned) using builder methods..."); -let calendar2 = new_calendar(). // ID removed - name("My Second Calendar"). - description("Another calendar for testing"); - -let calendar2 = set_calendar(db, calendar2); // Capture result -print("Second calendar saved with ID: " + calendar2.id); - -// Get all calendars -let all_calendars = get_all_calendars(db); -print("Total calendars: " + all_calendars.len()); - -for cal_item in all_calendars { // Renamed loop variable to avoid conflict if 'calendar' is still in scope - // Access 'base_data.id' and 'name' fields directly - print("Calendar ID: " + cal_item.base_data.id + ", Name: " + cal_item.name); -} - -// Delete a calendar -delete_calendar_by_id(db, calendar.id); // Use ID from the first calendar object -print("Attempted to delete calendar with ID " + calendar.id); - -// Verify deletion -if !calendar_exists(db, calendar.id) { // Use ID from the first calendar object - print("Calendar with ID " + calendar.id + " was successfully deleted"); -} else { - print("Failed to delete calendar with ID " + calendar.id + ", or it was not the one intended."); -} - -// Count remaining calendars -let remaining_calendars = get_all_calendars(db); -print("Remaining calendars: " + remaining_calendars.len()); \ No newline at end of file diff --git a/heromodels/examples/calendar_rhai/example.rs b/heromodels/examples/calendar_rhai/example.rs deleted file mode 100644 index dc7c05f..0000000 --- a/heromodels/examples/calendar_rhai/example.rs +++ /dev/null @@ -1,98 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::calendar::rhai::register_rhai_engine_functions; -use heromodels::models::calendar::{AttendanceStatus, Attendee, Calendar, Event}; -use rhai::Engine; -use rhai_wrapper::wrap_vec_return; -use std::sync::Arc; -use std::{fs, path::Path}; - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database with OurDB - let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database")); - - // Register the Calendar type with Rhai - // This function is generated by the #[rhai_model_export] attribute - Calendar::register_rhai_bindings_for_calendar(&mut engine, db.clone()); - register_rhai_engine_functions(&mut engine, db.clone()); - - // Register a function to get the database instance - engine.register_fn("get_db", move || db.clone()); - - // Register a calendar builder function - engine.register_fn("calendar__builder", |id: i64| { - let id_option = if id <= 0 { None } else { Some(id as u32) }; - Calendar::new(id_option, "New Calendar") - }); - - // Register setter methods for Calendar properties - engine.register_fn( - "set_description", - |calendar: &mut Calendar, desc: String| { - calendar.description = Some(desc); - }, - ); - - // Register getter methods for Calendar properties - engine.register_fn("get_description", |calendar: Calendar| -> String { - calendar.description.clone().unwrap_or_default() - }); - - // Register getter for base_data.id - engine.register_fn("get_id", |calendar: Calendar| -> i64 { - calendar.base_data.id as i64 - }); - - // Register additional functions needed by the script - engine.register_fn("set_calendar", |_db: Arc, _calendar: Calendar| { - // In a real implementation, this would save the calendar to the database - println!("Calendar saved: {}", _calendar.name); - }); - - engine.register_fn( - "get_calendar_by_id", - |_db: Arc, id: i64| -> Calendar { - // In a real implementation, this would retrieve the calendar from the database - Calendar::new(Some(id as u32), "Retrieved Calendar") - }, - ); - - // Register a function to check if a calendar exists - engine.register_fn("calendar_exists", |_db: Arc, id: i64| -> bool { - // In a real implementation, this would check if the calendar exists in the database - id == 1 || id == 2 - }); - - // Define the function separately to use with the wrap_vec_return macro - fn get_all_calendars(_db: Arc) -> Vec { - // In a real implementation, this would retrieve all calendars from the database - vec![ - Calendar::new(Some(1), "Calendar 1"), - Calendar::new(Some(2), "Calendar 2"), - ] - } - - // Register the function with the wrap_vec_return macro - engine.register_fn( - "get_all_calendars", - wrap_vec_return!(get_all_calendars, Arc => Calendar), - ); - - engine.register_fn("delete_calendar_by_id", |_db: Arc, _id: i64| { - // In a real implementation, this would delete the calendar from the database - println!("Calendar deleted with ID: {}", _id); - }); - - // Load and evaluate the Rhai script - let script_path = Path::new("examples/calendar_rhai/calendar.rhai"); - let script = fs::read_to_string(script_path)?; - - match engine.eval::<()>(&script) { - Ok(_) => println!("Script executed successfully!"), - Err(e) => eprintln!("Script execution failed: {}", e), - } - - Ok(()) -} diff --git a/heromodels/examples/finance_rhai/example.rs b/heromodels/examples/finance_rhai/example.rs deleted file mode 100644 index 10f8fde..0000000 --- a/heromodels/examples/finance_rhai/example.rs +++ /dev/null @@ -1,121 +0,0 @@ -use rhai::{Engine, EvalAltResult, Scope}; -use std::collections::HashMap; -use std::fs; -use std::sync::{Arc, Mutex}; - -// Import the models and the registration function -use heromodels::models::finance::account::Account; -use heromodels::models::finance::asset::Asset; -use heromodels::models::finance::marketplace::Listing; -use heromodels::models::finance::rhai::register_rhai_engine_functions; - -// Define a simple in-memory mock database for the example -#[derive(Clone, Debug)] -pub struct MockDb { - pub accounts: Arc>>, - pub assets: Arc>>, - pub listings: Arc>>, - // Bids are often part of Listings, so a separate HashMap for Bids might not be needed - // unless we want to query bids globally by a unique bid ID. -} - -impl MockDb { - fn new() -> Self { - Self { - accounts: Arc::new(Mutex::new(HashMap::new())), - assets: Arc::new(Mutex::new(HashMap::new())), - listings: Arc::new(Mutex::new(HashMap::new())), - } - } -} - -fn main() -> Result<(), Box> { - println!("--- Finance Rhai Example ---"); - - let mut engine = Engine::new(); - let mut scope = Scope::new(); - - let mock_db = Arc::new(MockDb::new()); - - // Register finance functions and types with the engine - register_rhai_engine_functions( - &mut engine, - Arc::clone(&mock_db.accounts), - Arc::clone(&mock_db.assets), - Arc::clone(&mock_db.listings), - ); - println!("Rhai functions registered."); - - scope.push("db_instance", mock_db.clone()); - - let script_path = "examples/finance_rhai/finance.rhai"; - println!("Loading script: {}", script_path); - let script = match fs::read_to_string(script_path) { - Ok(s) => s, - Err(e) => { - eprintln!("Error reading script file '{}': {}", script_path, e); - return Err(Box::new(EvalAltResult::ErrorSystem( - "Failed to read script".to_string(), - Box::new(e), - ))); - } - }; - - println!("Executing script..."); - match engine.run_with_scope(&mut scope, &script) { - Ok(_) => println!("Script executed successfully!"), - Err(e) => { - eprintln!("Script execution failed: {:?}", e); - return Err(e); - } - } - - // Print final state of Accounts - let final_accounts = mock_db.accounts.lock().unwrap(); - println!("\n--- Final Mock DB State (Accounts) ---"); - if final_accounts.is_empty() { - println!("No accounts in mock DB."); - } - for (id, account) in final_accounts.iter() { - println!( - "Account ID: {}, Name: '{}', User ID: {}, Assets: {}", - id, - account.name, - account.user_id, - account.assets.len() - ); - } - - // Print final state of Assets - let final_assets = mock_db.assets.lock().unwrap(); - println!("\n--- Final Mock DB State (Assets) ---"); - if final_assets.is_empty() { - println!("No assets in mock DB."); - } - for (id, asset) in final_assets.iter() { - println!( - "Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}", - id, asset.name, asset.amount, asset.asset_type - ); - } - - // Print final state of Listings - let final_listings = mock_db.listings.lock().unwrap(); - println!("\n--- Final Mock DB State (Listings) ---"); - if final_listings.is_empty() { - println!("No listings in mock DB."); - } - for (id, listing) in final_listings.iter() { - println!( - "Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}", - id, - listing.title, - listing.listing_type, - listing.status, - listing.price, - listing.bids.len() - ); - } - - Ok(()) -} diff --git a/heromodels/examples/finance_rhai/finance.rhai b/heromodels/examples/finance_rhai/finance.rhai deleted file mode 100644 index 076d9e7..0000000 --- a/heromodels/examples/finance_rhai/finance.rhai +++ /dev/null @@ -1,143 +0,0 @@ -// Finance Rhai Script Example - -print("--- Starting Finance Rhai Script ---"); - -// 1. Create an Account using the builder pattern -let user1_id = 1; // Assuming this user_id is a separate concept, e.g. from an auth system -let acc1 = new_account() - .set_name("User1 Main Account") - .set_user_id(user1_id) // user_id is i64 in Rhai, u32 in Rust. Conversion handled by setter. - .set_description("Primary account for User 1") - .set_ledger("LedgerX") - .set_address("0x123MainSt") - .set_pubkey("pubkeyUser1"); -print(`Created account (pre-save): ${acc1.name} with temp ID ${acc1.id}`); - -// 2. Save Account to Mock DB and get the version with DB-assigned ID -let acc1 = set_account(acc1); // Shadowing acc1 with the returned instance -print(`Account ${acc1.name} saved to DB with ID ${acc1.id}.`); - -// 3. Retrieve Account from Mock DB (using the new ID) -let fetched_acc1 = get_account_by_id(acc1.id); // Use the ID from the saved acc1 -print(`Fetched account from DB: ${fetched_acc1.name}, User ID: ${fetched_acc1.user_id}`); - -// 4. Create an Asset using the builder pattern -let asset1 = new_asset() - .set_name("HeroCoin") - .set_description("Utility token for Hero Platform") - .set_amount(1000.0) - .set_address("0xTokenContract") - .set_asset_type("Erc20") // Setter handles string to enum - .set_decimals(18); -print(`Created asset (pre-save): ${asset1.name} (temp ID: ${asset1.id}), Amount: ${asset1.amount}, Type: ${asset1.asset_type_str}`); - -// 5. Save Asset to Mock DB and get the version with DB-assigned ID -let asset1 = set_asset(asset1); // Shadowing asset1 -print(`Asset ${asset1.name} (ID: ${asset1.id}) saved to DB.`); - -// 6. Retrieve Asset from Mock DB (using the new ID) -let fetched_asset1 = get_asset_by_id(asset1.id); // Use the ID from the saved asset1 -print(`Fetched asset from DB: ${fetched_asset1.name}, Address: ${fetched_asset1.address}`); - -// 7. Add Asset to Account -// We have 'acc1' and 'asset1' from previous steps, both saved to DB and have their IDs. -print(`Attempting to add asset ${asset1.id} to account ${acc1.id}`); - -// Fetch the latest version of the account before modifying -// let mut acc1_for_update = get_account_by_id(acc1.get_id()); -// // Fetch the asset to add (or use fetched_asset1 if it's the correct one) -// let asset_to_add = get_asset_by_id(asset1.get_id()); -// -// try { -// acc1_for_update = acc1_for_update.add_asset(asset_to_add); // add_asset returns the modified account -// acc1_for_update = set_account(acc1_for_update); // Save the account with the new asset -// print(`Asset '${asset_to_add.name}' added to account '${acc1_for_update.name}'.`); -// print(`Account now has ${acc1_for_update.get_assets_cloned().len()} assets.`); -// // Verify the asset is there -// if (acc1_for_update.get_assets_cloned().len() > 0) { -// let first_asset_in_account = acc1_for_update.get_assets_cloned()[0]; -// print(`First asset in account: ${first_asset_in_account.name} (ID: ${first_asset_in_account.id})`); -// } -// } catch (err) { -// print(`Error adding asset to account: ${err}`); -// } - -// 8. Create a Listing for the Asset using the builder pattern -let current_timestamp = timestamp(); // Rhai's built-in for current unix timestamp (seconds) -let expires_at_ts = current_timestamp + (24 * 60 * 60 * 7); // Expires in 7 days - -let listing1 = new_listing() - .set_title("Rare HeroCoin Batch") - .set_description("100 HeroCoins for sale") - .set_asset_id(asset1.id.to_string()) // Use ID from the saved asset1 - .set_asset_type_str("Erc20") // asset_type as string - .set_seller_id(user1_id.to_string()) // seller_id as string (using the predefined user1_id) - .set_price(50.0) // price - .set_currency("USD") // currency - .set_listing_type("FixedPrice") // listing_type as string - .set_tags(["token", "herocoin", "sale"]); // tags as array of strings - // image_url is None by default from new_listing(), so no need to call set_image_url_opt for None - -print(`Created listing (pre-save): ${listing1.title} (temp ID: ${listing1.id}), Price: ${listing1.price} ${listing1.currency}`); -print(`Listing type: ${listing1.listing_type}, Status: ${listing1.status}`); -print(`Listing expires_at_ts_opt: ${listing1.expires_at_ts_opt}`); - -// 9. Save Listing to Mock DB and get the version with DB-assigned ID -let listing1 = set_listing(listing1); // Shadowing listing1 -print(`Listing ${listing1.get_title()} (ID: ${listing1.get_id()}) saved to DB.`); - -// 10. Retrieve Listing from Mock DB (using the new ID) -let fetched_listing1 = get_listing_by_id(listing1.get_id()); // Use the ID from the saved listing1 -print(`Fetched listing from DB: ${fetched_listing1.get_title()}, Seller ID: ${fetched_listing1.get_seller_id()}`); -print(`Fetched listing asset_id: ${fetched_listing1.get_asset_id()}, asset_type: ${fetched_listing1.get_asset_type_str()}`); - -// 11. Demonstrate an auction listing using the builder pattern -let auction_listing = new_listing() - .set_title("Vintage Hero Figurine") - .set_description("Rare collectible, starting bid low!") - .set_asset_id("asset_nft_123") // Mock asset ID for an NFT - this asset isn't created/saved in script - .set_asset_type("Erc721") - .set_seller_id(user1_id.to_string()) // Using the predefined user1_id - .set_price(10.0) // Starting price - .set_currency("USD") - .set_listing_type("Auction") - // expires_at_ts_opt is None by default - .set_tags(["collectible", "rare", "auction"]) - .set_image_url_opt("http://example.com/figurine.png"); - -// Save Auction Listing to Mock DB and get the version with DB-assigned ID -let auction_listing = set_listing(auction_listing); // Shadowing auction_listing -print(`Created auction listing: ${auction_listing.get_title()} (ID: ${auction_listing.get_id()})`); - -// 12. Create a Bid for the auction listing (Bid model not using builder pattern in this refactor) -let bid1 = new_bid(auction_listing.get_id().to_string(), 2, 12.0, "USD"); // User 2 bids 12 USD -print(`Created bid for listing ${bid1.listing_id} by bidder ${bid1.bidder_id} for ${bid1.amount} ${bid1.currency}`); -print(`Bid status: ${bid1.status_str}, Created at: ${bid1.created_at_ts}`); - -// 13. Add bid to listing -let auction_listing_for_bid = get_listing_by_id(auction_listing.get_id()); -// print(`Listing '${auction_listing_for_bid.get_title()}' fetched for bidding. Current price: ${auction_listing_for_bid.get_price()}, Bids: ${auction_listing_for_bid.get_bids_cloned().len()}`); - -try { - let updated_listing_after_bid = auction_listing_for_bid.add_listing_bid(bid1); - print(`Bid added to '${updated_listing_after_bid.get_title()}'. New bid count: ${updated_listing_after_bid.get_bids_cloned().len()}, New price: ${updated_listing_after_bid.get_price()};`); - set_listing(updated_listing_after_bid); // Save updated listing to DB - print("Auction listing with new bid saved to DB;"); -} catch (err) { - print(`Error adding bid: ${err}`); -} - -// 14. Try to complete sale for the fixed price listing -let listing_to_sell = get_listing_by_id(listing1.id); -let buyer_user_id = 3; -print(`Attempting to complete sale for listing: ${listing_to_sell.title} by buyer ${buyer_user_id}`); -try { - let sold_listing = listing_to_sell.complete_listing_sale(buyer_user_id.to_string(), listing_to_sell.price); - print(`Sale completed for listing ${sold_listing.id}. New status: ${sold_listing.status}`); - print(`Buyer ID: ${sold_listing.buyer_id_opt}, Sale Price: ${sold_listing.sale_price_opt}`); - set_listing(sold_listing); // Save updated listing -} catch (err) { - print(`Error completing sale: ${err}`); -} - -print("--- Finance Rhai Script Finished ---"); diff --git a/heromodels/examples/flow_rhai/example.rs b/heromodels/examples/flow_rhai/example.rs deleted file mode 100644 index 6c106d4..0000000 --- a/heromodels/examples/flow_rhai/example.rs +++ /dev/null @@ -1,41 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::flow::register_flow_rhai_module; -use rhai::Engine; -use std::sync::Arc; -use std::{fs, path::Path}; - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database with OurDB - // Using a temporary/in-memory database for the example - let db = Arc::new(OurDB::new("temp_flow_rhai_db", true).expect("Failed to create database")); - - // Register flow Rhai module functions - register_flow_rhai_module(&mut engine, db.clone()); - - // Load and evaluate the Rhai script - let script_path_str = "examples/flow_rhai/flow.rhai"; - let script_path = Path::new(script_path_str); - if !script_path.exists() { - eprintln!("Error: Rhai script not found at {}", script_path_str); - eprintln!( - "Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory." - ); - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Rhai script not found: {}", script_path_str), - ))); - } - - println!("Executing Rhai script: {}", script_path_str); - let script = fs::read_to_string(script_path)?; - - match engine.eval::<()>(&script) { - Ok(_) => println!("\nRhai script executed successfully!"), - Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e), - } - - Ok(()) -} diff --git a/heromodels/examples/flow_rhai/flow.rs b/heromodels/examples/flow_rhai/flow.rs deleted file mode 100644 index 8a66acf..0000000 --- a/heromodels/examples/flow_rhai/flow.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Hero Models - Flow Rhai Example -print("Hero Models - Flow Rhai Example"); -print("============================="); - -// Helper to format Option (Dynamic in Rhai: String or ()) for printing -fn format_optional(val, placeholder) { - if val == () { - placeholder - } else { - val - } -} - -// The database instance is now implicitly passed to DB functions. -print("DB instance will be implicitly passed."); - -// --- Test Flow Model --- -print("\n--- Testing Flow Model ---"); -// Create a new flow using the constructor and builder methods -print("Creating a new flow (ID: 1, UUID: flow-uuid-001)..."); -let flow1 = new_flow(1, "flow-uuid-001") - .name("Document Approval Workflow") - .status("Active"); - -print("Flow object created: " + flow1); -print("Flow ID: " + flow1.id); -print("Flow UUID: " + flow1.flow_uuid); -print("Flow Name: " + flow1.name); -print("Flow Status: " + flow1.status); - -// Save the flow to the database -set_flow(flow1); -print("Flow saved to database."); - -// Retrieve the flow -let retrieved_flow = get_flow_by_id(1); -print("Retrieved Flow by ID (1): " + retrieved_flow.name + ", Status: " + retrieved_flow.status); - -// --- Test FlowStep Model (as part of Flow) --- -print("\n--- Testing FlowStep Model (as part of Flow) ---"); -// Create FlowSteps -print("Creating flow steps and adding to flow..."); -let step1 = new_flow_step(101, 1) // id, step_order - .description("Initial Review by Manager") - .status("Pending"); - -let step2 = new_flow_step(102, 2) // id, step_order. Note: FlowStep ID 102 will be used for sig_req1 & sig_req2 - .description("Legal Team Sign-off") - .status("Pending"); - -// Add steps to the flow created earlier -flow1 = flow1.add_step(step1); -flow1 = flow1.add_step(step2); - -print("Flow now has " + flow1.steps.len() + " steps."); -print("First step description: " + format_optional(flow1.steps[0].description, "[No Description]")); - -// Re-save the flow with its steps -set_flow(flow1); -print("Flow with steps saved to database."); - -// Retrieve the flow and check its steps -let retrieved_flow_with_steps = get_flow_by_id(1); -print("Retrieved Flow by ID (1) has " + retrieved_flow_with_steps.steps.len() + " step(s)."); -if retrieved_flow_with_steps.steps.len() > 0 { - print("First step of retrieved flow: " + format_optional(retrieved_flow_with_steps.steps[0].description, "[No Description]")); -} - -// --- Test SignatureRequirement Model --- -print("\n--- Testing SignatureRequirement Model ---"); -// Create SignatureRequirements (referencing FlowStep ID 102, which is step2) -print("Creating signature requirements for step with ID 102..."); -let sig_req1 = new_signature_requirement(201, 102, "pubkey_legal_lead", "Legal Lead: Approve terms.") - .status("Required"); - -let sig_req2 = new_signature_requirement(202, 102, "pubkey_general_counsel", "General Counsel: Final Approval.") - .status("Required"); // signed_by and signature will default to None (Rust) / () (Rhai) - -print("SigReq 1: " + sig_req1.message + " for PubKey: " + sig_req1.public_key + " (Status: " + sig_req1.status + ")"); -if sig_req2.signed_by == () { - print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Not signed yet)"); -} else { - print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Signed by: " + format_optional(sig_req2.signed_by, "[Not Signed Yet]") + ")"); -} - - -// Save signature requirements -set_signature_requirement(sig_req1); -set_signature_requirement(sig_req2); -print("SignatureRequirements saved to database."); - -// Retrieve a signature requirement -let retrieved_sig_req = get_signature_requirement_by_id(201); -print("Retrieved SignatureRequirement by ID (201): " + retrieved_sig_req.message); - -// --- Test updating a SignatureRequirement --- -print("\n--- Testing Update for SignatureRequirement ---"); -let updated_sig_req = retrieved_sig_req - .status("Signed") - .signed_by("pubkey_legal_lead_actual_signer_id") - .signature("base64_encoded_signature_data_here"); - -print("Updated SigReq 1 - Status: " + updated_sig_req.status + ", Signed By: " + format_optional(updated_sig_req.signed_by, "[Not Signed Yet]") + ", Signature: " + format_optional(updated_sig_req.signature, "[No Signature]")); -set_signature_requirement(updated_sig_req); // Save updated -print("Updated SignatureRequirement saved."); - -print("\nFlow Rhai example script finished."); diff --git a/heromodels/examples/gov_example.rhai b/heromodels/examples/gov_example.rhai deleted file mode 100644 index 5187ea5..0000000 --- a/heromodels/examples/gov_example.rhai +++ /dev/null @@ -1,222 +0,0 @@ -// Governance Example - Demonstrates using the governance models with embedded types -// This example shows how to create, retrieve, and manipulate governance entities with embedded types - -// Get the database instance -let db = get_db(); -println("Hero Models - Governance Example"); -println("================================"); - -// Create a company with a business type using the fluent builder pattern -println("Creating a company..."); -// Create a business type directly -let business_type = #{ - type_name: "Corporation", - description: "A corporation is a legal entity that is separate and distinct from its owners" -}; - -let company = company__builder(1) - .name("Acme Corporation") - .registration_number("REG123456") - .incorporation_date(now()) - .fiscal_year_end("December 31") - .email("info@acme.com") - .phone("+1 555-123-4567") - .website("https://acme.com") - .address("123 Main St, Anytown, USA") - .business_type(business_type) - .industry("Technology") - .description("A leading technology company") - .status("Active") - .build(); - -// Save the company to the database -set_company(db, company); -println("Company created successfully!"); -println(""); - -// Create a committee with members using the fluent builder pattern -println("Creating a committee..."); -let committee = committee__builder(1) - .company_id(1) - .name("Board of Directors") - .description("The main governing body of the company") - .add_member( - committee_member() - .id(1) - .user_id(101) - .name("John Smith") - .role(CommitteeRole::Chair) - ) - .add_member( - committee_member() - .id(2) - .user_id(102) - .name("Jane Doe") - .role(CommitteeRole::Secretary) - ) - .add_member( - committee_member() - .id(3) - .user_id(103) - .name("Bob Johnson") - .role(CommitteeRole::Member) - ) - .build(); - -// Save the committee to the database -set_committee(db, committee); -println("Committee created successfully!"); -println(""); - -// Create a meeting with attendees using the fluent builder pattern -println("Creating a meeting..."); -let meeting = meeting__builder(1) - .company_id(1) - .title("Q2 Board Meeting") - .date(now() + days(7)) - .location("Conference Room A") - .description("Quarterly board meeting to review financial performance") - .add_attendee( - attendee() - .id(1) - .user_id(101) - .name("John Smith") - .role(AttendeeRole::Coordinator) - .status(AttendeeStatus::Confirmed) - ) - .add_attendee( - attendee() - .id(2) - .user_id(102) - .name("Jane Doe") - .role(AttendeeRole::Secretary) - .status(AttendeeStatus::Confirmed) - ) - .add_attendee( - attendee() - .id(3) - .user_id(103) - .name("Bob Johnson") - .role(AttendeeRole::Member) - .status(AttendeeStatus::Pending) - ) - .build(); - -// Save the meeting to the database -set_meeting(db, meeting); -println("Meeting created successfully!"); -println(""); - -// Create a resolution with approvals using the fluent builder pattern -println("Creating a resolution..."); -let resolution = resolution__builder(1) - .company_id(1) - .title("New Product Line Resolution") - .description("Resolution to approve the development of a new product line") - .resolution_type(ResolutionType::Ordinary) - .status(ResolutionStatus::Proposed) - .proposed_date(now()) - .effective_date(now() + days(30)) - .expiry_date(now() + days(365)) - .add_approval("John Smith") - .add_approval("Jane Doe") - .build(); - -// Save the resolution to the database -set_resolution(db, resolution); -println("Resolution created successfully!"); -println(""); - -// Create a vote with ballots using the fluent builder pattern -println("Creating a vote..."); -let vote = vote__builder(1) - .company_id(1) - .resolution_id(1) - .title("Vote on New Product Line") - .description("Vote to approve the development of a new product line") - .status(VoteStatus::Open) - .add_ballot( - ballot() - .id(1) - .user_id(101) - .user_name("John Smith") - .option(VoteOption::Yes) - .comments("Strongly support this initiative") - ) - .add_ballot( - ballot() - .id(2) - .user_id(102) - .user_name("Jane Doe") - .option(VoteOption::Yes) - .comments("Agree with the proposal") - ) - .add_ballot( - ballot() - .id(3) - .user_id(103) - .user_name("Bob Johnson") - .option(VoteOption::Abstain) - .comments("Need more information") - ) - .start_date(now()) - .end_date(now() + days(7)) - .build(); - -// Save the vote to the database -set_vote(db, vote); -println("Vote created successfully!"); -println(""); - -// Retrieve and display the company -println("Retrieving company..."); -let retrieved_company = get_company_by_id(db, 1); -println("Company: " + retrieved_company.name); -println("Business Type: " + retrieved_company.business_type.type_name); -println(""); - -// Retrieve and display the committee -println("Retrieving committee..."); -let retrieved_committee = get_committee_by_id(db, 1); -println("Committee: " + retrieved_committee.name); -println("Members: " + retrieved_committee.members.len()); -for member in retrieved_committee.members { - println("- " + member.name + " (" + member.role + ")"); -} -println(""); - -// Retrieve and display the meeting -println("Retrieving meeting..."); -let retrieved_meeting = get_meeting_by_id(db, 1); -println("Meeting: " + retrieved_meeting.title); -println("Date: " + retrieved_meeting.date); -println("Attendees: " + retrieved_meeting.attendees.len()); -for attendee in retrieved_meeting.attendees { - println("- " + attendee.name + " (" + attendee.role + ", " + attendee.status + ")"); -} -println(""); - -// Retrieve and display the resolution -println("Retrieving resolution..."); -let retrieved_resolution = get_resolution_by_id(db, 1); -println("Resolution: " + retrieved_resolution.title); -println("Status: " + retrieved_resolution.status); -println("Approvals: " + retrieved_resolution.approvals.len()); -for approval in retrieved_resolution.approvals { - println("- " + approval); -} -println(""); - -// Retrieve and display the vote -println("Retrieving vote..."); -let retrieved_vote = get_vote_by_id(db, 1); -println("Vote: " + retrieved_vote.title); -println("Status: " + retrieved_vote.status); -println("Ballots: " + retrieved_vote.ballots.len()); -for ballot in retrieved_vote.ballots { - println("- " + ballot.user_name + ": " + ballot.option); -} -println(""); - -println("Governance example completed successfully!"); -() diff --git a/heromodels/examples/gov_rhai_example.rs b/heromodels/examples/gov_rhai_example.rs deleted file mode 100644 index 0f09b78..0000000 --- a/heromodels/examples/gov_rhai_example.rs +++ /dev/null @@ -1,57 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::register_db_functions; -use rhai::Engine; -use std::sync::Arc; -use std::fs; -use std::path::Path; -use chrono::{DateTime, Utc, Duration}; - -fn main() -> Result<(), Box> { - // Create a temporary directory for the database - let temp_dir = std::env::temp_dir().join("heromodels_gov_example"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir)?; - } - fs::create_dir_all(&temp_dir)?; - - println!("Using temporary database at: {}", temp_dir.display()); - - // Create a new OurDB instance - let db = OurDB::new(&temp_dir, true)?; - let db = Arc::new(db); - - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Register the auto-generated DB functions with the engine - register_db_functions(&mut engine); - - // Register print functions - engine.register_fn("println", |s: &str| println!("{}", s)); - engine.register_fn("print", |s: &str| print!("{}", s)); - - // Register date/time functions - engine.register_fn("now", || Utc::now()); - engine.register_fn("days", |n: i64| Duration::days(n)); - engine.register_fn("+", |dt: DateTime, duration: Duration| dt + duration); - - // Add the DB instance to the engine's scope - engine.register_fn("get_db", move || -> Arc { db.clone() }); - - // Load and execute the Rhai script - let script_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("examples") - .join("gov_example.rhai"); - - println!("Loading Rhai script from: {}", script_path.display()); - - match engine.eval_file::<()>(script_path) { - Ok(_) => println!("Script executed successfully"), - Err(e) => eprintln!("Error executing script: {}", e), - } - - // Clean up the temporary directory - fs::remove_dir_all(&temp_dir)?; - - Ok(()) -} diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs deleted file mode 100644 index f6ee8fe..0000000 --- a/heromodels/examples/governance_rhai/example.rs +++ /dev/null @@ -1,290 +0,0 @@ -use chrono::{Duration, Utc}; -use heromodels::db::hero::OurDB; -use heromodels::models::governance::{ - Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, -}; -use rhai::Engine; -use rhai_wrapper::wrap_vec_return; -use std::sync::Arc; -use std::{fs, path::Path}; - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database - let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database")); - - // Register the Proposal type with Rhai - // This function is generated by the #[rhai_model_export] attribute - Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone()); - - // Register the Ballot type with Rhai - Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone()); - - // Register a function to get the database instance - engine.register_fn("get_db", move || db.clone()); - - // Register builder functions for Proposal and related types - engine.register_fn( - "create_proposal", - |id: i64, creator_id: String, creator_name: String, title: String, description: String| { - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - let id_option = if id <= 0 { None } else { Some(id as u32) }; - Proposal::new( - id_option, - creator_id, - creator_name, - title, - description, - ProposalStatus::Draft, - Utc::now(), - Utc::now(), - start_date, - end_date, - ) - }, - ); - - engine.register_fn("create_vote_option", |id: i64, text: String| { - VoteOption::new(id as u8, text, Some("This is an optional comment")) - }); - - engine.register_fn( - "create_ballot", - |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { - let id_option = if id <= 0 { None } else { Some(id as u32) }; - Ballot::new( - id_option, - user_id as u32, - vote_option_id as u8, - shares_count, - ) - }, - ); - - // Register getter and setter methods for Proposal properties - engine.register_fn("get_title", |proposal: Proposal| -> String { - proposal.title.clone() - }); - - engine.register_fn("get_description", |proposal: Proposal| -> String { - proposal.description.clone() - }); - - engine.register_fn("get_creator_id", |proposal: Proposal| -> String { - proposal.creator_id.clone() - }); - - engine.register_fn("get_id", |proposal: Proposal| -> i64 { - proposal.base_data.id as i64 - }); - - engine.register_fn("get_status", |proposal: Proposal| -> String { - format!("{:?}", proposal.status) - }); - - engine.register_fn("get_vote_status", |proposal: Proposal| -> String { - format!("{:?}", proposal.vote_status) - }); - - // Register methods for proposal operations - engine.register_fn( - "add_option_to_proposal", - |mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal { - proposal.add_option( - option_id as u8, - option_text, - Some("This is an optional comment".to_string()), - ) - }, - ); - - engine.register_fn( - "cast_vote_on_proposal", - |mut proposal: Proposal, - ballot_id: i64, - user_id: i64, - option_id: i64, - shares: i64| - -> Proposal { - let ballot_id_option = if ballot_id <= 0 { - None - } else { - Some(ballot_id as u32) - }; - proposal.cast_vote(ballot_id_option, user_id as u32, option_id as u8, shares) - }, - ); - - engine.register_fn( - "change_proposal_status", - |mut proposal: Proposal, status_str: String| -> Proposal { - let new_status = match status_str.as_str() { - "Draft" => ProposalStatus::Draft, - "Active" => ProposalStatus::Active, - "Approved" => ProposalStatus::Approved, - "Rejected" => ProposalStatus::Rejected, - "Cancelled" => ProposalStatus::Cancelled, - _ => ProposalStatus::Draft, - }; - proposal.change_proposal_status(new_status) - }, - ); - - engine.register_fn( - "change_vote_event_status", - |mut proposal: Proposal, status_str: String| -> Proposal { - let new_status = match status_str.as_str() { - "Open" => VoteEventStatus::Open, - "Closed" => VoteEventStatus::Closed, - "Cancelled" => VoteEventStatus::Cancelled, - _ => VoteEventStatus::Open, - }; - proposal.change_vote_event_status(new_status) - }, - ); - - // Register functions for database operations - engine.register_fn("save_proposal", |_db: Arc, proposal: Proposal| { - println!("Proposal saved: {}", proposal.title); - }); - - engine.register_fn( - "get_proposal_by_id", - |_db: Arc, id: i64| -> Proposal { - // In a real implementation, this would retrieve the proposal from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new( - Some(id as u32), - "Retrieved Creator", - "Retrieved Creator Name", - "Retrieved Proposal", - "Retrieved Description", - ProposalStatus::Draft, - Utc::now(), - Utc::now(), - start_date, - end_date, - ) - }, - ); - - // Register a function to check if a proposal exists - engine.register_fn("proposal_exists", |_db: Arc, id: i64| -> bool { - // In a real implementation, this would check if the proposal exists in the database - id == 1 || id == 2 - }); - - // Define the function for get_all_proposals - fn get_all_proposals(_db: Arc) -> Vec { - // In a real implementation, this would retrieve all proposals from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - vec![ - Proposal::new( - Some(1), - "Creator 1", - "Creator Name 1", - "Proposal 1", - "Description 1", - ProposalStatus::Draft, - Utc::now(), - Utc::now(), - start_date, - end_date, - ), - Proposal::new( - Some(2), - "Creator 2", - "Creator Name 2", - "Proposal 2", - "Description 2", - ProposalStatus::Draft, - Utc::now(), - Utc::now(), - start_date, - end_date, - ), - ] - } - - // Register the function with the wrap_vec_return macro - engine.register_fn( - "get_all_proposals", - wrap_vec_return!(get_all_proposals, Arc => Proposal), - ); - - engine.register_fn("delete_proposal_by_id", |_db: Arc, _id: i64| { - // In a real implementation, this would delete the proposal from the database - println!("Proposal deleted with ID: {}", _id); - }); - - // Register helper functions for accessing proposal options and ballots - engine.register_fn("get_option_count", |proposal: Proposal| -> i64 { - proposal.options.len() as i64 - }); - - engine.register_fn( - "get_option_at", - |proposal: Proposal, index: i64| -> VoteOption { - if index >= 0 && index < proposal.options.len() as i64 { - proposal.options[index as usize].clone() - } else { - VoteOption::new( - 0, - "Invalid Option", - Some("This is an invalid option".to_string()), - ) - } - }, - ); - - engine.register_fn("get_option_text", |option: VoteOption| -> String { - option.text.clone() - }); - - engine.register_fn("get_option_votes", |option: VoteOption| -> i64 { - option.count - }); - - engine.register_fn("get_ballot_count", |proposal: Proposal| -> i64 { - proposal.ballots.len() as i64 - }); - - engine.register_fn( - "get_ballot_at", - |proposal: Proposal, index: i64| -> Ballot { - if index >= 0 && index < proposal.ballots.len() as i64 { - proposal.ballots[index as usize].clone() - } else { - Ballot::new(None, 0, 0, 0) - } - }, - ); - - engine.register_fn("get_ballot_user_id", |ballot: Ballot| -> i64 { - ballot.user_id as i64 - }); - - engine.register_fn("get_ballot_option_id", |ballot: Ballot| -> i64 { - ballot.vote_option_id as i64 - }); - - engine.register_fn("get_ballot_shares", |ballot: Ballot| -> i64 { - ballot.shares_count - }); - - // Load and evaluate the Rhai script - let script_path = Path::new("examples/governance_rhai/governance.rhai"); - let script = fs::read_to_string(script_path)?; - - match engine.eval::<()>(&script) { - Ok(_) => println!("Script executed successfully!"), - Err(e) => eprintln!("Script execution failed: {}", e), - } - - Ok(()) -} diff --git a/heromodels/examples/governance_rhai/governance.rhai b/heromodels/examples/governance_rhai/governance.rhai deleted file mode 100644 index 4b7d1bb..0000000 --- a/heromodels/examples/governance_rhai/governance.rhai +++ /dev/null @@ -1,85 +0,0 @@ -// Get the database instance -let db = get_db(); - -// Create a new proposal with auto-generated ID (pass 0 for auto-generated ID) -let proposal = create_proposal(0, "user_creator_123", "Community Fund Allocation for Q3", - "Proposal to allocate funds for community projects in the third quarter."); - -print("Created Proposal: '" + get_title(proposal) + "' (ID: " + get_id(proposal) + ")"); -print("Status: " + get_status(proposal) + ", Vote Status: " + get_vote_status(proposal)); - -// Add vote options -let proposal_with_options = add_option_to_proposal(proposal, 1, "Approve Allocation"); -proposal_with_options = add_option_to_proposal(proposal_with_options, 2, "Reject Allocation"); -proposal_with_options = add_option_to_proposal(proposal_with_options, 3, "Abstain"); - -print("\nAdded Vote Options:"); -let option_count = get_option_count(proposal_with_options); -for i in range(0, option_count) { - let option = get_option_at(proposal_with_options, i); - print("- Option ID: " + i + ", Text: '" + get_option_text(option) + "', Votes: " + get_option_votes(option)); -} - -// Save the proposal to the database -save_proposal(db, proposal_with_options); -print("\nProposal saved to database"); - -// Simulate casting votes -print("\nSimulating Votes..."); -// User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID) -let proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100); -// User 2 votes for 'Reject Allocation' with 50 shares (with explicit ballot ID) -proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50); -// User 3 votes for 'Approve Allocation' with 75 shares (with auto-generated ballot ID) -proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 3, 1, 75); -// User 4 abstains with 20 shares (with auto-generated ballot ID) -proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 4, 3, 20); - -print("\nVote Counts After Simulation:"); -option_count = get_option_count(proposal_with_votes); -for i in range(0, option_count) { - let option = get_option_at(proposal_with_votes, i); - print("- Option ID: " + i + ", Text: '" + get_option_text(option) + "', Votes: " + get_option_votes(option)); -} - -print("\nBallots Cast:"); -let ballot_count = get_ballot_count(proposal_with_votes); -for i in range(0, ballot_count) { - let ballot = get_ballot_at(proposal_with_votes, i); - print("- Ballot ID: " + i + ", User ID: " + get_ballot_user_id(ballot) + - ", Option ID: " + get_ballot_option_id(ballot) + ", Shares: " + get_ballot_shares(ballot)); -} - -// Change proposal status -let active_proposal = change_proposal_status(proposal_with_votes, "Active"); -print("\nChanged Proposal Status to: " + get_status(active_proposal)); - -// Simulate closing the vote -let closed_proposal = change_vote_event_status(active_proposal, "Closed"); -print("Changed Vote Event Status to: " + get_vote_status(closed_proposal)); - -// Final proposal state -print("\nFinal Proposal State:"); -print("Title: '" + get_title(closed_proposal) + "'"); -print("Status: " + get_status(closed_proposal)); -print("Vote Status: " + get_vote_status(closed_proposal)); -print("Options:"); -option_count = get_option_count(closed_proposal); -for i in range(0, option_count) { - let option = get_option_at(closed_proposal, i); - print(" - " + i + ": " + get_option_text(option) + " (Votes: " + get_option_votes(option) + ")"); -} -print("Total Ballots: " + get_ballot_count(closed_proposal)); - -// Get all proposals from the database -let all_proposals = get_all_proposals(db); -print("\nTotal Proposals in Database: " + all_proposals.len()); -for proposal in all_proposals { - print("Proposal ID: " + get_id(proposal) + ", Title: '" + get_title(proposal) + "'"); -} - -// Delete a proposal -delete_proposal_by_id(db, 1); -print("\nDeleted proposal with ID 1"); - -print("\nGovernance Proposal Example Finished."); diff --git a/heromodels/examples/governance_rhai_client/example.rs b/heromodels/examples/governance_rhai_client/example.rs deleted file mode 100644 index d68e3ab..0000000 --- a/heromodels/examples/governance_rhai_client/example.rs +++ /dev/null @@ -1,457 +0,0 @@ -use chrono::{Duration, Utc}; -use heromodels::db::hero::OurDB; -use heromodels::models::governance::{ - Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, -}; -use rhai::Engine; -use rhai_client_macros::rhai; -use rhai_wrapper::wrap_vec_return; -use std::sync::Arc; - -// Define the functions we want to expose to Rhai -// We'll only use the #[rhai] attribute on functions with simple types - -// Create a proposal (returns a complex type, but takes simple parameters) -fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal { - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new( - id as u32, - creator_id, - title, - description, - start_date, - end_date, - ) -} - -// Getter functions for Proposal properties -fn get_title(proposal: &Proposal) -> String { - proposal.title.clone() -} - -fn get_description(proposal: &Proposal) -> String { - proposal.description.clone() -} - -fn get_creator_id(proposal: &Proposal) -> String { - proposal.creator_id.clone() -} - -fn get_id(proposal: &Proposal) -> i64 { - proposal.base_data.id as i64 -} - -fn get_status(proposal: &Proposal) -> String { - format!("{:?}", proposal.status) -} - -fn get_vote_status(proposal: &Proposal) -> String { - format!("{:?}", proposal.vote_status) -} - -// Functions that operate on Proposal objects -fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: String) -> Proposal { - proposal.add_option(option_id as u8, option_text) -} - -fn cast_vote_on_proposal( - proposal: Proposal, - ballot_id: i64, - user_id: i64, - option_id: i64, - shares: i64, -) -> Proposal { - proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares) -} - -fn change_proposal_status(proposal: Proposal, status_str: String) -> Proposal { - let new_status = match status_str.as_str() { - "Draft" => ProposalStatus::Draft, - "Active" => ProposalStatus::Active, - "Approved" => ProposalStatus::Approved, - "Rejected" => ProposalStatus::Rejected, - "Cancelled" => ProposalStatus::Cancelled, - _ => ProposalStatus::Draft, - }; - proposal.change_proposal_status(new_status) -} - -fn change_vote_event_status(proposal: Proposal, status_str: String) -> Proposal { - let new_status = match status_str.as_str() { - "Open" => VoteEventStatus::Open, - "Closed" => VoteEventStatus::Closed, - "Cancelled" => VoteEventStatus::Cancelled, - _ => VoteEventStatus::Open, - }; - proposal.change_vote_event_status(new_status) -} - -// Functions for accessing proposal options and ballots -fn get_option_count(proposal: &Proposal) -> i64 { - proposal.options.len() as i64 -} - -fn get_option_at(proposal: &Proposal, index: i64) -> VoteOption { - if index >= 0 && index < proposal.options.len() as i64 { - proposal.options[index as usize].clone() - } else { - VoteOption::new(0, "Invalid Option") - } -} - -fn get_option_text(option: &VoteOption) -> String { - option.text.clone() -} - -fn get_option_votes(option: &VoteOption) -> i64 { - option.count -} - -fn get_ballot_count(proposal: &Proposal) -> i64 { - proposal.ballots.len() as i64 -} - -fn get_ballot_at(proposal: &Proposal, index: i64) -> Ballot { - if index >= 0 && index < proposal.ballots.len() as i64 { - proposal.ballots[index as usize].clone() - } else { - Ballot::new(0, 0, 0, 0) - } -} - -fn get_ballot_user_id(ballot: &Ballot) -> i64 { - ballot.user_id as i64 -} - -fn get_ballot_option_id(ballot: &Ballot) -> i64 { - ballot.vote_option_id as i64 -} - -fn get_ballot_shares(ballot: &Ballot) -> i64 { - ballot.shares_count -} - -// Simple functions that we can use with the #[rhai] attribute -#[rhai] -fn create_proposal_wrapper( - id: i64, - creator_id: String, - title: String, - description: String, -) -> String { - let proposal = create_proposal(id, creator_id, title, description); - format!("Created proposal with ID: {}", proposal.base_data.id) -} - -#[rhai] -fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String { - let proposal = create_proposal( - id, - "user".to_string(), - "title".to_string(), - "description".to_string(), - ); - let updated = add_option_to_proposal(proposal, option_id, option_text.clone()); - format!("Added option '{}' to proposal {}", option_text, id) -} - -// Database operations -fn save_proposal(_db: Arc, proposal: Proposal) { - println!("Proposal saved: {}", proposal.title); -} - -fn get_all_proposals(_db: Arc) -> Vec { - // In a real implementation, this would retrieve all proposals from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - vec![ - Proposal::new( - 1, - "Creator 1", - "Proposal 1", - "Description 1", - start_date, - end_date, - ), - Proposal::new( - 2, - "Creator 2", - "Proposal 2", - "Description 2", - start_date, - end_date, - ), - ] -} - -fn delete_proposal_by_id(_db: Arc, id: i64) { - // In a real implementation, this would delete the proposal from the database - println!("Proposal deleted with ID: {}", id); -} - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database - let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database")); - - // Register the Proposal type with Rhai - // This function is generated by the #[rhai_model_export] attribute - Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone()); - - // Register the Ballot type with Rhai - Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone()); - - // Create a clone of db for use in the get_db function - let db_for_get_db = db.clone(); - - // Register a function to get the database instance - engine.register_fn("get_db", move || db_for_get_db.clone()); - - // Register builder functions for Proposal and related types - engine.register_fn( - "create_proposal", - |id: i64, creator_id: String, title: String, description: String| { - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new( - id as u32, - creator_id, - title, - description, - start_date, - end_date, - ) - }, - ); - - engine.register_fn("create_vote_option", |id: i64, text: String| { - VoteOption::new(id as u8, text) - }); - - engine.register_fn( - "create_ballot", - |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { - Ballot::new( - id as u32, - user_id as u32, - vote_option_id as u8, - shares_count, - ) - }, - ); - - // Register getter and setter methods for Proposal properties - engine.register_fn("get_title", get_title); - engine.register_fn("get_description", get_description); - engine.register_fn("get_creator_id", get_creator_id); - engine.register_fn("get_id", get_id); - engine.register_fn("get_status", get_status); - engine.register_fn("get_vote_status", get_vote_status); - - // Register methods for proposal operations - engine.register_fn("add_option_to_proposal", add_option_to_proposal); - engine.register_fn("cast_vote_on_proposal", cast_vote_on_proposal); - engine.register_fn("change_proposal_status", change_proposal_status); - engine.register_fn("change_vote_event_status", change_vote_event_status); - - // Register functions for database operations - engine.register_fn("save_proposal", save_proposal); - - engine.register_fn( - "get_proposal_by_id", - |_db: Arc, id: i64| -> Proposal { - // In a real implementation, this would retrieve the proposal from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new( - id as u32, - "Retrieved Creator", - "Retrieved Proposal", - "Retrieved Description", - start_date, - end_date, - ) - }, - ); - - // Register a function to check if a proposal exists - engine.register_fn("proposal_exists", |_db: Arc, id: i64| -> bool { - // In a real implementation, this would check if the proposal exists in the database - id == 1 || id == 2 - }); - - // Register the function with the wrap_vec_return macro - engine.register_fn( - "get_all_proposals", - wrap_vec_return!(get_all_proposals, Arc => Proposal), - ); - - engine.register_fn("delete_proposal_by_id", delete_proposal_by_id); - - // Register helper functions for accessing proposal options and ballots - engine.register_fn("get_option_count", get_option_count); - engine.register_fn("get_option_at", get_option_at); - engine.register_fn("get_option_text", get_option_text); - engine.register_fn("get_option_votes", get_option_votes); - engine.register_fn("get_ballot_count", get_ballot_count); - engine.register_fn("get_ballot_at", get_ballot_at); - engine.register_fn("get_ballot_user_id", get_ballot_user_id); - engine.register_fn("get_ballot_option_id", get_ballot_option_id); - engine.register_fn("get_ballot_shares", get_ballot_shares); - - // Register our wrapper functions that use the #[rhai] attribute - engine.register_fn("create_proposal_wrapper", create_proposal_wrapper); - engine.register_fn("add_option_wrapper", add_option_wrapper); - - // Now instead of loading and evaluating a Rhai script, we'll use direct function calls - // to implement the same functionality - - // Use the database instance - - // Create a new proposal - let proposal = create_proposal( - 1, - "user_creator_123".to_string(), - "Community Fund Allocation for Q3".to_string(), - "Proposal to allocate funds for community projects in the third quarter.".to_string(), - ); - - println!( - "Created Proposal: '{}' (ID: {})", - get_title(&proposal), - get_id(&proposal) - ); - println!( - "Status: {}, Vote Status: {}", - get_status(&proposal), - get_vote_status(&proposal) - ); - - // Add vote options - let mut proposal_with_options = - add_option_to_proposal(proposal, 1, "Approve Allocation".to_string()); - proposal_with_options = - add_option_to_proposal(proposal_with_options, 2, "Reject Allocation".to_string()); - proposal_with_options = add_option_to_proposal(proposal_with_options, 3, "Abstain".to_string()); - - println!("\nAdded Vote Options:"); - let option_count = get_option_count(&proposal_with_options); - for i in 0..option_count { - let option = get_option_at(&proposal_with_options, i); - println!( - "- Option ID: {}, Text: '{}', Votes: {}", - i, - get_option_text(&option), - get_option_votes(&option) - ); - } - - // Save the proposal to the database - save_proposal(db.clone(), proposal_with_options.clone()); - println!("\nProposal saved to database"); - - // Simulate casting votes - println!("\nSimulating Votes..."); - // User 1 votes for 'Approve Allocation' with 100 shares - let mut proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100); - // User 2 votes for 'Reject Allocation' with 50 shares - proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50); - // User 3 votes for 'Approve Allocation' with 75 shares - proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 103, 3, 1, 75); - // User 4 abstains with 20 shares - proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 104, 4, 3, 20); - - println!("\nVote Counts After Simulation:"); - let option_count = get_option_count(&proposal_with_votes); - for i in 0..option_count { - let option = get_option_at(&proposal_with_votes, i); - println!( - "- Option ID: {}, Text: '{}', Votes: {}", - i, - get_option_text(&option), - get_option_votes(&option) - ); - } - - println!("\nBallots Cast:"); - let ballot_count = get_ballot_count(&proposal_with_votes); - for i in 0..ballot_count { - let ballot = get_ballot_at(&proposal_with_votes, i); - println!( - "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", - i, - get_ballot_user_id(&ballot), - get_ballot_option_id(&ballot), - get_ballot_shares(&ballot) - ); - } - - // Change proposal status - let active_proposal = change_proposal_status(proposal_with_votes, "Active".to_string()); - println!( - "\nChanged Proposal Status to: {}", - get_status(&active_proposal) - ); - - // Simulate closing the vote - let closed_proposal = change_vote_event_status(active_proposal, "Closed".to_string()); - println!( - "Changed Vote Event Status to: {}", - get_vote_status(&closed_proposal) - ); - - // Final proposal state - println!("\nFinal Proposal State:"); - println!("Title: '{}'", get_title(&closed_proposal)); - println!("Status: {}", get_status(&closed_proposal)); - println!("Vote Status: {}", get_vote_status(&closed_proposal)); - println!("Options:"); - let option_count = get_option_count(&closed_proposal); - for i in 0..option_count { - let option = get_option_at(&closed_proposal, i); - println!( - " - {}: {} (Votes: {})", - i, - get_option_text(&option), - get_option_votes(&option) - ); - } - println!("Total Ballots: {}", get_ballot_count(&closed_proposal)); - - // Get all proposals from the database - let all_proposals = get_all_proposals(db.clone()); - println!("\nTotal Proposals in Database: {}", all_proposals.len()); - for proposal in all_proposals { - println!( - "Proposal ID: {}, Title: '{}'", - get_id(&proposal), - get_title(&proposal) - ); - } - - // Delete a proposal - delete_proposal_by_id(db.clone(), 1); - println!("\nDeleted proposal with ID 1"); - - // Demonstrate the use of Rhai client functions for simple types - println!("\nUsing Rhai client functions for simple types:"); - let create_result = create_proposal_wrapper_rhai_client( - &engine, - 2, - "rhai_user".to_string(), - "Rhai Proposal".to_string(), - "This proposal was created using a Rhai client function".to_string(), - ); - println!("{}", create_result); - - let add_option_result = - add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string()); - println!("{}", add_option_result); - - println!("\nGovernance Proposal Example Finished."); - - Ok(()) -} diff --git a/heromodels/examples/legal_rhai/example.rs b/heromodels/examples/legal_rhai/example.rs deleted file mode 100644 index 0b42f0d..0000000 --- a/heromodels/examples/legal_rhai/example.rs +++ /dev/null @@ -1,49 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::legal::register_legal_rhai_module; -use rhai::Engine; -use std::sync::Arc; -use std::{fs, path::Path}; - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database with OurDB - // Using a temporary/in-memory database for the example (creates files in current dir) - let db_name = "temp_legal_rhai_db"; - let db = Arc::new(OurDB::new(db_name, true).expect("Failed to create database")); - - // Register legal Rhai module functions - register_legal_rhai_module(&mut engine, db.clone()); - - // Load and evaluate the Rhai script - let script_path_str = "examples/legal_rhai/legal.rhai"; - let script_path = Path::new(script_path_str); - - if !script_path.exists() { - eprintln!("Error: Rhai script not found at {}", script_path_str); - eprintln!( - "Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory." - ); - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Rhai script not found: {}", script_path_str), - ))); - } - - println!("Executing Rhai script: {}", script_path_str); - let script = fs::read_to_string(script_path)?; - - match engine.eval::<()>(&script) { - Ok(_) => println!("\nRhai script executed successfully!"), - Err(e) => { - eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e); - // No explicit cleanup in this example, similar to flow_rhai_example - return Err(e.into()); // Propagate the Rhai error - } - } - - // No explicit cleanup in this example, similar to flow_rhai_example - - Ok(()) -} diff --git a/heromodels/examples/legal_rhai/legal.rhai b/heromodels/examples/legal_rhai/legal.rhai deleted file mode 100644 index 11e494a..0000000 --- a/heromodels/examples/legal_rhai/legal.rhai +++ /dev/null @@ -1,119 +0,0 @@ -// heromodels - Legal Module Rhai Example -print("Hero Models - Legal Rhai Example"); -print("==============================="); - -// Helper to format Option (Dynamic in Rhai: String or ()) for printing -fn format_optional_string(val, placeholder) { - if val == () { - placeholder - } else { - val - } -} - -// Helper to format Option (Dynamic in Rhai: i64 or ()) for printing -fn format_optional_int(val, placeholder) { - if val == () { - placeholder - } else { - "" + val // Convert int to string for concatenation - } -} - -print("DB instance will be implicitly passed to DB functions."); - -// --- Using Enum Constants --- -print(`\n--- Enum Constants ---`); -print(`ContractStatus Draft: ${ContractStatusConstants::Draft}`); -print(`ContractStatus Active: ${ContractStatusConstants::Active}`); -print(`SignerStatus Pending: ${SignerStatusConstants::Pending}`); -print(`SignerStatus Signed: ${SignerStatusConstants::Signed}`); - -// --- Test ContractSigner Model --- -print("\n--- Testing ContractSigner Model ---"); -let signer1_id = "signer-uuid-001"; -let signer1 = new_contract_signer(signer1_id, "Alice Wonderland", "alice@example.com") - .status(SignerStatusConstants::Pending) - .comments("Alice is the primary signatory."); - -print(`Signer 1 ID: ${signer1.id}, Name: ${signer1.name}, Email: ${signer1.email}`); -print(`Signer 1 Status: ${signer1.status}, Comments: ${format_optional_string(signer1.comments, "N/A")}`); -print(`Signer 1 Signed At: ${format_optional_int(signer1.signed_at, "Not signed")}`); - -let signer2_id = "signer-uuid-002"; -let signer2 = new_contract_signer(signer2_id, "Bob The Builder", "bob@example.com") - .status(SignerStatusConstants::Signed) - .signed_at(1678886400) // Example timestamp - .comments("Bob has already signed."); - -print(`Signer 2 ID: ${signer2.id}, Name: ${signer2.name}, Status: ${signer2.status}, Signed At: ${format_optional_int(signer2.signed_at, "N/A")}`); - -// --- Test ContractRevision Model --- -print("\n--- Testing ContractRevision Model ---"); -let revision1_version = 1; -let revision1 = new_contract_revision(revision1_version, "Initial draft content for the agreement, version 1.", 1678880000, "user-admin-01") - .comments("First version of the contract."); - -print(`Revision 1 Version: ${revision1.version}, Content: '${revision1.content}', Created At: ${revision1.created_at}, By: ${revision1.created_by}`); -print(`Revision 1 Comments: ${format_optional_string(revision1.comments, "N/A")}`); - -let revision2_version = 2; -let revision2 = new_contract_revision(revision2_version, "Updated content with new clauses, version 2.", 1678882200, "user-legal-02"); - -// --- Test Contract Model --- -print("\n--- Testing Contract Model ---"); -let contract1_base_id = 101; -let contract1_uuid = "contract-uuid-xyz-001"; - -print(`Creating a new contract (ID: ${contract1_base_id}, UUID: ${contract1_uuid})...`); -let contract1 = new_contract(contract1_base_id, contract1_uuid) - .title("Master Service Agreement") - .description("MSA between ACME Corp and Client Inc.") - .contract_type("Services") - .status(ContractStatusConstants::Draft) - .created_by("user-admin-01") - .terms_and_conditions("Standard terms and conditions apply. See Appendix A.") - .start_date(1678900000) - .current_version(revision1.version) - .add_signer(signer1) - .add_revision(revision1); - -print(`Contract 1 Title: ${contract1.title}, Status: ${contract1.status}`); -print(`Contract 1 Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}`); - -// Add more data -contract1 = contract1.add_signer(signer2).add_revision(revision2).current_version(revision2.version); -print(`Contract 1 Updated Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}, Current Version: ${contract1.current_version}`); - -// Save the contract to the database -print("Saving contract1 to database..."); -set_contract(contract1); -print("Contract1 saved."); - -// Retrieve the contract -print(`Retrieving contract by ID (${contract1_base_id})...`); -let retrieved_contract = get_contract_by_id(contract1_base_id); -print(`Retrieved Contract: ${retrieved_contract.title}, Status: ${retrieved_contract.status}`); -print(`Retrieved Contract Signers: ${retrieved_contract.signers.len()}, Revisions: ${retrieved_contract.revisions.len()}`); -if retrieved_contract.signers.len() > 0 { - print(`First signer of retrieved contract: ${retrieved_contract.signers[0].name}`); -} -if retrieved_contract.revisions.len() > 0 { - print(`First revision content of retrieved contract: '${retrieved_contract.revisions[0].content}'`); -} - -// --- Test updating a Contract --- -print("\n--- Testing Update for Contract ---"); -let updated_contract = retrieved_contract - .status(ContractStatusConstants::Active) - .end_date(1700000000) - .description("MSA (Active) between ACME Corp and Client Inc. with new addendum."); - -print(`Updated Contract - Title: ${updated_contract.title}, Status: ${updated_contract.status}, End Date: ${format_optional_int(updated_contract.end_date, "N/A")}`); -set_contract(updated_contract); // Save updated -print("Updated Contract saved."); - -let final_retrieved_contract = get_contract_by_id(contract1_base_id); -print(`Final Retrieved Contract - Status: ${final_retrieved_contract.status}, Description: '${final_retrieved_contract.description}'`); - -print("\nLegal Rhai example script finished."); diff --git a/heromodels/examples/library_rhai/example.rs b/heromodels/examples/library_rhai/example.rs deleted file mode 100644 index 13955f6..0000000 --- a/heromodels/examples/library_rhai/example.rs +++ /dev/null @@ -1,37 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::register_library_rhai_module; -use rhai::Engine; -use std::sync::Arc; -use std::{fs, path::Path}; - -fn main() -> Result<(), Box> { - // Initialize Rhai engine - let mut engine = Engine::new(); - - // Initialize database with OurDB - let db_path = "temp_library_db"; - // Clean up previous database file if it exists - if Path::new(db_path).exists() { - fs::remove_dir_all(db_path)?; - } - let db = Arc::new(OurDB::new(db_path, true).expect("Failed to create database")); - - // Register the library module with Rhai - register_library_rhai_module(&mut engine, db.clone()); - - // Load and evaluate the Rhai script - let script_path = Path::new(file!()).parent().unwrap().join("library.rhai"); - let script = fs::read_to_string(&script_path)?; - - println!("--- Running Library Rhai Script ---"); - match engine.eval::<()>(&script) { - Ok(_) => println!("\n--- Script executed successfully! ---"), - Err(e) => eprintln!("\n--- Script execution failed: {} ---", e), - } - - // Clean up the database file - fs::remove_dir_all(db_path)?; - println!("--- Cleaned up temporary database. ---"); - - Ok(()) -} diff --git a/heromodels/examples/library_rhai/library.rhai b/heromodels/examples/library_rhai/library.rhai deleted file mode 100644 index eb0a579..0000000 --- a/heromodels/examples/library_rhai/library.rhai +++ /dev/null @@ -1,78 +0,0 @@ -// heromodels/examples/library_rhai/library.rhai - -print("--- Testing Library Rhai Module ---"); - -// --- Image --- -print("\n1. Creating and saving an image..."); -let my_image = new_image() - .title("A Beautiful Sunset") - .description("A photo taken from a drone.") - .url("https://example.com/sunset.jpg") - .width(1920) - .height(1080); - -let saved_image = save_image(my_image); -print(" > Saved image with ID: " + saved_image.id); -let image_id = saved_image.id; - -// --- PDF --- -print("\n2. Creating and saving a PDF..."); -let my_pdf = new_pdf() - .title("Rust Programming Guide") - .description("A comprehensive guide to Rust.") - .url("https://example.com/rust.pdf") - .page_count(500); - -let saved_pdf = save_pdf(my_pdf); -print(" > Saved PDF with ID: " + saved_pdf.id); -let pdf_id = saved_pdf.id; - -// --- Markdown --- -print("\n3. Creating and saving a Markdown document..."); -let my_markdown = new_markdown() - .title("Meeting Notes") - .description("Notes from the weekly sync.") - .content("# Meeting Notes\n\n- Discussed project status.\n- Planned next sprint."); - -let saved_markdown = save_markdown(my_markdown); -print(" > Saved Markdown with ID: " + saved_markdown.id); -let markdown_id = saved_markdown.id; - -// --- Collection --- -print("\n4. Creating a collection and adding items..."); -let my_collection = new_collection() - .title("My Awesome Collection") - .description("A collection of various media.") - .add_image(image_id) - .add_pdf(pdf_id) - .add_markdown(markdown_id); - -let saved_collection = save_collection(my_collection); -print(" > Saved collection with ID: " + saved_collection.id); -let collection_id = saved_collection.id; - -// --- Verification --- -print("\n5. Verifying saved data..."); -let fetched_collection = get_collection(collection_id); -print(" > Fetched collection: '" + fetched_collection.title + "'"); -print(" > Collection contains " + fetched_collection.images + " image(s)."); -print(" > Collection contains " + fetched_collection.pdfs + " pdf(s)."); -print(" > Collection contains " + fetched_collection.markdowns + " markdown(s)."); - -let fetched_image = get_image(image_id); -print(" > Fetched image title: '" + fetched_image.title + "'"); -if (fetched_image.url != "https://example.com/sunset.jpg") { - throw "Image URL mismatch!"; -} -print(" > Image URL verified."); - -// --- Deletion --- -print("\n6. Cleaning up database..."); -delete_image(image_id); -print(" > Deleted image with ID: " + image_id); -delete_pdf(pdf_id); -print(" > Deleted PDF with ID: " + pdf_id); -delete_markdown(markdown_id); -print(" > Deleted Markdown with ID: " + markdown_id); -delete_collection(collection_id); -print(" > Deleted collection with ID: " + collection_id); \ No newline at end of file diff --git a/heromodels/examples/project_rhai/example.rs b/heromodels/examples/project_rhai/example.rs deleted file mode 100644 index d70bf0e..0000000 --- a/heromodels/examples/project_rhai/example.rs +++ /dev/null @@ -1,43 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::projects::register_projects_rhai_module; -use rhai::{Engine, EvalAltResult, Scope}; -use std::fs; -use std::sync::Arc; - -fn main() -> Result<(), Box> { - println!("Executing Rhai script: examples/project_rhai/project_test.rhai"); - - // Create a new Rhai engine - let mut engine = Engine::new(); - - // Create an Arc instance - let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB")); - - // Register the projects module with the engine - register_projects_rhai_module(&mut engine, Arc::clone(&db_instance)); - - // Read the Rhai script from file - let script_path = "examples/project_rhai/project_test.rhai"; - let script_content = fs::read_to_string(script_path).map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - format!("Cannot read script file: {}", script_path), - e.into(), - )) - })?; - - // Create a new scope - let mut scope = Scope::new(); - - // Execute the script - match engine.run_with_scope(&mut scope, &script_content) { - Ok(_) => { - println!("Rhai script executed successfully!"); - Ok(()) - } - Err(e) => { - println!("Rhai script execution failed: {}", e); - println!("Details: {:?}", e); - Err(e) - } - } -} diff --git a/heromodels/examples/project_rhai/project_test.rhai b/heromodels/examples/project_rhai/project_test.rhai deleted file mode 100644 index 757c778..0000000 --- a/heromodels/examples/project_rhai/project_test.rhai +++ /dev/null @@ -1,69 +0,0 @@ -// Test script for Project Rhai integration - -print("--- Testing Project Rhai Integration ---"); - -// Create a new project -let p1 = new_project() - .name("Project Alpha") - .description("This is the first test project.") - .owner_id(101) - .add_member_id(102) - .add_member_id(103) - .member_ids([201, 202, 203]) // Test setting multiple IDs - .add_tag("important") - .add_tag("rhai_test") - .tags(["core", "feature_test"]) // Test setting multiple tags - .status(Status::InProgress) - .priority(Priority::High) - .item_type(ItemType::Feature) - .add_base_comment(1001); - -print("Created project p1: " + p1); -print("p1.name: " + p1.name); -print("p1.description: " + p1.description); -print("p1.owner_id: " + p1.owner_id); -print("p1.member_ids: " + p1.member_ids); -print("p1.tags: " + p1.tags); -print(`p1.status: ${p1.status.to_string()}`); -print(`p1.priority: ${p1.priority.to_string()}`); -print(`p1.item_type: ${p1.item_type.to_string()}`); -print("p1.id: " + p1.id); -print("p1.created_at: " + p1.created_at); -print("p1.modified_at: " + p1.modified_at); -print("p1.comments: " + p1.comments); - -// Save to DB -try { - set_project(p1); - print("Project p1 saved successfully."); -} catch (err) { - print("Error saving project p1: " + err); -} - -// Retrieve from DB -try { - let retrieved_p1 = get_project_by_id(1); - if retrieved_p1 != () { // Check if Some(project) was returned (None becomes '()') - print("Retrieved project by ID 1: " + retrieved_p1); - print("Retrieved project name: " + retrieved_p1.name); - print("Retrieved project tags: " + retrieved_p1.tags); - } else { - print("Project with ID 1 not found."); - } -} catch (err) { - print("Error retrieving project by ID 1: " + err); -} - -// Test non-existent project -try { - let non_existent_project = get_project_by_id(999); - if non_existent_project != () { - print("Error: Found non-existent project 999: " + non_existent_project); - } else { - print("Correctly did not find project with ID 999."); - } -} catch (err) { - print("Error checking for non-existent project: " + err); -} - -print("--- Project Rhai Integration Test Complete ---"); diff --git a/heromodels/examples/project_rhai_wasm/.cargo/config.toml b/heromodels/examples/project_rhai_wasm/.cargo/config.toml deleted file mode 100644 index f603c8f..0000000 --- a/heromodels/examples/project_rhai_wasm/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[build] -# Set the default build target for this project -target = "wasm32-unknown-unknown" - -# Configuration for the wasm32-unknown-unknown target -[target.wasm32-unknown-unknown] -# Pass --cfg=wasm_js to rustc when compiling for this target. -# This is required by the getrandom crate. -rustflags = ["--cfg=wasm_js"] # For getrandom v0.3.x WASM support (required by rhai via ahash) diff --git a/heromodels/examples/project_rhai_wasm/Cargo.toml b/heromodels/examples/project_rhai_wasm/Cargo.toml deleted file mode 100644 index cf9b426..0000000 --- a/heromodels/examples/project_rhai_wasm/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "project_rhai_wasm_example" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -heromodels = { path = "../..", features = ["rhai"] } # Match heromodels main crate -wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["console"] } -console_error_panic_hook = "0.1.7" -js-sys = "0.3" -getrandom = { version = "0.3.3", features = ["js"] } # Align with rhai's dependency - -[profile.release] -# Tell `rustc` to optimize for small code size. -lto = true -opt-level = 's' diff --git a/heromodels/examples/project_rhai_wasm/index.html b/heromodels/examples/project_rhai_wasm/index.html deleted file mode 100644 index ec6ccdb..0000000 --- a/heromodels/examples/project_rhai_wasm/index.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - HeroModels Project Rhai WASM Test - - - -

HeroModels Project Rhai WASM Test

-

Open your browser's developer console to see detailed logs from the Rhai script.

- - - - - diff --git a/heromodels/examples/project_rhai_wasm/src/lib.rs b/heromodels/examples/project_rhai_wasm/src/lib.rs deleted file mode 100644 index 21aff17..0000000 --- a/heromodels/examples/project_rhai_wasm/src/lib.rs +++ /dev/null @@ -1,126 +0,0 @@ -use heromodels::db::{OurDB, Db}; // Import Db trait -use heromodels::models::projects::rhai::register_projects_rhai_module; -use rhai::{Engine, Scope, Dynamic, EvalAltResult, Position}; -use std::sync::Arc; -use wasm_bindgen::prelude::*; -use web_sys::console; - -// Called once when the WASM module is instantiated. -#[wasm_bindgen(start)] -pub fn main_wasm() -> Result<(), JsValue> { - // For better panic messages in the browser console - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - Ok(()) -} - -#[wasm_bindgen] -pub fn run_project_script_wasm() -> Result<(), JsValue> { - console::log_1(&"Starting Rhai script execution in WASM...".into()); - - let script = r#" -// Test script for Project Rhai integration - -print("--- Testing Project Rhai Integration (WASM) ---"); - -// Create a new project -let p1 = new_project() - .name("Project Alpha WASM") - .description("This is the first test project in WASM.") - .owner_id(101) - .add_member_id(102) - .add_member_id(103) - .member_ids([201, 202, 203]) - .add_tag("important") - .add_tag("rhai_test") - .add_tag("wasm") - .tags(["core", "feature_test", "wasm_run"]) - .status(Status::InProgress) - .priority(Priority::High) - .item_type(ItemType::Feature) - .add_base_comment(1001); - -print("Created project p1: " + p1); -print("p1.name: " + p1.name); -print("p1.description: " + p1.description); -print("p1.owner_id: " + p1.owner_id); -print("p1.member_ids: " + p1.member_ids); -print("p1.tags: " + p1.tags); -print(`p1.status: ${p1.status.to_string()}`); -print(`p1.priority: ${p1.priority.to_string()}`); -print(`p1.item_type: ${p1.item_type.to_string()}`); -print("p1.id: " + p1.id); -print("p1.created_at: " + p1.created_at); -print("p1.modified_at: " + p1.modified_at); -print("p1.comments: " + p1.comments); - -// Save to DB -try { - set_project(p1); - print("Project p1 saved successfully."); -} catch (err) { - print("Error saving project p1: " + err); -} - -// Retrieve from DB -try { - let retrieved_p1 = get_project_by_id(1); - if retrieved_p1 != () { // Check if Some(project) was returned (None becomes '()') - print("Retrieved project by ID 1: " + retrieved_p1); - print("Retrieved project name: " + retrieved_p1.name); - print("Retrieved project tags: " + retrieved_p1.tags); - } else { - print("Project with ID 1 not found."); - } -} catch (err) { - print("Error retrieving project by ID 1: " + err); -} - -// Test non-existent project -try { - let non_existent_project = get_project_by_id(999); - if non_existent_project != () { - print("Error: Found non-existent project 999: " + non_existent_project); - } else { - print("Correctly did not find project with ID 999."); - } -} catch (err) { - print("Error checking for non-existent project: " + err); -} - -print("--- Project Rhai Integration Test Complete (WASM) ---"); -"#; - - let mut engine = Engine::new(); - - // Redirect Rhai's print to browser console - engine.on_print(|text| { - console::log_1(&text.into()); - }); - - // Attempt to initialize OurDB. Sled's behavior in WASM with paths is experimental. - // It might work as an in-memory/temporary DB, or it might fail. - // Using a specific path and reset=true. - let db = match OurDB::new("/project_rhai_wasm_db", true) { - Ok(db_instance) => Arc::new(db_instance), - Err(e) => { - let err_msg = format!("Failed to initialize OurDB for WASM: {}", e); - console::error_1(&err_msg.into()); - return Err(JsValue::from_str(&err_msg)); - } - }; - - register_projects_rhai_module(&mut engine, db); - - let mut scope = Scope::new(); - - match engine.run_with_scope(&mut scope, script) { - Ok(_) => console::log_1(&"Rhai script executed successfully in WASM!".into()), - Err(e) => { - let err_msg = format!("Rhai script execution failed in WASM: {}\nDetails: {:?}", e, e.to_string()); - console::error_1(&err_msg.into()); - return Err(JsValue::from_str(&e.to_string())); - } - } - - Ok(()) -} diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs index 5aede48..f374269 100644 --- a/heromodels/src/models/access/access.rs +++ b/heromodels/src/models/access/access.rs @@ -1,5 +1,5 @@ use std::sync::Arc; - +use crate::models::Circle; use crate::db::{hero::OurDB, Collection, Db}; use heromodels_core::BaseModelData; use heromodels_derive::model; @@ -90,6 +90,17 @@ pub fn can_access_resource( object_id: u32, _object_type: &str, ) -> bool { + let circle = db + .collection::() + .expect("Failed to get Circle collection") + .get_all() + .unwrap()[0].clone(); + + // Circle members can access everything + if circle.members.contains(&public_key.to_string()) { + return true; + } + println!("Checking access for public key: {}", public_key); // get all access records for object @@ -112,3 +123,21 @@ pub fn can_access_resource( // if circle_pk is in access records true return access_records.iter().any(|record| record.circle_pk == public_key) } + +pub fn is_circle_member( + db: Arc, + public_key: &str, +) -> bool { + let circle = db + .collection::() + .expect("Failed to get Circle collection") + .get_all() + .unwrap()[0].clone(); + + // Circle members can access everything + if circle.members.contains(&public_key.to_string()) { + return true; + } + + return false; +} diff --git a/heromodels/src/models/access/mod.rs b/heromodels/src/models/access/mod.rs index 4ecbdc2..7e39043 100644 --- a/heromodels/src/models/access/mod.rs +++ b/heromodels/src/models/access/mod.rs @@ -1,7 +1,5 @@ // Export contact module pub mod access; -pub mod rhai; // Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs pub use self::access::Access; -pub use rhai::register_access_rhai_module; diff --git a/heromodels/src/models/access/rhai.rs b/heromodels/src/models/access/rhai.rs deleted file mode 100644 index fff6411..0000000 --- a/heromodels/src/models/access/rhai.rs +++ /dev/null @@ -1,238 +0,0 @@ -use crate::db::Db; -use rhai::plugin::*; -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::access::Access; -type RhaiAccess = Access; -use crate::db::Collection; -use crate::db::hero::OurDB; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_access_module { - // --- Access Functions --- - #[rhai_fn(name = "new_access", return_raw)] - pub fn new_access() -> Result> { - let access = Access::new(); - Ok(access) - } - - /// Sets the access name - #[rhai_fn(name = "object_id", return_raw, global, pure)] - pub fn access_object_id( - access: &mut RhaiAccess, - object_id: u32, - ) -> Result> { - // Create a default Access to replace the taken one - let default_access = Access::new(); - - // Take ownership of the access, apply the builder method, then put it back - let owned_access = std::mem::replace(access, default_access); - *access = owned_access.object_id(object_id); - Ok(access.clone()) - } - - #[rhai_fn(name = "circle_pk", return_raw, global, pure)] - pub fn access_circle_pk( - access: &mut RhaiAccess, - circle_pk: String, - ) -> Result> { - // Create a default Access to replace the taken one - let default_access = Access::new(); - - // Take ownership of the access, apply the builder method, then put it back - let owned_access = std::mem::replace(access, default_access); - *access = owned_access.circle_pk(circle_pk); - Ok(access.clone()) - } - - #[rhai_fn(name = "group_id", return_raw, global, pure)] - pub fn access_group_id( - access: &mut RhaiAccess, - group_id: u32, - ) -> Result> { - // Create a default Access to replace the taken one - let default_access = Access::new(); - - // Take ownership of the access, apply the builder method, then put it back - let owned_access = std::mem::replace(access, default_access); - *access = owned_access.group_id(group_id); - Ok(access.clone()) - } - - #[rhai_fn(name = "contact_id", return_raw, global, pure)] - pub fn access_contact_id( - access: &mut RhaiAccess, - contact_id: u32, - ) -> Result> { - // Create a default Access to replace the taken one - let default_access = Access::new(); - - // Take ownership of the access, apply the builder method, then put it back - let owned_access = std::mem::replace(access, default_access); - *access = owned_access.contact_id(contact_id); - Ok(access.clone()) - } - - #[rhai_fn(name = "expires_at", return_raw, global, pure)] - pub fn access_expires_at( - access: &mut RhaiAccess, - expires_at: Option, - ) -> Result> { - // Create a default Access to replace the taken one - let default_access = Access::new(); - - // Take ownership of the access, apply the builder method, then put it back - let owned_access = std::mem::replace(access, default_access); - *access = owned_access.expires_at(expires_at); - Ok(access.clone()) - } - - // Access Getters - #[rhai_fn(get = "id", pure)] - pub fn get_access_id(access: &mut RhaiAccess) -> i64 { - access.base_data.id as i64 - } - - #[rhai_fn(get = "object_id", pure)] - pub fn get_access_object_id(access: &mut RhaiAccess) -> i64 { - access.object_id as i64 - } - - #[rhai_fn(get = "circle_pk", pure)] - pub fn get_access_circle_pk(access: &mut RhaiAccess) -> String { - access.circle_pk.clone() - } - - #[rhai_fn(get = "group_id", pure)] - pub fn get_access_group_id(access: &mut RhaiAccess) -> i64 { - access.group_id as i64 - } - - #[rhai_fn(get = "contact_id", pure)] - pub fn get_access_contact_id(access: &mut RhaiAccess) -> i64 { - access.contact_id as i64 - } - - #[rhai_fn(get = "expires_at", pure)] - pub fn get_access_expires_at(access: &mut RhaiAccess) -> i64 { - access.expires_at.unwrap_or(0) as i64 - } - - #[rhai_fn(get = "created_at", pure)] - pub fn get_access_created_at(access: &mut RhaiAccess) -> i64 { - access.base_data.created_at - } - - #[rhai_fn(get = "modified_at", pure)] - pub fn get_access_modified_at(access: &mut RhaiAccess) -> i64 { - access.base_data.modified_at - } -} - -pub fn register_access_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_access_module); - engine.register_global_module(module.into()); - - // Create a module for database functions - let mut db_module = Module::new(); - - let db_clone_set_access = db.clone(); - db_module.set_native_fn( - "save_access", - move |access: Access| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_access.set(&access).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_access: {}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated access with the correct ID - Ok(result.1) - }, - ); - - // Manually register database functions as they need to capture 'db' - let db_clone_delete_access = db.clone(); - db_module.set_native_fn( - "delete_access", - move |access: Access| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_access - .collection::() - .expect("can open access collection") - .delete_by_id(access.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }, - ); - - let db_clone_get_access = db.clone(); - db_module.set_native_fn( - "get_access_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_access - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_access_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Access with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - // Add list_accesss function to get all accesss - let db_clone_list_accesss = db.clone(); - db_module.set_native_fn( - "list_accesss", - move || -> Result> { - let collection = db_clone_list_accesss.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get access collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let accesss = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all accesss: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for access in accesss { - array.push(Dynamic::from(access)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Register the database module globally - engine.register_global_module(db_module.into()); - - println!("Successfully registered access Rhai module using export_module approach."); -} diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index d5fe452..a43171d 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,7 +1,7 @@ use heromodels_core::BaseModelDataOps; use heromodels_core::{BaseModelData, Index}; use heromodels_derive::model; -use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] +use rhai::{CustomType, EvalAltResult, Position, TypeBuilder}; // For #[derive(CustomType)] use serde::{Deserialize, Serialize}; // --- Enums --- @@ -34,6 +34,26 @@ impl Default for BusinessType { } } +impl BusinessType { + pub fn to_string(&self) -> String { + format!("{:?}", self) + } + + pub fn from_string(s: &str) -> Result> { + match s.to_lowercase().as_str() { + "coop" => Ok(BusinessType::Coop), + "single" => Ok(BusinessType::Single), + "twin" => Ok(BusinessType::Twin), + "starter" => Ok(BusinessType::Starter), + "global" => Ok(BusinessType::Global), + _ => Err(Box::new(EvalAltResult::ErrorRuntime( + format!("Invalid business type: {}", s).into(), + Position::NONE, + ))), + } + } +} + // --- Company Struct --- #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, CustomType, Default)] // Added CustomType diff --git a/heromodels/src/models/biz/mod.rs b/heromodels/src/models/biz/mod.rs index abadf00..f81389c 100644 --- a/heromodels/src/models/biz/mod.rs +++ b/heromodels/src/models/biz/mod.rs @@ -14,11 +14,4 @@ pub use product::{Product, ProductComponent, ProductStatus, ProductType}; pub use shareholder::{Shareholder, ShareholderType}; pub mod sale; -pub use sale::{Sale, SaleItem, SaleStatus}; - -// pub use user::{User}; // Assuming a simple User model for now - -#[cfg(feature = "rhai")] -pub mod rhai; -#[cfg(feature = "rhai")] -pub use rhai::register_biz_rhai_module; +pub use sale::{Sale, SaleItem, SaleStatus}; \ No newline at end of file diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index c282918..5a498d9 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,4 +1,5 @@ use heromodels_core::BaseModelData; +use rhai::{CustomType, TypeBuilder}; use heromodels_derive::model; use serde::{Deserialize, Serialize}; @@ -19,7 +20,7 @@ pub enum ProductStatus { } // ProductComponent represents a component or sub-part of a product. -#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, CustomType)] pub struct ProductComponent { pub name: String, pub description: String, diff --git a/heromodels/src/models/biz/rhai.rs b/heromodels/src/models/biz/rhai.rs deleted file mode 100644 index 22ef6d8..0000000 --- a/heromodels/src/models/biz/rhai.rs +++ /dev/null @@ -1,794 +0,0 @@ -use crate::db::Collection; // For db.set and db.get_by_id -use crate::db::Db; -use crate::db::hero::OurDB; -use rhai::plugin::*; -use rhai::{Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::company::{BusinessType, Company, CompanyStatus}; -use crate::models::biz::product::{Product, ProductComponent, ProductStatus, ProductType}; -use crate::models::biz::sale::{Sale, SaleItem, SaleStatus}; -use crate::models::biz::shareholder::{Shareholder, ShareholderType}; -use heromodels_core::Model; - -type RhaiCompany = Company; -type RhaiShareholder = Shareholder; -type RhaiProduct = Product; -type RhaiProductComponent = ProductComponent; -type RhaiSale = Sale; -type RhaiSaleItem = SaleItem; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_biz_module { - // --- Company Functions --- - #[rhai_fn(name = "new_company")] - pub fn new_company() -> RhaiCompany { - Company::new() - } - - // Company builder methods - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn company_name( - company: &mut RhaiCompany, - name: String, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.name(name); - Ok(company.clone()) - } - - #[rhai_fn(name = "fiscal_year_end", return_raw, global, pure)] - pub fn company_fiscal_year_end( - company: &mut RhaiCompany, - fiscal_year_end: String, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.fiscal_year_end(fiscal_year_end); - Ok(company.clone()) - } - - #[rhai_fn(name = "registration_number", return_raw, global, pure)] - pub fn company_registration_number( - company: &mut RhaiCompany, - reg_num: String, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.registration_number(reg_num); - Ok(company.clone()) - } - - #[rhai_fn(name = "incorporation_date", return_raw, global, pure)] - pub fn company_incorporation_date( - company: &mut RhaiCompany, - date: i64, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.incorporation_date(date); - Ok(company.clone()) - } - - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn company_status( - company: &mut RhaiCompany, - status: CompanyStatus, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.status(status); - Ok(company.clone()) - } - - #[rhai_fn(name = "business_type", return_raw, global, pure)] - pub fn company_business_type( - company: &mut RhaiCompany, - business_type: BusinessType, - ) -> Result> { - let owned_company = mem::take(company); - *company = owned_company.business_type(business_type); - Ok(company.clone()) - } - - // Company getters - #[rhai_fn(name = "get_company_id")] - pub fn get_company_id(company: &mut RhaiCompany) -> i64 { - company.get_id() as i64 - } - - #[rhai_fn(name = "get_company_name")] - pub fn get_company_name(company: &mut RhaiCompany) -> String { - company.name.clone() - } - - #[rhai_fn(name = "get_company_created_at")] - pub fn get_company_created_at(company: &mut RhaiCompany) -> i64 { - company.base_data.created_at - } - - #[rhai_fn(name = "get_company_modified_at")] - pub fn get_company_modified_at(company: &mut RhaiCompany) -> i64 { - company.base_data.modified_at - } - - #[rhai_fn(name = "get_company_registration_number")] - pub fn get_company_registration_number(company: &mut RhaiCompany) -> String { - company.registration_number.clone() - } - - #[rhai_fn(name = "get_company_fiscal_year_end")] - pub fn get_company_fiscal_year_end(company: &mut RhaiCompany) -> String { - company.fiscal_year_end.clone() - } - - #[rhai_fn(name = "get_company_incorporation_date")] - pub fn get_company_incorporation_date(company: &mut RhaiCompany) -> i64 { - company.incorporation_date - } - - #[rhai_fn(name = "get_company_status")] - pub fn get_company_status(company: &mut RhaiCompany) -> CompanyStatus { - company.status.clone() - } - - #[rhai_fn(name = "get_company_business_type")] - pub fn get_company_business_type(company: &mut RhaiCompany) -> BusinessType { - company.business_type.clone() - } - - // --- Shareholder Functions --- - #[rhai_fn(name = "new_shareholder")] - pub fn new_shareholder() -> RhaiShareholder { - Shareholder::new() - } - - // Shareholder builder methods - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn shareholder_name( - shareholder: &mut RhaiShareholder, - name: String, - ) -> Result> { - let owned_shareholder = mem::take(shareholder); - *shareholder = owned_shareholder.name(name); - Ok(shareholder.clone()) - } - - #[rhai_fn(name = "company_id", return_raw, global, pure)] - pub fn shareholder_company_id( - shareholder: &mut RhaiShareholder, - company_id: i64, - ) -> Result> { - let company_id_u32 = id_from_i64_to_u32(company_id)?; - let owned_shareholder = mem::take(shareholder); - *shareholder = owned_shareholder.company_id(company_id_u32); - Ok(shareholder.clone()) - } - - #[rhai_fn(name = "share_count", return_raw, global, pure)] - pub fn shareholder_share_count( - shareholder: &mut RhaiShareholder, - share_count: f64, - ) -> Result> { - shareholder.shares = share_count; - Ok(shareholder.clone()) - } - - #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn shareholder_type( - shareholder: &mut RhaiShareholder, - type_: ShareholderType, - ) -> Result> { - let owned_shareholder = mem::take(shareholder); - *shareholder = owned_shareholder.type_(type_); - Ok(shareholder.clone()) - } - - // Shareholder getters - #[rhai_fn(name = "get_shareholder_id")] - pub fn get_shareholder_id(shareholder: &mut RhaiShareholder) -> i64 { - shareholder.get_id() as i64 - } - - #[rhai_fn(name = "get_shareholder_name")] - pub fn get_shareholder_name(shareholder: &mut RhaiShareholder) -> String { - shareholder.name.clone() - } - - #[rhai_fn(name = "get_shareholder_company_id")] - pub fn get_shareholder_company_id(shareholder: &mut RhaiShareholder) -> i64 { - shareholder.company_id as i64 - } - - #[rhai_fn(name = "get_shareholder_share_count")] - pub fn get_shareholder_share_count(shareholder: &mut RhaiShareholder) -> i64 { - shareholder.shares as i64 - } - - #[rhai_fn(name = "get_shareholder_type")] - pub fn get_shareholder_type(shareholder: &mut RhaiShareholder) -> ShareholderType { - shareholder.type_.clone() - } - - // --- ProductComponent Functions --- - #[rhai_fn(name = "new_product_component")] - pub fn new_product_component() -> RhaiProductComponent { - ProductComponent::new() - } - - // ProductComponent builder methods - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_component_name( - component: &mut RhaiProductComponent, - name: String, - ) -> Result> { - let owned_component = mem::take(component); - *component = owned_component.name(name); - Ok(component.clone()) - } - - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_component_description( - component: &mut RhaiProductComponent, - description: String, - ) -> Result> { - let owned_component = mem::take(component); - *component = owned_component.description(description); - Ok(component.clone()) - } - - #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn product_component_quantity( - component: &mut RhaiProductComponent, - quantity: i64, - ) -> Result> { - let owned_component = mem::take(component); - *component = owned_component.quantity(quantity as u32); - Ok(component.clone()) - } - - // ProductComponent getters - #[rhai_fn(name = "get_product_component_name")] - pub fn get_product_component_name(component: &mut RhaiProductComponent) -> String { - component.name.clone() - } - - #[rhai_fn(name = "get_product_component_description")] - pub fn get_product_component_description(component: &mut RhaiProductComponent) -> String { - component.description.clone() - } - - #[rhai_fn(name = "get_product_component_quantity")] - pub fn get_product_component_quantity(component: &mut RhaiProductComponent) -> i64 { - component.quantity as i64 - } - - // --- Product Functions --- - #[rhai_fn(name = "new_product")] - pub fn new_product() -> RhaiProduct { - Product::new() - } - - // Product builder methods - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn product_name( - product: &mut RhaiProduct, - name: String, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.name(name); - Ok(product.clone()) - } - - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn product_description( - product: &mut RhaiProduct, - description: String, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.description(description); - Ok(product.clone()) - } - - #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn product_price( - product: &mut RhaiProduct, - price: f64, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.price(price); - Ok(product.clone()) - } - - #[rhai_fn(name = "type_", return_raw, global, pure)] - pub fn product_type( - product: &mut RhaiProduct, - type_: ProductType, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.type_(type_); - Ok(product.clone()) - } - - #[rhai_fn(name = "category", return_raw, global, pure)] - pub fn product_category( - product: &mut RhaiProduct, - category: String, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.category(category); - Ok(product.clone()) - } - - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn product_status( - product: &mut RhaiProduct, - status: ProductStatus, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.status(status); - Ok(product.clone()) - } - - #[rhai_fn(name = "max_amount", return_raw, global, pure)] - pub fn product_max_amount( - product: &mut RhaiProduct, - max_amount: i64, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.max_amount(max_amount as u16); - Ok(product.clone()) - } - - #[rhai_fn(name = "purchase_till", return_raw, global, pure)] - pub fn product_purchase_till( - product: &mut RhaiProduct, - purchase_till: i64, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.purchase_till(purchase_till); - Ok(product.clone()) - } - - #[rhai_fn(name = "active_till", return_raw, global, pure)] - pub fn product_active_till( - product: &mut RhaiProduct, - active_till: i64, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.active_till(active_till); - Ok(product.clone()) - } - - #[rhai_fn(name = "add_component", return_raw, global, pure)] - pub fn product_add_component( - product: &mut RhaiProduct, - component: RhaiProductComponent, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.add_component(component); - Ok(product.clone()) - } - - #[rhai_fn(name = "components", return_raw, global, pure)] - pub fn product_components( - product: &mut RhaiProduct, - components: Vec, - ) -> Result> { - let owned_product = mem::take(product); - *product = owned_product.components(components); - Ok(product.clone()) - } - - // Product getters - #[rhai_fn(name = "get_product_id")] - pub fn get_product_id(product: &mut RhaiProduct) -> i64 { - product.get_id() as i64 - } - - #[rhai_fn(name = "get_product_name")] - pub fn get_product_name(product: &mut RhaiProduct) -> String { - product.name.clone() - } - - #[rhai_fn(name = "get_product_description")] - pub fn get_product_description(product: &mut RhaiProduct) -> String { - product.description.clone() - } - - #[rhai_fn(name = "get_product_price")] - pub fn get_product_price(product: &mut RhaiProduct) -> f64 { - product.price - } - - #[rhai_fn(name = "get_product_type")] - pub fn get_product_type(product: &mut RhaiProduct) -> ProductType { - product.type_.clone() - } - - #[rhai_fn(name = "get_product_category")] - pub fn get_product_category(product: &mut RhaiProduct) -> String { - product.category.clone() - } - - #[rhai_fn(name = "get_product_status")] - pub fn get_product_status(product: &mut RhaiProduct) -> ProductStatus { - product.status.clone() - } - - #[rhai_fn(name = "get_product_max_amount")] - pub fn get_product_max_amount(product: &mut RhaiProduct) -> i64 { - product.max_amount as i64 - } - - #[rhai_fn(name = "get_product_purchase_till")] - pub fn get_product_purchase_till(product: &mut RhaiProduct) -> i64 { - product.purchase_till - } - - #[rhai_fn(name = "get_product_active_till")] - pub fn get_product_active_till(product: &mut RhaiProduct) -> i64 { - product.active_till - } - - #[rhai_fn(name = "get_product_components")] - pub fn get_product_components(product: &mut RhaiProduct) -> Vec { - product.components.clone() - } - - #[rhai_fn(name = "get_product_created_at")] - pub fn get_product_created_at(product: &mut RhaiProduct) -> i64 { - product.base_data.created_at - } - - #[rhai_fn(name = "get_product_modified_at")] - pub fn get_product_modified_at(product: &mut RhaiProduct) -> i64 { - product.base_data.modified_at - } - - #[rhai_fn(name = "get_product_comments")] - pub fn get_product_comments(product: &mut RhaiProduct) -> Vec { - product - .base_data - .comments - .iter() - .map(|&id| id as i64) - .collect() - } - - // --- SaleItem Functions --- - #[rhai_fn(name = "new_sale_item")] - pub fn new_sale_item() -> RhaiSaleItem { - SaleItem::new() - } - - // SaleItem builder methods - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn sale_item_name( - item: &mut RhaiSaleItem, - name: String, - ) -> Result> { - let owned_item = mem::take(item); - *item = owned_item.name(name); - Ok(item.clone()) - } - - #[rhai_fn(name = "price", return_raw, global, pure)] - pub fn sale_item_price( - item: &mut RhaiSaleItem, - price: f64, - ) -> Result> { - item.unit_price = price; - Ok(item.clone()) - } - - #[rhai_fn(name = "quantity", return_raw, global, pure)] - pub fn sale_item_quantity( - item: &mut RhaiSaleItem, - quantity: i64, - ) -> Result> { - let owned_item = mem::take(item); - *item = owned_item.quantity(quantity.try_into().unwrap()); - Ok(item.clone()) - } - - #[rhai_fn(name = "product_id", return_raw, global, pure)] - pub fn sale_item_product_id( - item: &mut RhaiSaleItem, - product_id: i64, - ) -> Result> { - let product_id_u32 = id_from_i64_to_u32(product_id)?; - let owned_item = mem::take(item); - *item = owned_item.product_id(product_id_u32); - Ok(item.clone()) - } - - // SaleItem getters - #[rhai_fn(name = "get_sale_item_name")] - pub fn get_sale_item_name(item: &mut RhaiSaleItem) -> String { - item.name.clone() - } - - #[rhai_fn(name = "get_sale_item_price")] - pub fn get_sale_item_price(item: &mut RhaiSaleItem) -> f64 { - item.unit_price - } - - #[rhai_fn(name = "get_sale_item_quantity")] - pub fn get_sale_item_quantity(item: &mut RhaiSaleItem) -> i64 { - item.quantity as i64 - } - - #[rhai_fn(name = "get_sale_item_product_id")] - pub fn get_sale_item_product_id(item: &mut RhaiSaleItem) -> i64 { - item.product_id as i64 - } - - // --- Sale Functions --- - #[rhai_fn(name = "new_sale")] - pub fn new_sale() -> RhaiSale { - Sale::new() - } - - #[rhai_fn(name = "transaction_id", return_raw, global, pure)] - pub fn sale_transaction_id( - sale: &mut RhaiSale, - transaction_id: u32, - ) -> Result> { - sale.transaction_id = transaction_id; - Ok(sale.clone()) - } - - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn sale_status( - sale: &mut RhaiSale, - status: SaleStatus, - ) -> Result> { - let owned_sale = mem::take(sale); - *sale = owned_sale.status(status); - Ok(sale.clone()) - } - - #[rhai_fn(name = "add_item", return_raw, global, pure)] - pub fn sale_add_item( - sale: &mut RhaiSale, - item: RhaiSaleItem, - ) -> Result> { - let owned_sale = mem::take(sale); - *sale = owned_sale.add_item(item); - Ok(sale.clone()) - } - - #[rhai_fn(name = "items", return_raw, global, pure)] - pub fn sale_items( - sale: &mut RhaiSale, - items: Vec, - ) -> Result> { - let owned_sale = mem::take(sale); - *sale = owned_sale.items(items); - Ok(sale.clone()) - } - - // Sale getters - #[rhai_fn(name = "get_sale_id")] - pub fn get_sale_id(sale: &mut RhaiSale) -> i64 { - sale.get_id() as i64 - } - - #[rhai_fn(name = "get_sale_transaction_id")] - pub fn get_sale_transaction_id(sale: &mut RhaiSale) -> u32 { - sale.transaction_id - } - - #[rhai_fn(name = "get_sale_status")] - pub fn get_sale_status(sale: &mut RhaiSale) -> SaleStatus { - sale.status.clone() - } - - #[rhai_fn(name = "get_sale_items")] - pub fn get_sale_items(sale: &mut RhaiSale) -> Vec { - sale.items.clone() - } - - #[rhai_fn(name = "get_sale_created_at")] - pub fn get_sale_created_at(sale: &mut RhaiSale) -> i64 { - sale.base_data.created_at - } - - #[rhai_fn(name = "get_sale_modified_at")] - pub fn get_sale_modified_at(sale: &mut RhaiSale) -> i64 { - sale.base_data.modified_at - } - - #[rhai_fn(name = "get_sale_comments")] - pub fn get_sale_comments(sale: &mut RhaiSale) -> Vec { - sale.base_data - .comments - .iter() - .map(|&id| id as i64) - .collect() - } -} - -pub fn register_biz_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_biz_module); - engine.register_global_module(module.into()); - - // Create a new module for database operations - let mut db_module = Module::new(); - - // Database operations will obtain fresh collection handles directly. - - // Add database functions for Company - let db_for_set_company = Arc::clone(&db); - db_module.set_native_fn( - "set_company", - move |company: Company| -> Result> { - let company_collection_set = db_for_set_company - .collection::() - .expect("Failed to get company collection for set in closure"); - company_collection_set - .set(&company) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save company: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_for_get_company = Arc::clone(&db); - db_module.set_native_fn( - "get_company_by_id", - move |id: INT| -> Result> { - let company_collection_get = db_for_get_company - .collection::() - .expect("Failed to get company collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - company_collection_get - .get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get company with id {}: {:?}", id, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Add database functions for Shareholder - let db_for_set_shareholder = Arc::clone(&db); - db_module.set_native_fn( - "set_shareholder", - move |shareholder: Shareholder| -> Result> { - let shareholder_collection_set = db_for_set_shareholder - .collection::() - .expect("Failed to get shareholder collection for set in closure"); - shareholder_collection_set - .set(&shareholder) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save shareholder: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_for_get_shareholder = Arc::clone(&db); - db_module.set_native_fn( - "get_shareholder_by_id", - move |id: INT| -> Result> { - let shareholder_collection_get = db_for_get_shareholder - .collection::() - .expect("Failed to get shareholder collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - shareholder_collection_get - .get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get shareholder with id {}: {:?}", id, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Add database functions for Product - let db_for_set_product = Arc::clone(&db); - db_module.set_native_fn( - "set_product", - move |product: Product| -> Result> { - let product_collection_set = db_for_set_product - .collection::() - .expect("Failed to get product collection for set in closure"); - product_collection_set - .set(&product) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save product: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_for_get_product = Arc::clone(&db); - db_module.set_native_fn( - "get_product_by_id", - move |id: INT| -> Result> { - let product_collection_get = db_for_get_product - .collection::() - .expect("Failed to get product collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - product_collection_get - .get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get product with id {}: {:?}", id, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Add database functions for Sale - let db_for_set_sale = Arc::clone(&db); - db_module.set_native_fn( - "set_sale", - move |sale: Sale| -> Result> { - let sale_collection_set = db_for_set_sale - .collection::() - .expect("Failed to get sale collection for set in closure"); - sale_collection_set - .set(&sale) - .map(|(id_val, _)| id_val as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save sale: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_for_get_sale = Arc::clone(&db); - db_module.set_native_fn( - "get_sale_by_id", - move |id: INT| -> Result> { - let sale_collection_get = db_for_get_sale - .collection::() - .expect("Failed to get sale collection for get in closure"); - let id_u32 = id_from_i64_to_u32(id)?; - sale_collection_get - .get_by_id(id_u32) - .map(Dynamic::from) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get sale with id {}: {:?}", id, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Register the database module globally - engine.register_global_module(db_module.into()); - - println!("Successfully registered biz Rhai module using export_module approach."); -} diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index 938f58d..d7168cb 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -1,4 +1,5 @@ use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; /// Represents the status of a sale. @@ -16,7 +17,7 @@ impl Default for SaleStatus { } /// Represents an individual item within a Sale. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, CustomType)] pub struct SaleItem { pub product_id: u32, pub name: String, // Denormalized product name at time of sale @@ -72,7 +73,7 @@ impl SaleItem { } /// Represents a sale of products or services. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, CustomType)] pub struct Sale { pub base_data: BaseModelData, pub company_id: u32, diff --git a/heromodels/src/models/calendar/mod.rs b/heromodels/src/models/calendar/mod.rs index fc7beb4..5374dc2 100644 --- a/heromodels/src/models/calendar/mod.rs +++ b/heromodels/src/models/calendar/mod.rs @@ -1,7 +1,5 @@ // Export calendar module pub mod calendar; -pub mod rhai; // Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event}; -pub use rhai::register_calendar_rhai_module; diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs deleted file mode 100644 index a8e6af7..0000000 --- a/heromodels/src/models/calendar/rhai.rs +++ /dev/null @@ -1,484 +0,0 @@ -use crate::db::Db; -use rhai::plugin::*; -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::calendar::{AttendanceStatus, Attendee, Calendar, Event}; -type RhaiEvent = Event; -type RhaiAttendee = Attendee; -type RhaiCalendar = Calendar; -use crate::db::Collection; -use crate::db::hero::OurDB; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_calendar_module { - // --- Event Functions --- - #[rhai_fn(name = "new_event")] - pub fn new_event() -> RhaiEvent { - Event::new() - } - - /// Sets the event title - #[rhai_fn(name = "title", return_raw, global, pure)] - pub fn event_title( - event: &mut RhaiEvent, - title: String, - ) -> Result> { - let owned_event = mem::take(event); - *event = owned_event.title(title); - Ok(event.clone()) - } - - /// Sets the event description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn event_description( - event: &mut RhaiEvent, - description: String, - ) -> Result> { - let owned_event = mem::take(event); - *event = owned_event.description(description); - Ok(event.clone()) - } - - /// Sets the event location - #[rhai_fn(name = "location", return_raw, global, pure)] - pub fn event_location( - event: &mut RhaiEvent, - location: String, - ) -> Result> { - let owned_event = mem::take(event); - *event = owned_event.location(location); - Ok(event.clone()) - } - - /// Adds an attendee to the event - #[rhai_fn(name = "add_attendee", return_raw, global, pure)] - pub fn event_add_attendee( - event: &mut RhaiEvent, - attendee: RhaiAttendee, - ) -> Result> { - // Use take to get ownership of the event - let owned_event = mem::take(event); - *event = owned_event.add_attendee(attendee); - Ok(event.clone()) - } - - /// Reschedules the event with new start and end times - #[rhai_fn(name = "reschedule", return_raw, global, pure)] - pub fn event_reschedule( - event: &mut RhaiEvent, - new_start_time: i64, - new_end_time: i64, - ) -> Result> { - // Validate timestamps - if new_end_time <= new_start_time { - return Err(Box::new(EvalAltResult::ErrorRuntime( - "End time must be after start time".into(), - Position::NONE, - ))); - } - - // Use take to get ownership of the event - let owned_event = mem::take(event); - *event = owned_event.reschedule(new_start_time, new_end_time); - Ok(event.clone()) - } - - /// Updates an attendee's status in the event - #[rhai_fn(name = "update_attendee_status", return_raw, global, pure)] - pub fn event_update_attendee_status( - event: &mut RhaiEvent, - contact_id: i64, - status_str: String, - ) -> Result> { - let status_enum = AttendanceStatus::from_string(&status_str) - .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid attendance status: '{}'. Expected one of: Pending, Accepted, Declined, Tentative", status_str).into(), - Position::NONE - )))?; - - // Use take to get ownership of the event - let owned_event = mem::take(event); - *event = owned_event.update_attendee_status(id_from_i64_to_u32(contact_id)?, status_enum); - Ok(event.clone()) - } - - // Event Getters - #[rhai_fn(get = "id", pure)] - pub fn get_event_id(event: &mut RhaiEvent) -> i64 { - event.base_data.id as i64 - } - #[rhai_fn(get = "created_at", pure)] - pub fn get_event_created_at(event: &mut RhaiEvent) -> i64 { - event.base_data.created_at - } - #[rhai_fn(get = "modified_at", pure)] - pub fn get_event_modified_at(event: &mut RhaiEvent) -> i64 { - event.base_data.modified_at - } - - #[rhai_fn(get = "title", pure)] - pub fn get_event_title(event: &mut RhaiEvent) -> String { - event.title.clone() - } - #[rhai_fn(get = "description", pure)] - pub fn get_event_description(event: &mut RhaiEvent) -> Option { - event.description.clone() - } - #[rhai_fn(get = "start_time", pure)] - pub fn get_event_start_time(event: &mut RhaiEvent) -> i64 { - event.start_time - } - #[rhai_fn(get = "end_time", pure)] - pub fn get_event_end_time(event: &mut RhaiEvent) -> i64 { - event.end_time - } - #[rhai_fn(get = "location", pure)] - pub fn get_event_location(event: &mut RhaiEvent) -> Option { - event.location.clone() - } - #[rhai_fn(get = "attendees", pure)] - pub fn get_event_attendees(event: &mut RhaiEvent) -> Vec { - event.attendees.clone() - } - - // --- Attendee Functions --- - #[rhai_fn(name = "new_attendee")] - pub fn new_attendee() -> RhaiAttendee { - Attendee::new(0) // Default contact_id, will be set via builder - } - - /// Sets the contact ID for an attendee - #[rhai_fn(name = "with_contact_id", return_raw, global, pure)] - pub fn attendee_with_contact_id( - attendee: &mut RhaiAttendee, - contact_id: i64, - ) -> Result> { - let new_contact_id = id_from_i64_to_u32(contact_id).unwrap_or(0); - let owned_attendee = mem::replace(attendee, Attendee::new(0)); - *attendee = Attendee::new(new_contact_id); - attendee.status = owned_attendee.status; - Ok(attendee.clone()) - } - - /// Sets the status for an attendee - #[rhai_fn(name = "with_status", return_raw, global, pure)] - pub fn attendee_with_status( - attendee: &mut RhaiAttendee, - status_str: String, - ) -> Result> { - let status_enum = AttendanceStatus::from_string(&status_str) - .map_err(|_| Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid attendance status: '{}'. Expected one of: Accepted, Declined, Tentative, NoResponse", status_str).into(), - Position::NONE - )))?; - - let owned_attendee = mem::replace(attendee, Attendee::new(0)); - *attendee = owned_attendee.status(status_enum); - Ok(attendee.clone()) - } - - // We now use with_status instead of update_attendee_status for consistency - - // Attendee Getters - #[rhai_fn(get = "contact_id", pure)] - pub fn get_attendee_contact_id(attendee: &mut RhaiAttendee) -> i64 { - attendee.contact_id as i64 - } - #[rhai_fn(get = "status", pure)] - pub fn get_attendee_status(attendee: &mut RhaiAttendee) -> String { - attendee.status.to_string() - } - - // --- Calendar Functions --- - #[rhai_fn(name = "new_calendar", return_raw)] - pub fn new_calendar() -> Result> { - let calendar = Calendar::new(None, ""); - Ok(calendar) - } - - /// Sets the calendar name - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn calendar_name( - calendar: &mut RhaiCalendar, - name: String, - ) -> Result> { - // Create a default Calendar to replace the taken one - let default_calendar = Calendar::new(None, ""); - - // Take ownership of the calendar, apply the builder method, then put it back - let owned_calendar = std::mem::replace(calendar, default_calendar); - *calendar = Calendar::new(Some(owned_calendar.base_data.id), name); - Ok(calendar.clone()) - } - - /// Sets the calendar description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn calendar_description( - calendar: &mut RhaiCalendar, - description: String, - ) -> Result> { - // Create a default Calendar to replace the taken one - let default_calendar = Calendar::new(None, ""); - - // Take ownership of the calendar, apply the builder method, then put it back - let owned_calendar = std::mem::replace(calendar, default_calendar); - let updated_calendar = owned_calendar.description(description); - *calendar = updated_calendar.clone(); - Ok(updated_calendar) - } - - #[rhai_fn(name = "add_event_to_calendar", return_raw, global, pure)] - pub fn calendar_add_event( - calendar: &mut RhaiCalendar, - event: RhaiEvent, - ) -> Result> { - // Create a default Calendar to replace the taken one - let default_calendar = Calendar::new(None, ""); - - // Take ownership of the calendar, apply the builder method, then put it back - let owned_calendar = std::mem::replace(calendar, default_calendar); - *calendar = owned_calendar.add_event(event.base_data.id as i64); - Ok(calendar.clone()) - } - - #[rhai_fn(name = "remove_event_from_calendar", return_raw)] - pub fn calendar_remove_event( - calendar: &mut RhaiCalendar, - event_id: i64, - ) -> Result<(), Box> { - // Create a default Calendar to replace the taken one - let default_calendar = Calendar::new(None, ""); - - // Take ownership of the calendar, apply the builder method, then put it back - let owned_calendar = std::mem::replace(calendar, default_calendar); - *calendar = owned_calendar.remove_event(id_from_i64_to_u32(event_id)? as i64); - Ok(()) - } - - // Calendar Getters - #[rhai_fn(get = "id", pure)] - pub fn get_calendar_id(calendar: &mut RhaiCalendar) -> i64 { - calendar.base_data.id as i64 - } - - #[rhai_fn(get = "name", pure)] - pub fn get_calendar_name(calendar: &mut RhaiCalendar) -> String { - calendar.name.clone() - } - - #[rhai_fn(get = "created_at", pure)] - pub fn get_calendar_created_at(calendar: &mut RhaiCalendar) -> i64 { - calendar.base_data.created_at - } - - #[rhai_fn(get = "modified_at", pure)] - pub fn get_calendar_modified_at(calendar: &mut RhaiCalendar) -> i64 { - calendar.base_data.modified_at - } - - #[rhai_fn(get = "events", pure)] - pub fn get_calendar_events(calendar: &mut RhaiCalendar) -> Vec { - calendar.events.clone() - } - - #[rhai_fn(get = "description", pure)] - pub fn get_calendar_description(calendar: &mut RhaiCalendar) -> Option { - calendar.description.clone() - } - - // Calendar doesn't have an owner_id field in the current implementation - // pub fn get_calendar_owner_id(calendar: &mut RhaiCalendar) -> i64 { calendar.owner_id as i64 } -} - -pub fn register_calendar_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_calendar_module); - engine.register_global_module(module.into()); - - // Create a module for database functions - let mut db_module = Module::new(); - - // Manually register database functions as they need to capture 'db' - let db_clone_set_event = db.clone(); - db_module.set_native_fn( - "save_event", - move |event: Event| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_event.set(&event).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_event: {}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated event with the correct ID - Ok(result.1) - }, - ); - - // Manually register database functions as they need to capture 'db' - let db_clone_delete_event = db.clone(); - db_module.set_native_fn( - "delete_event", - move |event: Event| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_event - .collection::() - .expect("can open event collection") - .delete_by_id(event.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }, - ); - - let db_clone_get_event = db.clone(); - db_module.set_native_fn( - "get_event_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_event - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_event_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Event with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone_set_calendar = db.clone(); - db_module.set_native_fn( - "save_calendar", - move |calendar: Calendar| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_calendar.set(&calendar).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_calendar: {}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated calendar with the correct ID - Ok(result.1) - }, - ); - - // Manually register database functions as they need to capture 'db' - let db_clone_delete_calendar = db.clone(); - db_module.set_native_fn( - "delete_calendar", - move |calendar: Calendar| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_calendar - .collection::() - .expect("can open calendar collection") - .delete_by_id(calendar.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }, - ); - - let db_clone_get_calendar = db.clone(); - db_module.set_native_fn( - "get_calendar_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_calendar - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_calendar_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Calendar with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - // Add list_calendars function to get all calendars - let db_clone_list_calendars = db.clone(); - db_module.set_native_fn( - "list_calendars", - move || -> Result> { - let collection = db_clone_list_calendars - .collection::() - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get calendar collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let calendars = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all calendars: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for calendar in calendars { - array.push(Dynamic::from(calendar)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Add list_events function to get all events - let db_clone_list_events = db.clone(); - db_module.set_native_fn( - "list_events", - move || -> Result> { - let collection = db_clone_list_events.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get event collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let events = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all events: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for event in events { - array.push(Dynamic::from(event)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Register the database module globally - engine.register_global_module(db_module.into()); - - println!("Successfully registered calendar Rhai module using export_module approach."); -} diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs index b9a0696..d5e996d 100644 --- a/heromodels/src/models/circle/circle.rs +++ b/heromodels/src/models/circle/circle.rs @@ -4,7 +4,7 @@ use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; /// Represents the visual theme for a circle. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct ThemeData { pub primary_color: String, pub background_color: String, @@ -15,20 +15,6 @@ pub struct ThemeData { pub nav_timeline_visible: bool, } -impl Default for ThemeData { - fn default() -> Self { - Self { - primary_color: "#3b82f6".to_string(), - background_color: "#0a0a0a".to_string(), - background_pattern: "none".to_string(), - logo_symbol: "â—¯".to_string(), - logo_url: "".to_string(), - nav_dashboard_visible: true, - nav_timeline_visible: true, - } - } -} - /// Represents an event in a calendar #[model] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] diff --git a/heromodels/src/models/contact/contact.rs b/heromodels/src/models/contact/contact.rs index 1b9da79..823276f 100644 --- a/heromodels/src/models/contact/contact.rs +++ b/heromodels/src/models/contact/contact.rs @@ -21,6 +21,21 @@ pub struct Contact { pub circle: String, } +impl Default for Contact { + fn default() -> Self { + Contact { + base_data: BaseModelData::new(), + name: String::new(), + description: None, + address: String::new(), + phone: String::new(), + email: String::new(), + notes: None, + circle: String::new(), + } + } +} + impl Contact { pub fn new() -> Self { Contact { diff --git a/heromodels/src/models/contact/mod.rs b/heromodels/src/models/contact/mod.rs index 4a3aa34..533e45a 100644 --- a/heromodels/src/models/contact/mod.rs +++ b/heromodels/src/models/contact/mod.rs @@ -1,7 +1,5 @@ // Export contact module pub mod contact; -pub mod rhai; // Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs pub use self::contact::{Contact, Group}; -pub use rhai::register_contact_rhai_module; diff --git a/heromodels/src/models/contact/rhai.rs b/heromodels/src/models/contact/rhai.rs deleted file mode 100644 index fb0b01d..0000000 --- a/heromodels/src/models/contact/rhai.rs +++ /dev/null @@ -1,336 +0,0 @@ -use crate::db::Db; -use rhai::plugin::*; -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::contact::{Contact, Group}; -type RhaiGroup = Group; -type RhaiContact = Contact; -use crate::db::Collection; -use crate::db::hero::OurDB; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_contact_module { - // --- Event Functions --- - #[rhai_fn(name = "new_group")] - pub fn new_group() -> RhaiGroup { - Group::new() - } - - /// Sets the event title - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn group_name( - group: &mut RhaiGroup, - name: String, - ) -> Result> { - let owned_group = mem::take(group); - *group = owned_group.name(name); - Ok(group.clone()) - } - - /// Sets the event description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn group_description( - group: &mut RhaiGroup, - description: String, - ) -> Result> { - let owned_group = mem::take(group); - *group = owned_group.description(description); - Ok(group.clone()) - } - - /// Adds an attendee to the event - #[rhai_fn(name = "add_contact", return_raw, global, pure)] - pub fn group_add_contact( - group: &mut RhaiGroup, - contact_id: i64, - ) -> Result> { - // Use take to get ownership of the event - let owned_group = mem::take(group); - *group = owned_group.add_contact(contact_id as u32); - Ok(group.clone()) - } - - #[rhai_fn(get = "contacts", pure)] - pub fn get_group_contacts(group: &mut RhaiGroup) -> Vec { - group - .contacts - .clone() - .into_iter() - .map(|id| id as i64) - .collect() - } - - // Group Getters - #[rhai_fn(get = "id", pure)] - pub fn get_group_id(group: &mut RhaiGroup) -> i64 { - group.base_data.id as i64 - } - #[rhai_fn(get = "created_at", pure)] - pub fn get_group_created_at(group: &mut RhaiGroup) -> i64 { - group.base_data.created_at - } - #[rhai_fn(get = "modified_at", pure)] - pub fn get_group_modified_at(group: &mut RhaiGroup) -> i64 { - group.base_data.modified_at - } - - #[rhai_fn(get = "name", pure)] - pub fn get_group_name(group: &mut RhaiGroup) -> String { - group.name.clone() - } - #[rhai_fn(get = "description", pure)] - pub fn get_group_description(group: &mut RhaiGroup) -> Option { - group.description.clone() - } - - // --- Contact Functions --- - #[rhai_fn(name = "new_contact", return_raw)] - pub fn new_contact() -> Result> { - let contact = Contact::new(); - Ok(contact) - } - - /// Sets the contact name - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn contact_name( - contact: &mut RhaiContact, - name: String, - ) -> Result> { - // Create a default Contact to replace the taken one - let default_contact = Contact::new(); - - // Take ownership of the contact, apply the builder method, then put it back - let owned_contact = std::mem::replace(contact, default_contact); - *contact = owned_contact.name(name); - Ok(contact.clone()) - } - - /// Sets the contact description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn contact_description( - contact: &mut RhaiContact, - description: String, - ) -> Result> { - // Create a default Contact to replace the taken one - let default_contact = Contact::new(); - - // Take ownership of the contact, apply the builder method, then put it back - let owned_contact = std::mem::replace(contact, default_contact); - *contact = owned_contact.description(description); - Ok(contact.clone()) - } - - // Contact Getters - #[rhai_fn(get = "id", pure)] - pub fn get_contact_id(contact: &mut RhaiContact) -> i64 { - contact.base_data.id as i64 - } - - #[rhai_fn(get = "name", pure)] - pub fn get_contact_name(contact: &mut RhaiContact) -> String { - contact.name.clone() - } - - #[rhai_fn(get = "created_at", pure)] - pub fn get_contact_created_at(contact: &mut RhaiContact) -> i64 { - contact.base_data.created_at - } - - #[rhai_fn(get = "modified_at", pure)] - pub fn get_contact_modified_at(contact: &mut RhaiContact) -> i64 { - contact.base_data.modified_at - } -} - -pub fn register_contact_rhai_module(engine: &mut Engine, db: Arc) { - // Register the exported module globally - let module = exported_module!(rhai_contact_module); - engine.register_global_module(module.into()); - - // Create a module for database functions - let mut db_module = Module::new(); - - // Manually register database functions as they need to capture 'db' - let db_clone_set_group = db.clone(); - db_module.set_native_fn( - "save_group", - move |group: Group| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_group.set(&group).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_group: {}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated event with the correct ID - Ok(result.1) - }, - ); - - // Manually register database functions as they need to capture 'db' - let db_clone_delete_group = db.clone(); - db_module.set_native_fn( - "delete_group", - move |group: Group| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_group - .collection::() - .expect("can open group collection") - .delete_by_id(group.base_data.id) - .expect("can delete group"); - - // Return the updated event with the correct ID - Ok(result) - }, - ); - - let db_clone_get_group = db.clone(); - db_module.set_native_fn( - "get_group_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_group - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_event_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Event with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone_set_contact = db.clone(); - db_module.set_native_fn( - "save_contact", - move |contact: Contact| -> Result> { - // Use the Collection trait method directly - let result = db_clone_set_contact.set(&contact).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error set_contact: {}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated contact with the correct ID - Ok(result.1) - }, - ); - - // Manually register database functions as they need to capture 'db' - let db_clone_delete_contact = db.clone(); - db_module.set_native_fn( - "delete_contact", - move |contact: Contact| -> Result<(), Box> { - // Use the Collection trait method directly - let result = db_clone_delete_contact - .collection::() - .expect("can open contact collection") - .delete_by_id(contact.base_data.id) - .expect("can delete event"); - - // Return the updated event with the correct ID - Ok(result) - }, - ); - - let db_clone_get_contact = db.clone(); - db_module.set_native_fn( - "get_contact_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone_get_contact - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_contact_by_id: {}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Contact with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - // Add list_contacts function to get all contacts - let db_clone_list_contacts = db.clone(); - db_module.set_native_fn( - "list_contacts", - move || -> Result> { - let collection = db_clone_list_contacts - .collection::() - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get contact collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let contacts = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all contacts: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for contact in contacts { - array.push(Dynamic::from(contact)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Add list_events function to get all events - let db_clone_list_groups = db.clone(); - db_module.set_native_fn( - "list_groups", - move || -> Result> { - let collection = db_clone_list_groups.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get group collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let groups = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all groups: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for group in groups { - array.push(Dynamic::from(group)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Register the database module globally - engine.register_global_module(db_module.into()); - - println!("Successfully registered contact Rhai module using export_module approach."); -} diff --git a/heromodels/src/models/finance/mod.rs b/heromodels/src/models/finance/mod.rs index 871c4f9..a9706a7 100644 --- a/heromodels/src/models/finance/mod.rs +++ b/heromodels/src/models/finance/mod.rs @@ -4,7 +4,6 @@ pub mod account; pub mod asset; pub mod marketplace; -pub mod rhai; // Re-export main models for easier access pub use self::account::Account; diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs deleted file mode 100644 index e45057e..0000000 --- a/heromodels/src/models/finance/rhai.rs +++ /dev/null @@ -1,973 +0,0 @@ -use chrono::Utc; -use rhai::plugin::*; -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::account::Account; -use super::asset::{Asset, AssetType}; -use super::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; - -use crate::db::hero::OurDB; -use crate::db::{Collection, Db}; - -type RhaiAccount = Account; -type RhaiAsset = Asset; -type RhaiListing = Listing; -type RhaiBid = Bid; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -// Helper functions for enum conversions - -// AssetType conversions -fn asset_type_to_string(asset_type: &AssetType) -> String { - match asset_type { - AssetType::Native => "Native".to_string(), - AssetType::Erc20 => "Erc20".to_string(), - AssetType::Erc721 => "Erc721".to_string(), - AssetType::Erc1155 => "Erc1155".to_string(), - } -} - -fn string_to_asset_type(s: &str) -> Result> { - match s { - "Native" => Ok(AssetType::Native), - "Erc20" => Ok(AssetType::Erc20), - "Erc721" => Ok(AssetType::Erc721), - "Erc1155" => Ok(AssetType::Erc1155), - _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!( - "Invalid asset type: '{}'. Expected one of: Native, Erc20, Erc721, Erc1155", - s - ) - .into(), - Position::NONE, - ))), - } -} - -// ListingStatus conversions -fn listing_status_to_string(status: &ListingStatus) -> String { - match status { - ListingStatus::Active => "Active".to_string(), - ListingStatus::Sold => "Sold".to_string(), - ListingStatus::Cancelled => "Cancelled".to_string(), - ListingStatus::Expired => "Expired".to_string(), - } -} - -fn string_to_listing_status(s: &str) -> Result> { - match s { - "Active" => Ok(ListingStatus::Active), - "Sold" => Ok(ListingStatus::Sold), - "Cancelled" => Ok(ListingStatus::Cancelled), - "Expired" => Ok(ListingStatus::Expired), - _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!( - "Invalid listing status: '{}'. Expected one of: Active, Sold, Cancelled, Expired", - s - ) - .into(), - Position::NONE, - ))), - } -} - -// ListingType conversions -fn listing_type_to_string(lt: &ListingType) -> String { - match lt { - ListingType::FixedPrice => "FixedPrice".to_string(), - ListingType::Auction => "Auction".to_string(), - ListingType::Exchange => "Exchange".to_string(), - } -} - -fn string_to_listing_type(s: &str) -> Result> { - match s { - "FixedPrice" => Ok(ListingType::FixedPrice), - "Auction" => Ok(ListingType::Auction), - "Exchange" => Ok(ListingType::Exchange), - _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!( - "Invalid listing type: '{}'. Expected one of: FixedPrice, Auction, Exchange", - s - ) - .into(), - Position::NONE, - ))), - } -} - -// BidStatus conversions -fn bid_status_to_string(status: &BidStatus) -> String { - match status { - BidStatus::Active => "Active".to_string(), - BidStatus::Accepted => "Accepted".to_string(), - BidStatus::Rejected => "Rejected".to_string(), - BidStatus::Cancelled => "Cancelled".to_string(), - } -} - -fn string_to_bid_status(s: &str) -> Result> { - match s { - "Active" => Ok(BidStatus::Active), - "Accepted" => Ok(BidStatus::Accepted), - "Rejected" => Ok(BidStatus::Rejected), - "Cancelled" => Ok(BidStatus::Cancelled), - _ => Err(Box::new(EvalAltResult::ErrorRuntime( - format!( - "Invalid bid status: '{}'. Expected one of: Active, Accepted, Rejected, Cancelled", - s - ) - .into(), - Position::NONE, - ))), - } -} - -// Account module -#[export_module] -mod account_module { - use super::*; - - // Constructor - #[rhai_fn(return_raw, global)] - pub fn new_account() -> Result> { - Ok(Account::new()) - } - - // Getters - #[rhai_fn(global, pure)] - pub fn get_id(account: &mut RhaiAccount) -> INT { - account.base_data.id as INT - } - - #[rhai_fn(global, pure)] - pub fn get_created_at(account: &mut RhaiAccount) -> INT { - account.base_data.created_at - } - - #[rhai_fn(global, pure)] - pub fn get_name(account: &mut RhaiAccount) -> String { - account.name.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_user_id(account: &mut RhaiAccount) -> INT { - account.user_id as INT - } - - #[rhai_fn(global, pure)] - pub fn get_description(account: &mut RhaiAccount) -> String { - account.description.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_ledger(account: &mut RhaiAccount) -> String { - account.ledger.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_address(account: &mut RhaiAccount) -> String { - account.address.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_pubkey(account: &mut RhaiAccount) -> String { - account.pubkey.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_assets(account: &mut RhaiAccount) -> Array { - let mut assets_array = Array::new(); - for asset_id in &account.assets { - assets_array.push(Dynamic::from(*asset_id as INT)); - } - assets_array - } - - // Setters using builder pattern with proper mutability handling - #[rhai_fn(return_raw, global)] - pub fn name( - account: &mut RhaiAccount, - name: String, - ) -> Result> { - let mut acc = mem::take(account); - acc = acc.name(name); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn user_id( - account: &mut RhaiAccount, - user_id: INT, - ) -> Result> { - let user_id = id_from_i64_to_u32(user_id)?; - let mut acc = mem::take(account); - acc = acc.user_id(user_id); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn description( - account: &mut RhaiAccount, - description: String, - ) -> Result> { - let mut acc = mem::take(account); - acc = acc.description(description); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn ledger( - account: &mut RhaiAccount, - ledger: String, - ) -> Result> { - let mut acc = mem::take(account); - acc = acc.ledger(ledger); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn address( - account: &mut RhaiAccount, - address: String, - ) -> Result> { - let mut acc = mem::take(account); - acc = acc.address(address); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn pubkey( - account: &mut RhaiAccount, - pubkey: String, - ) -> Result> { - let mut acc = mem::take(account); - acc = acc.pubkey(pubkey); - *account = acc; - Ok(account.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn add_asset( - account: &mut RhaiAccount, - asset_id: INT, - ) -> Result> { - let asset_id = id_from_i64_to_u32(asset_id)?; - let mut acc = mem::take(account); - acc = acc.add_asset(asset_id); - *account = acc; - Ok(account.clone()) - } -} - -// Asset module -#[export_module] -mod asset_module { - use super::*; - - // Constructor - #[rhai_fn(return_raw, global)] - pub fn new_asset() -> Result> { - Ok(Asset::new()) - } - - // Getters - #[rhai_fn(global, pure)] - pub fn get_id(asset: &mut RhaiAsset) -> INT { - asset.base_data.id as INT - } - - #[rhai_fn(global, pure)] - pub fn get_created_at(asset: &mut RhaiAsset) -> INT { - asset.base_data.created_at - } - - #[rhai_fn(global, pure)] - pub fn get_name(asset: &mut RhaiAsset) -> String { - asset.name.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_description(asset: &mut RhaiAsset) -> String { - asset.description.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_amount(asset: &mut RhaiAsset) -> f64 { - asset.amount - } - - #[rhai_fn(global, pure)] - pub fn get_address(asset: &mut RhaiAsset) -> String { - asset.address.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_asset_type(asset: &mut RhaiAsset) -> String { - asset_type_to_string(&asset.asset_type) - } - - #[rhai_fn(global, pure)] - pub fn get_decimals(asset: &mut RhaiAsset) -> INT { - asset.decimals as INT - } - - // Setters using builder pattern with proper mutability handling - #[rhai_fn(return_raw, global)] - pub fn name(asset: &mut RhaiAsset, name: String) -> Result> { - let mut ast = mem::take(asset); - ast = ast.name(name); - *asset = ast; - Ok(asset.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn description( - asset: &mut RhaiAsset, - description: String, - ) -> Result> { - let mut ast = mem::take(asset); - ast = ast.description(description); - *asset = ast; - Ok(asset.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn amount(asset: &mut RhaiAsset, amount: f64) -> Result> { - let mut ast = mem::take(asset); - ast = ast.amount(amount); - *asset = ast; - Ok(asset.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn address( - asset: &mut RhaiAsset, - address: String, - ) -> Result> { - let mut ast = mem::take(asset); - ast = ast.address(address); - *asset = ast; - Ok(asset.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn asset_type( - asset: &mut RhaiAsset, - asset_type_str: String, - ) -> Result> { - let asset_type_enum = string_to_asset_type(&asset_type_str)?; - let mut ast = mem::take(asset); - ast = ast.asset_type(asset_type_enum); - *asset = ast; - Ok(asset.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn decimals(asset: &mut RhaiAsset, decimals: INT) -> Result> { - if decimals < 0 || decimals > 255 { - return Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Decimals value must be between 0 and 255, got {}", decimals).into(), - Position::NONE, - ))); - } - let mut ast = mem::take(asset); - ast = ast.decimals(decimals as u8); - *asset = ast; - Ok(asset.clone()) - } -} -// Listing module -#[export_module] -mod listing_module { - use super::*; - - // Constructor - #[rhai_fn(return_raw, global)] - pub fn new_listing() -> Result> { - Ok(Listing::new()) - } - - // Getters - #[rhai_fn(global, pure)] - pub fn get_id(listing: &mut RhaiListing) -> INT { - listing.base_data.id as INT - } - - #[rhai_fn(global, pure)] - pub fn get_created_at(listing: &mut RhaiListing) -> INT { - listing.base_data.created_at - } - - #[rhai_fn(global, pure)] - pub fn get_seller_id(listing: &mut RhaiListing) -> INT { - // Parse the seller_id string to u32, then to INT - listing.seller_id.parse::().unwrap_or(0) as INT - } - - #[rhai_fn(global, pure)] - pub fn get_asset_id(listing: &mut RhaiListing) -> INT { - // Parse the asset_id string to u32, then to INT - listing.asset_id.parse::().unwrap_or(0) as INT - } - - #[rhai_fn(global, pure)] - pub fn get_price(listing: &mut RhaiListing) -> f64 { - listing.price - } - - #[rhai_fn(global, pure)] - pub fn get_currency(listing: &mut RhaiListing) -> String { - listing.currency.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_listing_type(listing: &mut RhaiListing) -> String { - listing_type_to_string(&listing.listing_type) - } - - #[rhai_fn(global, pure)] - pub fn get_status(listing: &mut RhaiListing) -> String { - listing_status_to_string(&listing.status) - } - - #[rhai_fn(global, pure)] - pub fn get_title(listing: &mut RhaiListing) -> String { - listing.title.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_description(listing: &mut RhaiListing) -> String { - listing.description.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_image_url(listing: &mut RhaiListing) -> String { - listing.image_url.clone().unwrap_or_else(|| "".to_string()) - } - - #[rhai_fn(global, pure)] - pub fn get_end_date(listing: &mut RhaiListing) -> INT { - listing.expires_at.map_or(0, |d| d.timestamp()) - } - - #[rhai_fn(global, pure)] - pub fn get_tags(listing: &mut RhaiListing) -> Array { - let mut tags_array = Array::new(); - for tag in &listing.tags { - tags_array.push(Dynamic::from(tag.clone())); - } - tags_array - } - - #[rhai_fn(global, pure)] - pub fn get_bids(listing: &mut RhaiListing) -> Array { - let mut bids_array = Array::new(); - for bid in &listing.bids { - bids_array.push(Dynamic::from(bid.clone())); - } - bids_array - } - - // Setters using builder pattern with proper mutability handling - #[rhai_fn(return_raw, global)] - pub fn seller_id( - listing: &mut RhaiListing, - seller_id: INT, - ) -> Result> { - let seller_id = id_from_i64_to_u32(seller_id)?; - let mut lst = mem::take(listing); - lst = lst.seller_id(seller_id); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn asset_id( - listing: &mut RhaiListing, - asset_id: INT, - ) -> Result> { - let asset_id = id_from_i64_to_u32(asset_id)?; - let mut lst = mem::take(listing); - lst = lst.asset_id(asset_id); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn price(listing: &mut RhaiListing, price: f64) -> Result> { - let mut lst = mem::take(listing); - lst = lst.price(price); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn currency( - listing: &mut RhaiListing, - currency: String, - ) -> Result> { - let mut lst = mem::take(listing); - lst = lst.currency(currency); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn listing_type( - listing: &mut RhaiListing, - listing_type_str: String, - ) -> Result> { - let listing_type_enum = string_to_listing_type(&listing_type_str)?; - let mut lst = mem::take(listing); - lst = lst.listing_type(listing_type_enum); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn title( - listing: &mut RhaiListing, - title: String, - ) -> Result> { - let mut lst = mem::take(listing); - lst = lst.title(title); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn description( - listing: &mut RhaiListing, - description: String, - ) -> Result> { - let mut lst = mem::take(listing); - lst = lst.description(description); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn image_url( - listing: &mut RhaiListing, - image_url: String, - ) -> Result> { - let mut lst = mem::take(listing); - lst = lst.image_url(Some(image_url)); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn expires_at( - listing: &mut RhaiListing, - end_date_ts: INT, - ) -> Result> { - use chrono::TimeZone; - let end_date = chrono::Utc - .timestamp_opt(end_date_ts, 0) - .single() - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Invalid timestamp: {}", end_date_ts).into(), - Position::NONE, - )) - })?; - - let mut lst = mem::take(listing); - lst = lst.expires_at(Some(end_date)); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn add_tag( - listing: &mut RhaiListing, - tag: String, - ) -> Result> { - let mut lst = mem::take(listing); - lst = lst.add_tag(tag); - *listing = lst; - Ok(listing.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn add_bid( - listing: &mut RhaiListing, - bid: RhaiBid, - ) -> Result> { - let lst = mem::take(listing); - match lst.clone().add_bid(bid) { - Ok(updated_lst) => { - *listing = updated_lst; - Ok(listing.clone()) - } - Err(err) => { - // Put back the original listing since the add_bid failed - *listing = lst; - Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to add bid: {}", err).into(), - Position::NONE, - ))) - } - } - } - - #[rhai_fn(return_raw, global)] - pub fn complete_sale( - listing: &mut RhaiListing, - buyer_id: INT, - ) -> Result> { - let buyer_id = id_from_i64_to_u32(buyer_id)?; - let lst = mem::take(listing); - - // First set the buyer_id and sale_price - let lst_with_buyer = lst.clone().buyer_id(buyer_id); - - // Now complete the sale - match lst_with_buyer.complete_sale() { - Ok(updated_lst) => { - *listing = updated_lst; - Ok(listing.clone()) - } - Err(err) => { - // Put back the original listing since the complete_sale failed - *listing = lst; - Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to complete sale: {}", err).into(), - Position::NONE, - ))) - } - } - } - - #[rhai_fn(return_raw, global)] - pub fn cancel(listing: &mut RhaiListing) -> Result> { - let lst = mem::take(listing); - match lst.clone().cancel() { - Ok(updated_lst) => { - *listing = updated_lst; - Ok(listing.clone()) - } - Err(err) => { - // Put back the original listing since the cancel failed - *listing = lst; - Err(Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to cancel listing: {}", err).into(), - Position::NONE, - ))) - } - } - } - - #[rhai_fn(return_raw, global)] - pub fn check_expiration(listing: &mut RhaiListing) -> Result> { - let mut lst = mem::take(listing); - lst = lst.check_expiration(); - *listing = lst; - Ok(listing.clone()) - } -} - -// Bid module -#[export_module] -mod bid_module { - use super::*; - - // Constructor - #[rhai_fn(return_raw, global)] - pub fn new_bid() -> Result> { - Ok(Bid::new()) - } - - // Getters - #[rhai_fn(global, pure)] - pub fn get_listing_id(bid: &mut RhaiBid) -> String { - bid.listing_id.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_bidder_id(bid: &mut RhaiBid) -> INT { - bid.bidder_id as INT - } - - #[rhai_fn(global, pure)] - pub fn get_amount(bid: &mut RhaiBid) -> f64 { - bid.amount - } - - #[rhai_fn(global, pure)] - pub fn get_currency(bid: &mut RhaiBid) -> String { - bid.currency.clone() - } - - #[rhai_fn(global, pure)] - pub fn get_status(bid: &mut RhaiBid) -> String { - bid_status_to_string(&bid.status) - } - - #[rhai_fn(global, pure)] - pub fn get_created_at(bid: &mut RhaiBid) -> INT { - bid.created_at.timestamp() - } - - // Setters using builder pattern with proper mutability handling - #[rhai_fn(return_raw, global)] - pub fn listing_id( - bid: &mut RhaiBid, - listing_id: String, - ) -> Result> { - let mut b = mem::take(bid); - b = b.listing_id(listing_id); - *bid = b; - Ok(bid.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn bidder_id(bid: &mut RhaiBid, bidder_id: INT) -> Result> { - let bidder_id = id_from_i64_to_u32(bidder_id)?; - let mut b = mem::take(bid); - b = b.bidder_id(bidder_id); - *bid = b; - Ok(bid.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn amount(bid: &mut RhaiBid, amount: f64) -> Result> { - let mut b = mem::take(bid); - b = b.amount(amount); - *bid = b; - Ok(bid.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn currency(bid: &mut RhaiBid, currency: String) -> Result> { - let mut b = mem::take(bid); - b = b.currency(currency); - *bid = b; - Ok(bid.clone()) - } - - #[rhai_fn(return_raw, global)] - pub fn update_status( - bid: &mut RhaiBid, - status_str: String, - ) -> Result> { - let status = string_to_bid_status(&status_str)?; - let mut b = mem::take(bid); - b = b.status(status); - *bid = b; - Ok(bid.clone()) - } -} -use rhai::ImmutableString; - -// Register all Rhai functions for the finance module -pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { - // --- Register model-specific modules with the engine --- - let account_module = exported_module!(account_module); - engine.register_global_module(account_module.into()); - - let asset_module = exported_module!(asset_module); - engine.register_global_module(asset_module.into()); - - let listing_module = exported_module!(listing_module); - engine.register_global_module(listing_module.into()); - - let bid_module = exported_module!(bid_module); - engine.register_global_module(bid_module.into()); - - // --- Global Helper Functions (Enum conversions) --- - engine.register_fn("str_to_asset_type", |s: ImmutableString| { - self::string_to_asset_type(s.as_str()) - }); - engine.register_fn("asset_type_to_str", self::asset_type_to_string); - engine.register_fn("str_to_listing_status", |s: ImmutableString| { - self::string_to_listing_status(s.as_str()) - }); - engine.register_fn("listing_status_to_str", self::listing_status_to_string); - engine.register_fn("str_to_listing_type", |s: ImmutableString| { - self::string_to_listing_type(s.as_str()) - }); - engine.register_fn("listing_type_to_str", self::listing_type_to_string); - engine.register_fn("str_to_bid_status", |s: ImmutableString| { - self::string_to_bid_status(s.as_str()) - }); - engine.register_fn("bid_status_to_str", self::bid_status_to_string); - - // --- Database interaction functions (registered in a separate db_module) --- - let mut db_module = Module::new(); - - // Account DB functions - let db_set_account = Arc::clone(&db); - db_module.set_native_fn( - "set_account", - move |account: Account| -> Result> { - let collection = db_set_account.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Account collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .set(&account) - .map(|(id, _)| id as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save Account: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_get_account = Arc::clone(&db); - db_module.set_native_fn( - "get_account_by_id", - move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_account.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Account collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .get_by_id(id_u32) - .map(|opt_account| { - opt_account - .map(Dynamic::from) - .unwrap_or_else(|| Dynamic::UNIT) - }) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Account with ID {}: {:?}", id_rhai, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Asset DB functions - let db_set_asset = Arc::clone(&db); - db_module.set_native_fn( - "set_asset", - move |asset: Asset| -> Result> { - let collection = db_set_asset.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Asset collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .set(&asset) - .map(|(id, _)| id as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save Asset: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_get_asset = Arc::clone(&db); - db_module.set_native_fn( - "get_asset_by_id", - move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_asset.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Asset collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .get_by_id(id_u32) - .map(|opt_asset| { - opt_asset - .map(Dynamic::from) - .unwrap_or_else(|| Dynamic::UNIT) - }) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Asset with ID {}: {:?}", id_rhai, e).into(), - Position::NONE, - )) - }) - }, - ); - - // Listing DB functions - let db_set_listing = Arc::clone(&db); - db_module.set_native_fn( - "set_listing", - move |listing: Listing| -> Result> { - let collection = db_set_listing.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Listing collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .set(&listing) - .map(|(id, _)| id as INT) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to save Listing: {:?}", e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_get_listing = Arc::clone(&db); - db_module.set_native_fn( - "get_listing_by_id", - move |id_rhai: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_rhai)?; - let collection = db_get_listing.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Listing collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection - .get_by_id(id_u32) - .map(|opt_listing| { - opt_listing - .map(Dynamic::from) - .unwrap_or_else(|| Dynamic::UNIT) - }) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get Listing with ID {}: {:?}", id_rhai, e).into(), - Position::NONE, - )) - }) - }, - ); - - engine.register_global_module(db_module.into()); - - // Global timestamp function for scripts to get current time - engine.register_fn("timestamp", || Utc::now().timestamp()); - - println!("Successfully registered finance Rhai module."); -} diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index 28d16f9..0eec251 100644 --- a/heromodels/src/models/flow/flow.rs +++ b/heromodels/src/models/flow/flow.rs @@ -1,13 +1,15 @@ use super::flow_step::FlowStep; use heromodels_core::BaseModelData; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; /// Represents a signing flow. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default, CustomType)] #[model] pub struct Flow { /// Base model data (id, created_at, updated_at). + #[rhai_type(skip)] pub base_data: BaseModelData, /// A unique UUID for the flow, for external reference. diff --git a/heromodels/src/models/flow/flow_step.rs b/heromodels/src/models/flow/flow_step.rs index ce554f6..e5bc2b6 100644 --- a/heromodels/src/models/flow/flow_step.rs +++ b/heromodels/src/models/flow/flow_step.rs @@ -1,13 +1,15 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; use std::default::Default; /// Represents a step within a signing flow. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, CustomType)] #[model] pub struct FlowStep { /// Base model data. + #[rhai_type(skip)] pub base_data: BaseModelData, /// Optional description for the step. diff --git a/heromodels/src/models/flow/mod.rs b/heromodels/src/models/flow/mod.rs index 8dd2206..fd3cb68 100644 --- a/heromodels/src/models/flow/mod.rs +++ b/heromodels/src/models/flow/mod.rs @@ -1,11 +1,9 @@ // Export flow model submodules pub mod flow; pub mod flow_step; -pub mod rhai; pub mod signature_requirement; // Re-export key types for convenience pub use flow::Flow; pub use flow_step::FlowStep; -pub use rhai::register_flow_rhai_module; -pub use signature_requirement::SignatureRequirement; +pub use signature_requirement::SignatureRequirement; \ No newline at end of file diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs deleted file mode 100644 index 03f96b9..0000000 --- a/heromodels/src/models/flow/rhai.rs +++ /dev/null @@ -1,535 +0,0 @@ -use rhai::plugin::*; -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT, Module, Position}; -use std::mem; -use std::sync::Arc; - -use super::flow::Flow; -use super::flow_step::FlowStep; -use super::signature_requirement::SignatureRequirement; -type RhaiFlow = Flow; -type RhaiFlowStep = FlowStep; -type RhaiSignatureRequirement = SignatureRequirement; -use crate::db::Collection; -use crate::db::Db; -use crate::db::hero::OurDB; - -// Helper to convert i64 from Rhai to u32 for IDs -fn id_from_i64_to_u32(id_i64: i64) -> Result> { - u32::try_from(id_i64).map_err(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert ID '{}' to u32", id_i64).into(), - Position::NONE, - )) - }) -} - -#[export_module] -mod rhai_flow_module { - // --- Flow Functions --- - #[rhai_fn(name = "new_flow")] - pub fn new_flow(flow_uuid: String) -> RhaiFlow { - Flow::new(flow_uuid) - } - - /// Sets the flow name - #[rhai_fn(name = "name", return_raw, global, pure)] - pub fn flow_name(flow: &mut RhaiFlow, name: String) -> Result> { - let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement - *flow = owned_flow.name(name); - Ok(flow.clone()) - } - - /// Sets the flow status - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_status( - flow: &mut RhaiFlow, - status: String, - ) -> Result> { - let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement - *flow = owned_flow.status(status); - Ok(flow.clone()) - } - - /// Adds a step to the flow - #[rhai_fn(name = "add_step", return_raw, global, pure)] - pub fn flow_add_step( - flow: &mut RhaiFlow, - step: RhaiFlowStep, - ) -> Result> { - let owned_flow = mem::replace(flow, Flow::new("")); // Dummy for replacement - *flow = owned_flow.add_step(step); - Ok(flow.clone()) - } - - // Flow Getters - #[rhai_fn(get = "id", pure)] - pub fn get_id(flow: &mut RhaiFlow) -> i64 { - flow.base_data.id as i64 - } - #[rhai_fn(get = "created_at", pure)] - pub fn get_created_at(flow: &mut RhaiFlow) -> i64 { - flow.base_data.created_at - } - #[rhai_fn(get = "modified_at", pure)] - pub fn get_modified_at(flow: &mut RhaiFlow) -> i64 { - flow.base_data.modified_at - } - #[rhai_fn(get = "flow_uuid", pure)] - pub fn get_flow_uuid(flow: &mut RhaiFlow) -> String { - flow.flow_uuid.clone() - } - #[rhai_fn(get = "name", pure)] - pub fn get_name(flow: &mut RhaiFlow) -> String { - flow.name.clone() - } - #[rhai_fn(get = "status", pure)] - pub fn get_status(flow: &mut RhaiFlow) -> String { - flow.status.clone() - } - #[rhai_fn(get = "steps", pure)] - pub fn get_steps(flow: &mut RhaiFlow) -> Array { - flow.steps - .iter() - .cloned() - .map(Dynamic::from) - .collect::() - } - - // --- FlowStep Functions --- - #[rhai_fn(global)] - pub fn new_flow_step(step_order_i64: i64) -> Dynamic { - match id_from_i64_to_u32(step_order_i64) { - Ok(step_order) => { - let mut flow_step = FlowStep::default(); - flow_step.step_order = step_order; - Dynamic::from(flow_step) - } - Err(err) => Dynamic::from(err.to_string()), - } - } - - /// Sets the flow step description - #[rhai_fn(name = "description", return_raw, global, pure)] - pub fn flow_step_description( - step: &mut RhaiFlowStep, - description: String, - ) -> Result> { - let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait - *step = owned_step.description(description); - Ok(step.clone()) - } - - /// Sets the flow step status - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn flow_step_status( - step: &mut RhaiFlowStep, - status: String, - ) -> Result> { - let owned_step = mem::replace(step, FlowStep::default()); // Use Default trait - *step = owned_step.status(status); - Ok(step.clone()) - } - - // FlowStep Getters - #[rhai_fn(get = "id", pure)] - pub fn get_step_id(step: &mut RhaiFlowStep) -> i64 { - step.base_data.id as i64 - } - #[rhai_fn(get = "created_at", pure)] - pub fn get_step_created_at(step: &mut RhaiFlowStep) -> i64 { - step.base_data.created_at - } - #[rhai_fn(get = "modified_at", pure)] - pub fn get_step_modified_at(step: &mut RhaiFlowStep) -> i64 { - step.base_data.modified_at - } - #[rhai_fn(get = "description", pure)] - pub fn get_step_description(step: &mut RhaiFlowStep) -> Dynamic { - match &step.description { - Some(desc) => Dynamic::from(desc.clone()), - None => Dynamic::UNIT, - } - } - #[rhai_fn(get = "step_order", pure)] - pub fn get_step_order(step: &mut RhaiFlowStep) -> i64 { - step.step_order as i64 - } - #[rhai_fn(get = "status", pure)] - pub fn get_step_status(step: &mut RhaiFlowStep) -> String { - step.status.clone() - } - - // --- SignatureRequirement Functions --- - /// Create a new signature requirement - #[rhai_fn(global)] - pub fn new_signature_requirement( - flow_step_id_i64: i64, - public_key: String, - message: String, - ) -> Dynamic { - match id_from_i64_to_u32(flow_step_id_i64) { - Ok(flow_step_id) => { - let mut signature_requirement = SignatureRequirement::default(); - signature_requirement.flow_step_id = flow_step_id; - signature_requirement.public_key = public_key; - signature_requirement.message = message; - Dynamic::from(signature_requirement) - } - Err(err) => Dynamic::from(err.to_string()), - } - } - - /// Sets the signed_by field - #[rhai_fn(name = "signed_by", return_raw, global, pure)] - pub fn signature_requirement_signed_by( - sr: &mut RhaiSignatureRequirement, - signed_by: String, - ) -> Result> { - let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait - *sr = owned_sr.signed_by(signed_by); - Ok(sr.clone()) - } - - /// Sets the signature field - #[rhai_fn(name = "signature", return_raw, global, pure)] - pub fn signature_requirement_signature( - sr: &mut RhaiSignatureRequirement, - signature: String, - ) -> Result> { - let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait - *sr = owned_sr.signature(signature); - Ok(sr.clone()) - } - - /// Sets the status field - #[rhai_fn(name = "status", return_raw, global, pure)] - pub fn signature_requirement_status( - sr: &mut RhaiSignatureRequirement, - status: String, - ) -> Result> { - let owned_sr = mem::replace(sr, SignatureRequirement::default()); // Use Default trait - *sr = owned_sr.status(status); - Ok(sr.clone()) - } - - // SignatureRequirement Getters - #[rhai_fn(get = "id", pure)] - pub fn get_sr_id(sr: &mut RhaiSignatureRequirement) -> i64 { - sr.base_data.id as i64 - } - #[rhai_fn(get = "created_at", pure)] - pub fn get_sr_created_at(sr: &mut RhaiSignatureRequirement) -> i64 { - sr.base_data.created_at - } - #[rhai_fn(get = "modified_at", pure)] - pub fn get_sr_modified_at(sr: &mut RhaiSignatureRequirement) -> i64 { - sr.base_data.modified_at - } - #[rhai_fn(get = "flow_step_id", pure)] - pub fn get_sr_flow_step_id(sr: &mut RhaiSignatureRequirement) -> i64 { - sr.flow_step_id as i64 - } - #[rhai_fn(get = "public_key", pure)] - pub fn get_sr_public_key(sr: &mut RhaiSignatureRequirement) -> String { - sr.public_key.clone() - } - #[rhai_fn(get = "message", pure)] - pub fn get_sr_message(sr: &mut RhaiSignatureRequirement) -> String { - sr.message.clone() - } - #[rhai_fn(get = "signed_by", pure)] - pub fn get_sr_signed_by(sr: &mut RhaiSignatureRequirement) -> Dynamic { - match &sr.signed_by { - Some(signed_by) => Dynamic::from(signed_by.clone()), - None => Dynamic::UNIT, - } - } - #[rhai_fn(get = "signature", pure)] - pub fn get_sr_signature(sr: &mut RhaiSignatureRequirement) -> Dynamic { - match &sr.signature { - Some(signature) => Dynamic::from(signature.clone()), - None => Dynamic::UNIT, - } - } - #[rhai_fn(get = "status", pure)] - pub fn get_sr_status(sr: &mut RhaiSignatureRequirement) -> String { - sr.status.clone() - } -} - -/// Register the flow module with the Rhai engine -pub fn register_flow_rhai_module(engine: &mut Engine, db: Arc) { - // Create a module for database functions - let mut db_module = Module::new(); - - // Flow database functions - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "save_flow", - move |flow: Flow| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&flow).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error save_flow: {:?}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated flow with the correct ID - Ok(result.1) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "get_flow_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_flow_by_id: {:?}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Flow with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "delete_flow", - move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection.delete_by_id(id_u32).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete Flow (ID: {}): {:?}", id_u32, e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "list_flows", - move || -> Result> { - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let flows = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flows: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for flow in flows { - array.push(Dynamic::from(flow)); - } - Ok(Dynamic::from(array)) - }, - ); - - // FlowStep database functions - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "save_flow_step", - move |step: FlowStep| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&step).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error save_flow_step: {:?}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated flow step with the correct ID - Ok(result.1) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "get_flow_step_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_flow_step_by_id: {:?}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("FlowStep with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "delete_flow_step", - move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection.delete_by_id(id_u32).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to delete FlowStep (ID: {}): {:?}", id_u32, e).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "list_flow_steps", - move || -> Result> { - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get flow step collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let steps = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all flow steps: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for step in steps { - array.push(Dynamic::from(step)); - } - Ok(Dynamic::from(array)) - }, - ); - - // SignatureRequirement database functions - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "save_signature_requirement", - move |sr: SignatureRequirement| -> Result> { - // Use the Collection trait method directly - let result = db_clone.set(&sr).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error save_signature_requirement: {:?}", e).into(), - Position::NONE, - )) - })?; - - // Return the updated signature requirement with the correct ID - Ok(result.1) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "get_signature_requirement_by_id", - move |id_i64: INT| -> Result> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - db_clone - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("DB Error get_signature_requirement_by_id: {:?}", e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("SignatureRequirement with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "delete_signature_requirement", - move |id_i64: INT| -> Result<(), Box> { - let id_u32 = id_from_i64_to_u32(id_i64)?; - // Use the Collection trait method directly - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE, - )) - })?; - collection.delete_by_id(id_u32).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!( - "Failed to delete SignatureRequirement (ID: {}): {:?}", - id_u32, e - ) - .into(), - Position::NONE, - )) - }) - }, - ); - - let db_clone = Arc::clone(&db); - db_module.set_native_fn( - "list_signature_requirements", - move || -> Result> { - let collection = db_clone.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get signature requirement collection: {:?}", e).into(), - Position::NONE, - )) - })?; - let srs = collection.get_all().map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Failed to get all signature requirements: {:?}", e).into(), - Position::NONE, - )) - })?; - let mut array = Array::new(); - for sr in srs { - array.push(Dynamic::from(sr)); - } - Ok(Dynamic::from(array)) - }, - ); - - // Register the database module globally - engine.register_static_module("db", db_module.into()); - - // Register the flow module using exported_module! macro - let module = exported_module!(rhai_flow_module); - engine.register_global_module(module.into()); - - println!("Flow Rhai module registered."); -} diff --git a/heromodels/src/models/flow/signature_requirement.rs b/heromodels/src/models/flow/signature_requirement.rs index 453b99d..318b8a5 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -1,13 +1,15 @@ use heromodels_core::BaseModelData; use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; use std::default::Default; /// Represents a signature requirement for a flow step. -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, CustomType)] #[model] pub struct SignatureRequirement { /// Base model data. + #[rhai_type(skip)] pub base_data: BaseModelData, /// Foreign key to the FlowStep this requirement belongs to. diff --git a/heromodels/src/models/legal/mod.rs b/heromodels/src/models/legal/mod.rs index 4b16576..17ada52 100644 --- a/heromodels/src/models/legal/mod.rs +++ b/heromodels/src/models/legal/mod.rs @@ -1,5 +1,3 @@ pub mod contract; -pub mod rhai; pub use contract::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; -pub use rhai::register_legal_rhai_module; diff --git a/heromodels/src/models/legal/rhai.rs b/heromodels/src/models/legal/rhai.rs deleted file mode 100644 index 279fa0a..0000000 --- a/heromodels/src/models/legal/rhai.rs +++ /dev/null @@ -1,613 +0,0 @@ -use rhai::{Array, Dynamic, Engine, EvalAltResult, Module, NativeCallContext, Position}; -use std::sync::Arc; - -use crate::db::hero::OurDB; // Updated path based on compiler suggestion -// use heromodels_core::BaseModelData; // Removed as fields are accessed via contract.base_data directly -use crate::models::legal::{ - Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus, -}; - -use crate::db::Collection; // Import the Collection trait - -// --- Helper Functions for ID and Timestamp Conversion --- -fn i64_to_u32( - val: i64, - context_pos: Position, - field_name: &str, - object_name: &str, -) -> Result> { - val.try_into().map_err(|_e| { - Box::new(EvalAltResult::ErrorArithmetic( - format!( - "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u32", - field_name, object_name, val - ), - context_pos, - )) - }) -} - -fn i64_to_u64( - val: i64, - context_pos: Position, - field_name: &str, - object_name: &str, -) -> Result> { - val.try_into().map_err(|_e| { - Box::new(EvalAltResult::ErrorArithmetic( - format!( - "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to u64", - field_name, object_name, val - ), - context_pos, - )) - }) -} - -fn i64_to_i32( - val: i64, - context_pos: Position, - field_name: &str, - object_name: &str, -) -> Result> { - val.try_into().map_err(|_e| { - Box::new(EvalAltResult::ErrorArithmetic( - format!( - "Conversion error for field '{}' in object '{}': cannot convert i64 ({}) to i32", - field_name, object_name, val - ), - context_pos, - )) - }) -} - -pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc) { - // --- ContractStatus Enum --- - // Register ContractStatus enum as constants - let mut contract_status_module = Module::new(); - contract_status_module.set_var("Draft", ContractStatus::Draft); - contract_status_module.set_var("PendingSignatures", ContractStatus::PendingSignatures); - contract_status_module.set_var("Signed", ContractStatus::Signed); - contract_status_module.set_var("Active", ContractStatus::Active); - contract_status_module.set_var("Expired", ContractStatus::Expired); - contract_status_module.set_var("Cancelled", ContractStatus::Cancelled); - engine.register_static_module("ContractStatusConstants", contract_status_module.into()); - engine.register_type_with_name::("ContractStatus"); // Expose the type itself - - // Register SignerStatus enum as constants - let mut signer_status_module = Module::new(); - signer_status_module.set_var("Pending", SignerStatus::Pending); - signer_status_module.set_var("Signed", SignerStatus::Signed); - signer_status_module.set_var("Rejected", SignerStatus::Rejected); - engine.register_static_module("SignerStatusConstants", signer_status_module.into()); - engine.register_type_with_name::("SignerStatus"); // Expose the type itself - - // --- ContractRevision --- - engine.register_type_with_name::("ContractRevision"); - engine.register_fn( - "new_contract_revision", - move |context: NativeCallContext, - version_i64: i64, - content: String, - created_at_i64: i64, - created_by: String| - -> Result> { - let version = i64_to_u32( - version_i64, - context.position(), - "version", - "new_contract_revision", - )?; - let created_at = i64_to_u64( - created_at_i64, - context.position(), - "created_at", - "new_contract_revision", - )?; - Ok(ContractRevision::new( - version, content, created_at, created_by, - )) - }, - ); - engine.register_fn( - "comments", - |mut revision: ContractRevision, comments: String| -> ContractRevision { - revision.comments = Some(comments); - revision - }, - ); - engine.register_get( - "version", - |revision: &mut ContractRevision| -> Result> { - Ok(revision.version as i64) - }, - ); - engine.register_get( - "content", - |revision: &mut ContractRevision| -> Result> { - Ok(revision.content.clone()) - }, - ); - engine.register_get( - "created_at", - |revision: &mut ContractRevision| -> Result> { - Ok(revision.created_at as i64) - }, - ); - engine.register_get( - "created_by", - |revision: &mut ContractRevision| -> Result> { - Ok(revision.created_by.clone()) - }, - ); - engine.register_get( - "comments", - |revision: &mut ContractRevision| -> Result> { - Ok(revision - .comments - .clone() - .map_or(Dynamic::UNIT, Dynamic::from)) - }, - ); - - // --- ContractSigner --- - engine.register_type_with_name::("ContractSigner"); - engine.register_fn( - "new_contract_signer", - |id: String, name: String, email: String| -> ContractSigner { - ContractSigner::new(id, name, email) - }, - ); - engine.register_fn( - "status", - |signer: ContractSigner, status: SignerStatus| -> ContractSigner { signer.status(status) }, - ); - engine.register_fn( - "signed_at", - |context: NativeCallContext, - signer: ContractSigner, - signed_at_i64: i64| - -> Result> { - let signed_at_u64 = i64_to_u64( - signed_at_i64, - context.position(), - "signed_at", - "ContractSigner.signed_at", - )?; - Ok(signer.signed_at(signed_at_u64)) - }, - ); - engine.register_fn( - "clear_signed_at", - |signer: ContractSigner| -> ContractSigner { signer.clear_signed_at() }, - ); - engine.register_fn( - "comments", - |signer: ContractSigner, comments: String| -> ContractSigner { signer.comments(comments) }, - ); - engine.register_fn( - "clear_comments", - |signer: ContractSigner| -> ContractSigner { signer.clear_comments() }, - ); - - engine.register_get( - "id", - |signer: &mut ContractSigner| -> Result> { - Ok(signer.id.clone()) - }, - ); - engine.register_get( - "name", - |signer: &mut ContractSigner| -> Result> { - Ok(signer.name.clone()) - }, - ); - engine.register_get( - "email", - |signer: &mut ContractSigner| -> Result> { - Ok(signer.email.clone()) - }, - ); - engine.register_get( - "status", - |signer: &mut ContractSigner| -> Result> { - Ok(signer.status.clone()) - }, - ); - engine.register_get( - "signed_at_ts", - |signer: &mut ContractSigner| -> Result> { - Ok(signer - .signed_at - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - engine.register_get( - "comments", - |signer: &mut ContractSigner| -> Result> { - Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from)) - }, - ); - engine.register_get( - "signed_at", - |signer: &mut ContractSigner| -> Result> { - Ok(signer - .signed_at - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts))) - }, - ); - - // --- Contract --- - engine.register_type_with_name::("Contract"); - engine.register_fn( - "new_contract", - move |context: NativeCallContext, - base_id_i64: i64, - contract_id: String| - -> Result> { - let base_id = i64_to_u32( - base_id_i64, - context.position(), - "base_id", - "new_contract", - )?; - Ok(Contract::new(base_id, contract_id)) - }, - ); - - // Builder methods - engine.register_fn("title", |contract: Contract, title: String| -> Contract { - contract.title(title) - }); - engine.register_fn( - "description", - |contract: Contract, description: String| -> Contract { contract.description(description) }, - ); - engine.register_fn( - "contract_type", - |contract: Contract, contract_type: String| -> Contract { - contract.contract_type(contract_type) - }, - ); - engine.register_fn( - "status", - |contract: Contract, status: ContractStatus| -> Contract { contract.status(status) }, - ); - engine.register_fn( - "created_by", - |contract: Contract, created_by: String| -> Contract { contract.created_by(created_by) }, - ); - engine.register_fn( - "terms_and_conditions", - |contract: Contract, terms: String| -> Contract { contract.terms_and_conditions(terms) }, - ); - - engine.register_fn( - "start_date", - |context: NativeCallContext, - contract: Contract, - start_date_i64: i64| - -> Result> { - let start_date_u64 = i64_to_u64( - start_date_i64, - context.position(), - "start_date", - "Contract.start_date", - )?; - Ok(contract.start_date(start_date_u64)) - }, - ); - engine.register_fn("clear_start_date", |contract: Contract| -> Contract { - contract.clear_start_date() - }); - - engine.register_fn( - "end_date", - |context: NativeCallContext, - contract: Contract, - end_date_i64: i64| - -> Result> { - let end_date_u64 = i64_to_u64( - end_date_i64, - context.position(), - "end_date", - "Contract.end_date", - )?; - Ok(contract.end_date(end_date_u64)) - }, - ); - engine.register_fn("clear_end_date", |contract: Contract| -> Contract { - contract.clear_end_date() - }); - - engine.register_fn( - "renewal_period_days", - |context: NativeCallContext, - contract: Contract, - days_i64: i64| - -> Result> { - let days_i32 = i64_to_i32( - days_i64, - context.position(), - "renewal_period_days", - "Contract.renewal_period_days", - )?; - Ok(contract.renewal_period_days(days_i32)) - }, - ); - engine.register_fn( - "clear_renewal_period_days", - |contract: Contract| -> Contract { contract.clear_renewal_period_days() }, - ); - - engine.register_fn( - "next_renewal_date", - |context: NativeCallContext, - contract: Contract, - date_i64: i64| - -> Result> { - let date_u64 = i64_to_u64( - date_i64, - context.position(), - "next_renewal_date", - "Contract.next_renewal_date", - )?; - Ok(contract.next_renewal_date(date_u64)) - }, - ); - engine.register_fn( - "clear_next_renewal_date", - |contract: Contract| -> Contract { contract.clear_next_renewal_date() }, - ); - - engine.register_fn( - "add_signer", - |contract: Contract, signer: ContractSigner| -> Contract { contract.add_signer(signer) }, - ); - engine.register_fn( - "signers", - |contract: Contract, signers_array: Array| -> Contract { - let signers_vec = signers_array - .into_iter() - .filter_map(|s| s.try_cast::()) - .collect(); - contract.signers(signers_vec) - }, - ); - - engine.register_fn( - "add_revision", - |contract: Contract, revision: ContractRevision| -> Contract { - contract.add_revision(revision) - }, - ); - engine.register_fn( - "revisions", - |contract: Contract, revisions_array: Array| -> Contract { - let revisions_vec = revisions_array - .into_iter() - .filter_map(|r| r.try_cast::()) - .collect(); - contract.revisions(revisions_vec) - }, - ); - - engine.register_fn( - "current_version", - |context: NativeCallContext, - contract: Contract, - version_i64: i64| - -> Result> { - let version_u32 = i64_to_u32( - version_i64, - context.position(), - "current_version", - "Contract.current_version", - )?; - Ok(contract.current_version(version_u32)) - }, - ); - - engine.register_fn( - "last_signed_date", - |context: NativeCallContext, - contract: Contract, - date_i64: i64| - -> Result> { - let date_u64 = i64_to_u64( - date_i64, - context.position(), - "last_signed_date", - "Contract.last_signed_date", - )?; - Ok(contract.last_signed_date(date_u64)) - }, - ); - engine.register_fn("clear_last_signed_date", |contract: Contract| -> Contract { - contract.clear_last_signed_date() - }); - - // Getters for Contract - engine.register_get( - "id", - |contract: &mut Contract| -> Result> { - Ok(contract.base_data.id as i64) - }, - ); - engine.register_get( - "created_at_ts", - |contract: &mut Contract| -> Result> { - Ok(contract.base_data.created_at as i64) - }, - ); - engine.register_get( - "updated_at_ts", - |contract: &mut Contract| -> Result> { - Ok(contract.base_data.modified_at as i64) - }, - ); - engine.register_get( - "contract_id", - |contract: &mut Contract| -> Result> { - Ok(contract.contract_id.clone()) - }, - ); - engine.register_get( - "title", - |contract: &mut Contract| -> Result> { - Ok(contract.title.clone()) - }, - ); - engine.register_get( - "description", - |contract: &mut Contract| -> Result> { - Ok(contract.description.clone()) - }, - ); - engine.register_get( - "contract_type", - |contract: &mut Contract| -> Result> { - Ok(contract.contract_type.clone()) - }, - ); - engine.register_get( - "status", - |contract: &mut Contract| -> Result> { - Ok(contract.status.clone()) - }, - ); - engine.register_get( - "created_by", - |contract: &mut Contract| -> Result> { - Ok(contract.created_by.clone()) - }, - ); - engine.register_get( - "terms_and_conditions", - |contract: &mut Contract| -> Result> { - Ok(contract.terms_and_conditions.clone()) - }, - ); - - engine.register_get( - "start_date", - |contract: &mut Contract| -> Result> { - Ok(contract - .start_date - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - engine.register_get( - "end_date", - |contract: &mut Contract| -> Result> { - Ok(contract - .end_date - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - engine.register_get( - "renewal_period_days", - |contract: &mut Contract| -> Result> { - Ok(contract - .renewal_period_days - .map_or(Dynamic::UNIT, |days| Dynamic::from(days as i64))) - }, - ); - engine.register_get( - "next_renewal_date", - |contract: &mut Contract| -> Result> { - Ok(contract - .next_renewal_date - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - engine.register_get( - "last_signed_date", - |contract: &mut Contract| -> Result> { - Ok(contract - .last_signed_date - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - - engine.register_get( - "current_version", - |contract: &mut Contract| -> Result> { - Ok(contract.current_version as i64) - }, - ); - - engine.register_get( - "signers", - |contract: &mut Contract| -> Result> { - let rhai_array = contract - .signers - .iter() - .cloned() - .map(Dynamic::from) - .collect::(); - Ok(rhai_array) - }, - ); - engine.register_get( - "revisions", - |contract: &mut Contract| -> Result> { - let rhai_array = contract - .revisions - .iter() - .cloned() - .map(Dynamic::from) - .collect::(); - Ok(rhai_array) - }, - ); - - // Method set_status - engine.register_fn( - "set_contract_status", - |contract: &mut Contract, status: ContractStatus| { - contract.set_status(status); - }, - ); - - // --- Database Interaction --- - let captured_db_for_set = Arc::clone(&db); - engine.register_fn( - "set_contract", - move |contract: Contract| -> Result<(), Box> { - captured_db_for_set.set(&contract).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!( - "Failed to set Contract (ID: {}): {:?}", - contract.base_data.id, e - ) - .into(), - Position::NONE, - )) - }) - }, - ); - - let captured_db_for_get = Arc::clone(&db); - engine.register_fn( - "get_contract_by_id", - move |context: NativeCallContext, id_i64: i64| -> Result> { - let id_u32 = i64_to_u32(id_i64, context.position(), "id", "get_contract_by_id")?; - - captured_db_for_get - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Error getting Contract (ID: {}): {}", id_u32, e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Contract with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); -} diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index 8587cf3..6b9363c 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -28,15 +28,4 @@ pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use governance::{AttachedFile, Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption}; pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; pub use library::collection::Collection; -pub use library::items::{Image, Markdown, Pdf}; - -#[cfg(feature = "rhai")] -pub use biz::register_biz_rhai_module; -#[cfg(feature = "rhai")] -pub use calendar::register_calendar_rhai_module; -#[cfg(feature = "rhai")] -pub use circle::register_circle_rhai_module; -pub use flow::register_flow_rhai_module; -pub use legal::register_legal_rhai_module; -#[cfg(feature = "rhai")] -pub use projects::register_projects_rhai_module; +pub use library::items::{Image, Markdown, Pdf}; \ No newline at end of file diff --git a/heromodels/src/models/object/object.rs b/heromodels/src/models/object/object.rs index 8c5bcd5..8ebca6d 100644 --- a/heromodels/src/models/object/object.rs +++ b/heromodels/src/models/object/object.rs @@ -1,16 +1,13 @@ -use std::sync::Arc; - -use crate::db::{hero::OurDB, Collection, Db}; use heromodels_core::BaseModelData; use heromodels_derive::model; -// Temporarily removed to fix compilation issues -// use rhai_autobind_macros::rhai_model_export; -use rhai::{CustomType, TypeBuilder}; +use rhai::CustomType; +use rhailib_derive::RhaiApi; use serde::{Deserialize, Serialize}; +use rhai::TypeBuilder; /// Represents an event in a contact #[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default, RhaiApi)] pub struct Object { /// Base model data pub base_data: BaseModelData, diff --git a/heromodels/src/models/projects/mod.rs b/heromodels/src/models/projects/mod.rs index afdab0e..6e58770 100644 --- a/heromodels/src/models/projects/mod.rs +++ b/heromodels/src/models/projects/mod.rs @@ -23,9 +23,3 @@ pub use task_enums::*; // pub use kanban::*; // pub use sprint::*; // pub use story::*; - -#[cfg(feature = "rhai")] -pub mod rhai; - -#[cfg(feature = "rhai")] -pub use rhai::register_projects_rhai_module; diff --git a/heromodels/src/models/projects/rhai.rs b/heromodels/src/models/projects/rhai.rs deleted file mode 100644 index 6f38196..0000000 --- a/heromodels/src/models/projects/rhai.rs +++ /dev/null @@ -1,408 +0,0 @@ -// heromodels/src/models/projects/rhai.rs - -use crate::db::hero::OurDB; -use crate::db::{Collection, Db}; -use heromodels_core::{BaseModelDataOps, Model}; -use rhai::{Dynamic, Engine, EvalAltResult, Position}; -use std::sync::Arc; - -// Import models from the projects::base module -use super::base::{ItemType, /* Label, */ Priority, Project, Status}; // Label commented out as it's unused for now - -// Helper function for ID conversion (if needed, similar to other rhai.rs files) -fn id_from_i64(val: i64) -> Result> { - if val < 0 { - Err(EvalAltResult::ErrorArithmetic( - format!("ID value cannot be negative: {}", val), - rhai::Position::NONE, - ) - .into()) - } else { - Ok(val as u32) - } -} - -pub fn register_projects_rhai_module(engine: &mut Engine, db: Arc) { - // Register enums as constants (example for Priority) - engine.register_static_module("Priority", { - let mut module = rhai::Module::new(); - module.set_var("Critical", Priority::Critical); - module.set_var("High", Priority::High); - module.set_var("Medium", Priority::Medium); - module.set_var("Low", Priority::Low); - module.set_var("None", Priority::None); - module.into() - }); - - engine.register_static_module("Status", { - let mut module = rhai::Module::new(); - module.set_var("Todo", Status::Todo); - module.set_var("InProgress", Status::InProgress); - module.set_var("Review", Status::Review); - module.set_var("Done", Status::Done); - module.set_var("Archived", Status::Archived); - module.into() - }); - - engine.register_static_module("ItemType", { - let mut module = rhai::Module::new(); - module.set_var("Epic", ItemType::Epic); - module.set_var("Story", ItemType::Story); - module.set_var("Task", ItemType::Task); - module.set_var("Bug", ItemType::Bug); - module.set_var("Improvement", ItemType::Improvement); - module.set_var("Feature", ItemType::Feature); - module.into() - }); - - // --- Enum Type Registration --- - engine.register_type_with_name::("Priority"); - engine.register_fn("to_string", |p: &mut Priority| ToString::to_string(p)); - - engine.register_type_with_name::("Status"); - engine.register_fn("to_string", |s: &mut Status| ToString::to_string(s)); - - engine.register_type_with_name::("ItemType"); - engine.register_fn("to_string", |it: &mut ItemType| ToString::to_string(it)); - - // --- Project Registration --- - engine.register_type_with_name::("Project"); - - // Constructor for Project - // Zero-argument constructor - engine.register_fn("new_project", || -> Result> { - // Assuming Project::new() or Project::default() can be used. - // If Project::new() requires args, this needs adjustment or Project needs Default impl. - Ok(Project::new(0, "".to_string(), "".to_string(), 0)) - }); - - // Multi-argument constructor (renamed) - engine.register_fn( - "new_project_with_details", - |id_i64: i64, - name: String, - description: String, - owner_id_i64: i64| - -> Result> { - Ok(Project::new( - id_from_i64(id_i64)?, - name, - description, - id_from_i64(owner_id_i64)?, - )) - }, - ); - - // Getters for Project - engine.register_get("id", |p: &mut Project| -> Result> { - Ok(p.get_id() as i64) - }); - engine.register_get( - "name", - |p: &mut Project| -> Result> { Ok(p.name.clone()) }, - ); - engine.register_get( - "description", - |p: &mut Project| -> Result> { Ok(p.description.clone()) }, - ); - engine.register_get( - "owner_id", - |p: &mut Project| -> Result> { Ok(p.owner_id as i64) }, - ); - engine.register_get( - "member_ids", - |p: &mut Project| -> Result> { - Ok(p.member_ids - .iter() - .map(|&id| rhai::Dynamic::from(id as i64)) - .collect()) - }, - ); - engine.register_get( - "board_ids", - |p: &mut Project| -> Result> { - Ok(p.board_ids - .iter() - .map(|&id| rhai::Dynamic::from(id as i64)) - .collect()) - }, - ); - engine.register_get( - "sprint_ids", - |p: &mut Project| -> Result> { - Ok(p.sprint_ids - .iter() - .map(|&id| rhai::Dynamic::from(id as i64)) - .collect()) - }, - ); - engine.register_get( - "epic_ids", - |p: &mut Project| -> Result> { - Ok(p.epic_ids - .iter() - .map(|&id| rhai::Dynamic::from(id as i64)) - .collect()) - }, - ); - engine.register_get( - "tags", - |p: &mut Project| -> Result> { - Ok(p.tags - .iter() - .map(|tag| rhai::Dynamic::from(tag.clone())) - .collect()) - }, - ); - engine.register_get( - "created_at", - |p: &mut Project| -> Result> { Ok(p.base_data.created_at) }, - ); - engine.register_get( - "modified_at", - |p: &mut Project| -> Result> { Ok(p.base_data.modified_at) }, - ); - engine.register_get( - "comments", - |p: &mut Project| -> Result> { - Ok(p.base_data - .comments - .iter() - .map(|&id| rhai::Dynamic::from(id as i64)) - .collect()) - }, - ); - engine.register_get( - "status", - |p: &mut Project| -> Result> { Ok(p.status.clone()) }, - ); - engine.register_get( - "priority", - |p: &mut Project| -> Result> { Ok(p.priority.clone()) }, - ); - engine.register_get( - "item_type", - |p: &mut Project| -> Result> { Ok(p.item_type.clone()) }, - ); - - // Builder methods for Project - // let db_clone = db.clone(); // This was unused - engine.register_fn( - "name", - |p: Project, name: String| -> Result> { Ok(p.name(name)) }, - ); - engine.register_fn( - "description", - |p: Project, description: String| -> Result> { - Ok(p.description(description)) - }, - ); - engine.register_fn( - "owner_id", - |p: Project, owner_id_i64: i64| -> Result> { - Ok(p.owner_id(id_from_i64(owner_id_i64)?)) - }, - ); - engine.register_fn( - "add_member_id", - |p: Project, member_id_i64: i64| -> Result> { - Ok(p.add_member_id(id_from_i64(member_id_i64)?)) - }, - ); - engine.register_fn( - "member_ids", - |p: Project, member_ids_i64: rhai::Array| -> Result> { - let ids = member_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.member_ids(ids)) - }, - ); - engine.register_fn( - "add_board_id", - |p: Project, board_id_i64: i64| -> Result> { - Ok(p.add_board_id(id_from_i64(board_id_i64)?)) - }, - ); - engine.register_fn( - "board_ids", - |p: Project, board_ids_i64: rhai::Array| -> Result> { - let ids = board_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.board_ids(ids)) - }, - ); - engine.register_fn( - "add_sprint_id", - |p: Project, sprint_id_i64: i64| -> Result> { - Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) - }, - ); - engine.register_fn( - "sprint_ids", - |p: Project, sprint_ids_i64: rhai::Array| -> Result> { - let ids = sprint_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.sprint_ids(ids)) - }, - ); - engine.register_fn( - "add_epic_id", - |p: Project, epic_id_i64: i64| -> Result> { - Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) - }, - ); - engine.register_fn( - "epic_ids", - |p: Project, epic_ids_i64: rhai::Array| -> Result> { - let ids = epic_ids_i64 - .into_iter() - .map(|id_dyn: Dynamic| { - let val_i64 = id_dyn.clone().try_cast::().ok_or_else(|| { - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected integer for ID".to_string(), - id_dyn.type_name().to_string(), - Position::NONE, - )) - })?; - id_from_i64(val_i64) - }) - .collect::, Box>>()?; - Ok(p.epic_ids(ids)) - }, - ); - engine.register_fn( - "add_tag", - |p: Project, tag: String| -> Result> { Ok(p.add_tag(tag)) }, - ); - engine.register_fn( - "tags", - |p: Project, tags_dyn: rhai::Array| -> Result> { - let tags_vec = tags_dyn - .into_iter() - .map(|tag_dyn: Dynamic| { - tag_dyn.clone().into_string().map_err(|_err| { - // _err is Rhai's internal error, we create a new one - Box::new(EvalAltResult::ErrorMismatchDataType( - "Expected string for tag".to_string(), - tag_dyn.type_name().to_string(), - Position::NONE, - )) - }) - }) - .collect::, Box>>()?; - Ok(p.tags(tags_vec)) - }, - ); - - engine.register_fn( - "status", - |p: Project, status: Status| -> Result> { - Ok(p.status(status)) - }, - ); - engine.register_fn( - "priority", - |p: Project, priority: Priority| -> Result> { - Ok(p.priority(priority)) - }, - ); - engine.register_fn( - "item_type", - |p: Project, item_type: ItemType| -> Result> { - Ok(p.item_type(item_type)) - }, - ); - // Base ModelData builders - engine.register_fn( - "add_base_comment", - |p: Project, comment_id_i64: i64| -> Result> { - Ok(p.add_base_comment(id_from_i64(comment_id_i64)?)) - }, - ); - - // --- Database Interaction Functions --- - let db_clone_set = db.clone(); - engine.register_fn( - "set_project", - move |project: Project| -> Result<(), Box> { - let collection = db_clone_set.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; - - collection.set(&project).map(|_| ()).map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to save project".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - }) - }, - ); - - let db_clone_get = db.clone(); - engine.register_fn( - "get_project_by_id", - move |id_i64: i64| -> Result> { - let id = id_from_i64(id_i64)?; - let collection = db_clone_get.collection::().map_err(|e| { - Box::new(EvalAltResult::ErrorSystem( - "Failed to access project collection".to_string(), - format!("DB operation failed: {:?}", e).into(), - )) - })?; - - match collection.get_by_id(id) { - Ok(Some(project)) => Ok(Dynamic::from(project)), - Ok(None) => Ok(Dynamic::UNIT), // Represents '()' in Rhai - Err(e) => Err(Box::new(EvalAltResult::ErrorSystem( - "Failed to retrieve project by ID".to_string(), - format!("DB operation failed: {:?}", e).into(), - ))), - } - }, - ); - - // TODO: Register Rhai bindings for the `Label` model if needed, or remove unused import. - // Register Label type and its methods/getters - // engine.register_type_with_name::