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 6862bea..f4f8c5d 100644 Binary files a/heromodels/.DS_Store and b/heromodels/.DS_Store differ 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;