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/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/_archive/rhai.rs b/_archive/rhai.rs new file mode 100644 index 0000000..99e9290 --- /dev/null +++ b/_archive/rhai.rs @@ -0,0 +1,1051 @@ +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::{ + Book as RhaiBook, Image as RhaiImage, Markdown as RhaiMarkdown, Pdf as RhaiPdf, + Slide as RhaiSlide, Slideshow 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(|_| { + 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_pretty(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); + +impl From> for RhaiCollectionArray { + fn from(collections: Vec) -> Self { + RhaiCollectionArray(collections) + } +} + +#[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() + } + + // --- 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() + } + + #[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, + slide: RhaiSlide, + ) -> Result> { + let owned = mem::take(slides); + *slides = owned.add_slide(slide); + 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 = "slides", pure)] + pub fn get_slides_slides(slides: &mut RhaiSlides) -> Vec { + slides.slides.clone() + } +} + +pub fn register_library_rhai_module(engine: &mut Engine) { + 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(()) + }, + ); + + // --- Slideshow 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!("Slideshow 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-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/.DS_Store b/heromodels/.DS_Store new file mode 100644 index 0000000..f4f8c5d Binary files /dev/null and b/heromodels/.DS_Store differ diff --git a/heromodels/Cargo.lock b/heromodels/Cargo.lock index d8cd394..2bf5b50 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" @@ -154,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" @@ -177,12 +177,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 +187,20 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" name = "heromodels" version = "0.1.0" dependencies = [ - "adapter_macros", "bincode", "chrono", + "derive", "heromodels-derive", "heromodels_core", "ourdb", "rhai", - "rhai_autobind_macros", "rhai_client_macros", - "rhai_wrapper", "serde", "serde_json", "strum", "strum_macros", "tst", + "uuid", ] [[package]] @@ -216,7 +209,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -419,16 +412,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" @@ -436,7 +419,7 @@ dependencies = [ "proc-macro2", "quote", "rhai", - "syn", + "syn 2.0.101", ] [[package]] @@ -447,26 +430,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "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", + "syn 2.0.101", ] [[package]] @@ -508,7 +472,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -570,11 +534,22 @@ 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", - "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]] @@ -611,7 +586,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -643,6 +618,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" @@ -692,7 +678,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -714,7 +700,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -749,7 +735,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -760,7 +746,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -813,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 4b317e8..e69ae48 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -14,13 +14,12 @@ 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" } +rhailib_derive = { package = "derive", path = "../../rhailib/src/derive" } 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 = [] @@ -40,51 +39,11 @@ path = "examples/governance_proposal_example/main.rs" name = "finance_example" path = "examples/finance_example/main.rs" -[[example]] -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" - -[[example]] -name = "finance_rhai" -path = "examples/finance_rhai/example.rs" - -[[example]] -name = "governance_rhai" -path = "examples/governance_rhai/example.rs" - -[[example]] -name = "governance_rhai_client" -path = "examples/governance_rhai_client/example.rs" - [[example]] name = "flow_example" path = "examples/flow_example.rs" -[[example]] -name = "legal_rhai" -path = "examples/legal_rhai/example.rs" -required-features = ["rhai"] - - -[[example]] -name = "project_rhai" -path = "examples/project_rhai/example.rs" -required-features = ["rhai"] - [[example]] name = "biz_rhai" path = "examples/biz_rhai/example.rs" -required-features = ["rhai"] - -[[example]] -name = "payment_flow_rhai" -path = "examples/biz_rhai/payment_flow_example.rs" -required-features = ["rhai"] +required-features = ["rhai"] \ No newline at end of file diff --git a/heromodels/data/0.db b/heromodels/data/0.db index c68be08..531af47 100644 Binary files a/heromodels/data/0.db and b/heromodels/data/0.db differ diff --git a/heromodels/data/lookup/data b/heromodels/data/lookup/data index 42244a8..bdcc1ae 100644 Binary files a/heromodels/data/lookup/data and b/heromodels/data/lookup/data differ 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/biz.rs b/heromodels/examples/biz_rhai/biz.rs deleted file mode 100644 index ad86a7b..0000000 --- a/heromodels/examples/biz_rhai/biz.rs +++ /dev/null @@ -1,306 +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 PendingPayment: ${CompanyStatusConstants::PendingPayment}`); -print(`CompanyStatus Active: ${CompanyStatusConstants::Active}`); -print(`CompanyStatus Suspended: ${CompanyStatusConstants::Suspended}`); -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.") - // Note: status defaults to PendingPayment for new companies - .fiscal_year_end("12-31") - .set_base_created_at(1672531200) - .set_base_modified_at(1672531205); - -print(`Company 1 Name: ${company1.name}, Status: ${company1.status} (default for new companies)`); -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."); - -// Demonstrate payment flow for the company -print("\n--- Payment Processing for Company ---"); -print("Creating payment record for company registration..."); -let payment_intent_id = `pi_demo_${company1.id}`; -let payment = new_payment( - payment_intent_id, - company1.id, - "yearly", - 500.0, // Setup fee - 99.0, // Monthly fee - 1688.0 // Total amount (setup + 12 months) -); - -payment = set_payment(payment); -print(`Payment created: ${payment.payment_intent_id}, Status: ${payment.status}`); - -// Simulate successful payment processing -print("Processing payment..."); -payment = payment.complete_payment(`cus_demo_${company1.id}`); -payment = set_payment(payment); -print(`Payment completed: ${payment.status}`); - -// Update company status to Active after successful payment -print("Updating company status to Active after payment..."); -company1 = company1.status(CompanyStatusConstants::Active); -company1 = set_company(company1); -print(`Company status updated: ${company1.status}`); - -// 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.") - // Note: Company is already Active from payment processing above - .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}`); -print(`Company Status: ${updated_company.status} (already Active from payment)`); -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 e857f5a..0000000 --- a/heromodels/examples/biz_rhai/example.rs +++ /dev/null @@ -1,41 +0,0 @@ -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 std::fs; - -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 f5886ab..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(). // 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 bab62cd..0000000 --- a/heromodels/examples/calendar_rhai/example.rs +++ /dev/null @@ -1,83 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::calendar::rhai::register_rhai_engine_functions; -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 = 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 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(()) -} \ 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 deleted file mode 100644 index 23ea125..0000000 --- a/heromodels/examples/finance_rhai/example.rs +++ /dev/null @@ -1,109 +0,0 @@ -use rhai::{Engine, Scope, EvalAltResult}; -use std::sync::{Arc, Mutex}; -use std::collections::HashMap; -use std::fs; - -// 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_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 deleted file mode 100644 index a6cdd67..0000000 --- a/heromodels/examples/flow_rhai/example.rs +++ /dev/null @@ -1,36 +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/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 6dcc648..0000000 --- a/heromodels/examples/governance_rhai_client/example.rs +++ /dev/null @@ -1,366 +0,0 @@ -use heromodels::db::hero::OurDB; -use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot}; -use rhai::Engine; -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 - -// 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 b5a3e53..0000000 --- a/heromodels/examples/legal_rhai/example.rs +++ /dev/null @@ -1,44 +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 800e397..0000000 --- a/heromodels/examples/legal_rhai/legal.rhai +++ /dev/null @@ -1,174 +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")}`); -print(`Signer 1 Last Reminder: ${format_optional_int(signer1.last_reminder_mail_sent_at, "Never sent")}`); -print(`Signer 1 Signature Data: ${format_optional_string(signer1.signature_data, "No signature")}`); - -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.") - .last_reminder_mail_sent_at(1678880000) // Example reminder timestamp - .signature_data("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="); // Example signature - -print(`Signer 2 ID: ${signer2.id}, Name: ${signer2.name}, Status: ${signer2.status}, Signed At: ${format_optional_int(signer2.signed_at, "N/A")}`); -print(`Signer 2 Last Reminder: ${format_optional_int(signer2.last_reminder_mail_sent_at, "Never sent")}`); -print(`Signer 2 Signature Data Length: ${signer2.signature_data.len()} characters`); - -// --- 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}'`); - -// --- Test Reminder Functionality --- -print("\n--- Testing Reminder Functionality ---"); -let current_time = 1678900000; // Example current timestamp - -// Test reminder functionality on signers -if final_retrieved_contract.signers.len() > 0 { - let test_signer = final_retrieved_contract.signers[0]; - print(`Testing reminder for signer: ${test_signer.name}`); - - let can_send = can_send_reminder(test_signer, current_time); - print(`Can send reminder: ${can_send}`); - - let cooldown_remaining = reminder_cooldown_remaining(test_signer, current_time); - print(`Cooldown remaining: ${format_optional_int(cooldown_remaining, "No cooldown")}`); - - // Simulate sending a reminder - if can_send { - print("Simulating reminder sent..."); - mark_reminder_sent(test_signer, current_time); - print("Reminder timestamp updated"); - - // Check cooldown after sending - let new_cooldown = reminder_cooldown_remaining(test_signer, current_time); - print(`New cooldown: ${format_optional_int(new_cooldown, "No cooldown")} seconds`); - } -} - -// --- Test Signature Functionality --- -print("\n--- Testing Signature Functionality ---"); - -// Test signing with signature data -let test_signer = new_contract_signer("test-signer-001", "Test Signer", "test@example.com"); -print(`Before signing: Status = ${test_signer.status}, Signature Data = ${format_optional_string(test_signer.signature_data, "None")}`); - -// Sign with signature data -sign(test_signer, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==", "I agree to the terms"); -print(`After signing: Status = ${test_signer.status}, Signature Data Length = ${test_signer.signature_data.len()}`); -print(`Comments: ${format_optional_string(test_signer.comments, "No comments")}`); - -// Test signing without signature data -let test_signer2 = new_contract_signer("test-signer-002", "Test Signer 2", "test2@example.com"); -sign_without_signature(test_signer2, "Electronic signature without visual data"); -print(`Signer 2 after signing: Status = ${test_signer2.status}, Signature Data = ${format_optional_string(test_signer2.signature_data, "None")}`); - -// Test simple signing -let test_signer3 = new_contract_signer("test-signer-003", "Test Signer 3", "test3@example.com"); -sign_simple(test_signer3); -print(`Signer 3 after simple signing: Status = ${test_signer3.status}`); - -print("\nLegal Rhai example script finished."); 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 deleted file mode 100644 index 08ac43b..0000000 --- a/heromodels/examples/project_rhai/example.rs +++ /dev/null @@ -1,39 +0,0 @@ -use rhai::{Engine, EvalAltResult, Scope}; -use std::sync::Arc; -use heromodels::db::hero::OurDB; -use heromodels::models::projects::register_projects_rhai_module; -use std::fs; - -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/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/index/0.db b/heromodels/index/0.db index 029921e..7f8214c 100644 Binary files a/heromodels/index/0.db and b/heromodels/index/0.db differ diff --git a/heromodels/index/lookup/data b/heromodels/index/lookup/data index 5b3c62c..708d4df 100644 Binary files a/heromodels/index/lookup/data and b/heromodels/index/lookup/data differ 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 47fd7c4..4aefc14 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -454,14 +454,27 @@ where let mut index_db = self.index.lock().expect("can lock index DB"); let mut data_db = self.data.lock().expect("can lock data DB"); - // Look for the primary key index entry for this model type - let primary_index_key = format!("{}::primary", M::db_prefix()); - let all_object_ids: HashSet = - match Self::get_tst_value(&mut index_db, &primary_index_key)? { - Some(ids) => ids, - None => { - // No primary index found, meaning no objects of this type exist - return Ok(Vec::new()); + let prefix = M::db_prefix(); + let mut all_object_ids: HashSet = HashSet::new(); + + // Use getall to find all index entries (values are serialized HashSet) for the given model prefix. + match index_db.getall(prefix) { + 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) + all_object_ids.extend(ids_set); + } + Err(e) => { + // If deserialization of an ID set fails, propagate as a decode error. + return Err(super::Error::Decode(e)); + } + } } }; diff --git a/heromodels/src/herodb/model_methods.rs b/heromodels/src/herodb/model_methods.rs index 86734c4..c802cb7 100644 --- a/heromodels/src/herodb/model_methods.rs +++ b/heromodels/src/herodb/model_methods.rs @@ -9,6 +9,15 @@ use crate::models::gov::{ // ComplianceRequirement, ComplianceDocument, ComplianceAudit - These don't exist }; use crate::models::circle::{Circle, Member, Name, Wallet}; // Remove Asset +use crate::models::calendar::{Event, Calendar}; + +// Implement model-specific methods for Event +impl_model_methods!(Event, event, events); + +// Implement model-specific methods for Calendar +impl_model_methods!(Calendar, calendar, calendars); + +impl_model_methods!(Attendee, attendee, attendees); // Implement model-specific methods for Product impl_model_methods!(Product, product, products); diff --git a/heromodels/src/lib.rs b/heromodels/src/lib.rs index 8bc137e..410dde7 100644 --- a/heromodels/src/lib.rs +++ b/heromodels/src/lib.rs @@ -6,3 +6,6 @@ pub mod db; // Re-export the procedural macro pub use heromodels_derive::model; + +// Re-export BaseModelData from heromodels_core +pub use heromodels_core::BaseModelData; diff --git a/heromodels/src/models/access/access.rs b/heromodels/src/models/access/access.rs new file mode 100644 index 0000000..f374269 --- /dev/null +++ b/heromodels/src/models/access/access.rs @@ -0,0 +1,143 @@ +use std::sync::Arc; +use crate::models::Circle; +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 Access { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub object_type: String, + #[index] + pub object_id: u32, + #[index] + pub circle_pk: String, + #[index] + pub contact_id: u32, + #[index] + pub group_id: u32, + pub expires_at: Option, +} + +impl Access { + pub fn new() -> Self { + Access { + base_data: BaseModelData::new(), + object_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 + } + + 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_pk(mut self, circle_pk: String) -> Self { + self.circle_pk = circle_pk; + self + } + + pub fn expires_at(mut self, expires_at: Option) -> Self { + self.expires_at = expires_at; + 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, + 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 + let access_records = match db + .collection::() + .expect("Failed to get Access collection") + .get::(&object_id) + { + 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; + } + }; + + println!("Access records: {:#?}", access_records); + + // 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 new file mode 100644 index 0000000..7e39043 --- /dev/null +++ b/heromodels/src/models/access/mod.rs @@ -0,0 +1,5 @@ +// Export contact module +pub mod access; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::access::Access; diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index ae55759..ff30261 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,7 +1,8 @@ -use heromodels_core::BaseModelData; +use heromodels_core::BaseModelDataOps; +use heromodels_core::{BaseModelData, Index}; use heromodels_derive::model; -use rhai::{CustomType, TypeBuilder}; -use serde::{Deserialize, Serialize}; // For #[derive(CustomType)] +use rhai::{CustomType, EvalAltResult, Position, TypeBuilder}; // For #[derive(CustomType)] +use serde::{Deserialize, Serialize}; // --- Enums --- @@ -34,10 +35,30 @@ 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 #[model] -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, CustomType)] // Added CustomType pub struct Company { pub base_data: BaseModelData, pub name: String, @@ -54,16 +75,41 @@ pub struct Company { pub status: CompanyStatus, } +// --- Index Implementations (Example) --- + +pub struct CompanyNameIndex; +impl Index for CompanyNameIndex { + type Model = Company; + type Key = str; + fn key() -> &'static str { + "name" + } +} + +pub struct CompanyRegistrationNumberIndex; +impl Index for CompanyRegistrationNumberIndex { + type Model = Company; + type Key = str; + fn key() -> &'static str { + "registration_number" + } +} + // --- 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(), @@ -76,28 +122,43 @@ impl Company { } } - 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 email(mut self, email: String) -> Self { - self.email = email; + pub fn registration_number(mut self, registration_number: impl ToString) -> Self { + self.registration_number = registration_number.to_string(); self } - pub fn phone(mut self, phone: String) -> Self { - self.phone = phone; + pub fn incorporation_date(mut self, incorporation_date: i64) -> Self { + self.incorporation_date = incorporation_date; self } - pub fn website(mut self, website: String) -> Self { - self.website = website; + 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 address(mut self, address: String) -> Self { - self.address = address; + pub fn email(mut self, email: impl ToString) -> Self { + self.email = email.to_string(); + self + } + + pub fn phone(mut self, phone: impl ToString) -> Self { + self.phone = phone.to_string(); + self + } + + 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 } @@ -106,13 +167,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 } @@ -121,17 +182,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..5a498d9 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,6 +1,7 @@ -use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; -use heromodels_core::Model; +use rhai::{CustomType, TypeBuilder}; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; // ProductType represents the type of a product #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] @@ -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, @@ -27,18 +28,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 +48,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 +71,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 +89,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 +109,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 } @@ -150,30 +138,11 @@ impl Product { self.components.push(component); self } - + pub fn components(mut self, components: Vec) -> Self { self.components = components; 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 deleted file mode 100644 index 327a028..0000000 --- a/heromodels/src/models/biz/rhai.rs +++ /dev/null @@ -1,1270 +0,0 @@ -use super::company::{BusinessType, Company, CompanyStatus}; -use super::payment::{Payment, PaymentStatus}; -use crate::db::Collection; // For db.set and db.get_by_id -use crate::db::hero::OurDB; -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; -use rhai::{Dynamic, Engine, EvalAltResult, Module, Position}; -use std::sync::Arc; - -// 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(|_| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert i64 '{}' to u32 for ID", id_val), - Position::NONE, - )) - }) -} - -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( - "PendingPayment", - Dynamic::from(CompanyStatus::PendingPayment.clone()), - ); - status_constants_module.set_var("Active", Dynamic::from(CompanyStatus::Active.clone())); - status_constants_module.set_var("Suspended", Dynamic::from(CompanyStatus::Suspended.clone())); - status_constants_module.set_var("Inactive", Dynamic::from(CompanyStatus::Inactive.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"); - - // --- Enum Constants: PaymentStatus --- - let mut payment_status_module = Module::new(); - payment_status_module.set_var("Pending", Dynamic::from(PaymentStatus::Pending.clone())); - payment_status_module.set_var("Completed", Dynamic::from(PaymentStatus::Completed.clone())); - payment_status_module.set_var("Failed", Dynamic::from(PaymentStatus::Failed.clone())); - payment_status_module.set_var("Refunded", Dynamic::from(PaymentStatus::Refunded.clone())); - engine.register_static_module("PaymentStatusConstants", payment_status_module.into()); - engine.register_type_with_name::("PaymentStatus"); - - // --- 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, - )) - }) - }, - ); - - // --- Payment --- - engine.register_type_with_name::("Payment"); - - // Constructor for Payment - engine.register_fn( - "new_payment", - |payment_intent_id: String, - company_id: i64, - payment_plan: String, - setup_fee: f64, - monthly_fee: f64, - total_amount: f64| - -> Result> { - Ok(Payment::new( - payment_intent_id, - id_from_i64(company_id)?, - payment_plan, - setup_fee, - monthly_fee, - total_amount, - )) - }, - ); - - // Getters for Payment - engine.register_get( - "id", - |payment: &mut Payment| -> Result> { Ok(payment.get_id() as i64) }, - ); - engine.register_get( - "created_at", - |payment: &mut Payment| -> Result> { - Ok(payment.base_data.created_at) - }, - ); - engine.register_get( - "modified_at", - |payment: &mut Payment| -> Result> { - Ok(payment.base_data.modified_at) - }, - ); - engine.register_get( - "payment_intent_id", - |payment: &mut Payment| -> Result> { - Ok(payment.payment_intent_id.clone()) - }, - ); - engine.register_get( - "company_id", - |payment: &mut Payment| -> Result> { - Ok(payment.company_id as i64) - }, - ); - engine.register_get( - "payment_plan", - |payment: &mut Payment| -> Result> { - Ok(payment.payment_plan.clone()) - }, - ); - engine.register_get( - "setup_fee", - |payment: &mut Payment| -> Result> { Ok(payment.setup_fee) }, - ); - engine.register_get( - "monthly_fee", - |payment: &mut Payment| -> Result> { Ok(payment.monthly_fee) }, - ); - engine.register_get( - "total_amount", - |payment: &mut Payment| -> Result> { Ok(payment.total_amount) }, - ); - engine.register_get( - "status", - |payment: &mut Payment| -> Result> { - Ok(payment.status.clone()) - }, - ); - engine.register_get( - "stripe_customer_id", - |payment: &mut Payment| -> Result, Box> { - Ok(payment.stripe_customer_id.clone()) - }, - ); - - // Builder methods for Payment - engine.register_fn( - "payment_intent_id", - |payment: Payment, payment_intent_id: String| -> Result> { - Ok(payment.payment_intent_id(payment_intent_id)) - }, - ); - engine.register_fn( - "company_id", - |payment: Payment, company_id: i64| -> Result> { - Ok(payment.company_id(id_from_i64(company_id)?)) - }, - ); - engine.register_fn( - "payment_plan", - |payment: Payment, payment_plan: String| -> Result> { - Ok(payment.payment_plan(payment_plan)) - }, - ); - engine.register_fn( - "setup_fee", - |payment: Payment, setup_fee: f64| -> Result> { - Ok(payment.setup_fee(setup_fee)) - }, - ); - engine.register_fn( - "monthly_fee", - |payment: Payment, monthly_fee: f64| -> Result> { - Ok(payment.monthly_fee(monthly_fee)) - }, - ); - engine.register_fn( - "total_amount", - |payment: Payment, total_amount: f64| -> Result> { - Ok(payment.total_amount(total_amount)) - }, - ); - engine.register_fn( - "status", - |payment: Payment, status: PaymentStatus| -> Result> { - Ok(payment.status(status)) - }, - ); - engine.register_fn( - "stripe_customer_id", - |payment: Payment, - stripe_customer_id: Option| - -> Result> { - Ok(payment.stripe_customer_id(stripe_customer_id)) - }, - ); - - // Business logic methods for Payment - engine.register_fn( - "complete_payment", - |payment: Payment, - stripe_customer_id: Option| - -> Result> { - Ok(payment.complete_payment(stripe_customer_id)) - }, - ); - // Overload for string parameter - engine.register_fn( - "complete_payment", - |payment: Payment, stripe_customer_id: String| -> Result> { - Ok(payment.complete_payment(Some(stripe_customer_id))) - }, - ); - engine.register_fn( - "fail_payment", - |payment: Payment| -> Result> { Ok(payment.fail_payment()) }, - ); - engine.register_fn( - "refund_payment", - |payment: Payment| -> Result> { Ok(payment.refund_payment()) }, - ); - - // Status check methods for Payment - engine.register_fn( - "is_completed", - |payment: &mut Payment| -> Result> { Ok(payment.is_completed()) }, - ); - engine.register_fn( - "is_pending", - |payment: &mut Payment| -> Result> { Ok(payment.is_pending()) }, - ); - engine.register_fn( - "has_failed", - |payment: &mut Payment| -> Result> { Ok(payment.has_failed()) }, - ); - engine.register_fn( - "is_refunded", - |payment: &mut Payment| -> Result> { Ok(payment.is_refunded()) }, - ); - - // DB functions for Payment - let captured_db_for_set_payment = Arc::clone(&db); - engine.register_fn( - "set_payment", - move |payment: Payment| -> Result> { - let original_id_for_error = payment.get_id(); - captured_db_for_set_payment - .set(&payment) - .map(|(_id, updated_payment)| updated_payment) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!( - "Failed to set Payment (Original ID: {}): {}", - original_id_for_error, e - ) - .into(), - Position::NONE, - )) - }) - }, - ); - - let captured_db_for_get_payment = Arc::clone(&db); - engine.register_fn( - "get_payment_by_id", - move |id_i64: i64| -> Result> { - let id_u32 = id_from_i64(id_i64)?; - captured_db_for_get_payment - .get_by_id(id_u32) - .map_err(|e| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Error getting Payment (ID: {}): {}", id_u32, e).into(), - Position::NONE, - )) - })? - .ok_or_else(|| { - Box::new(EvalAltResult::ErrorRuntime( - format!("Payment with ID {} not found", id_u32).into(), - Position::NONE, - )) - }) - }, - ); - - engine.register_global_module(module.into()); -} diff --git a/heromodels/src/models/biz/sale.rs b/heromodels/src/models/biz/sale.rs index 2d6d39f..d7168cb 100644 --- a/heromodels/src/models/biz/sale.rs +++ b/heromodels/src/models/biz/sale.rs @@ -1,5 +1,6 @@ +use heromodels_core::{BaseModelData, BaseModelDataOps, Model}; +use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model}; /// Represents the status of a sale. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -16,7 +17,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, CustomType)] pub struct SaleItem { pub product_id: u32, pub name: String, // Denormalized product name at time of sale @@ -27,14 +28,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 +46,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 +73,12 @@ impl SaleItem { } /// Represents a sale of products or services. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, CustomType)] 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 +100,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 +128,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 +163,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..ce7b58a 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,5 +1,6 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, 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, @@ -29,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 } } @@ -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 } diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 13732f5..c88069d 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; use rhai::{CustomType, TypeBuilder}; @@ -6,7 +5,7 @@ use rhai_autobind_macros::rhai_model_export; 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 = 0, Declined = 1, @@ -22,9 +21,31 @@ pub enum EventStatus { Cancelled = 2, } +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 -#[model] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] pub struct Attendee { /// Base model data pub base_data: BaseModelData, @@ -71,15 +92,16 @@ pub struct Event { /// Base model data 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, - /// List of attendee IDs for the event - pub attendees: Vec, + /// 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, /// Color for the event (hex color code) @@ -110,46 +132,18 @@ impl Event { pub fn from_json(json: &str) -> Result { serde_json::from_str(json) } +} - /// Creates a new event with auto-generated ID - pub fn new(title: impl ToString, start_time: DateTime, end_time: DateTime) -> Self { +impl Event { + /// Creates a new event + pub fn new() -> Self { + let now = chrono::Utc::now().timestamp(); Self { - base_data: BaseModelData::new(), // ID will be auto-generated by OurDB - title: title.to_string(), + base_data: BaseModelData::new(), + title: String::new(), description: None, - start_time, - end_time, - attendees: Vec::new(), - location: None, - color: Some("#4285F4".to_string()), // Default blue color - all_day: false, - created_by: None, - status: EventStatus::Published, - is_recurring: false, - timezone: None, - category: None, - reminder_minutes: None, - } - } - - /// Creates a new event with optional ID (use None for auto-generated ID) - pub fn new_with_id( - id: Option, - title: impl ToString, - start_time: DateTime, - end_time: DateTime, - ) -> Self { - let mut base_data = BaseModelData::new(); - if let Some(id) = id { - base_data.update_id(id); - } - - Self { - base_data, - title: title.to_string(), - description: None, - start_time, - end_time, + start_time: now, + end_time: now + 3600, // Add 1 hour in seconds attendees: Vec::new(), location: None, color: Some("#4285F4".to_string()), // Default blue color @@ -238,18 +232,26 @@ impl Event { self } - /// Removes an attendee from the event by attendee ID - pub fn remove_attendee(mut self, attendee_id: u32) -> Self { - self.attendees.retain(|&a_id| a_id != attendee_id); + /// Removes an attendee from the event by user_id + pub fn remove_attendee(mut self, contact_id: u32) -> Self { + self.attendees.retain(|a| a.contact_id != contact_id); + self + } + + /// 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) + { + attendee.status = status; + } self } /// Reschedules the event to new start and end times - pub fn reschedule( - mut self, - new_start_time: DateTime, - new_end_time: DateTime, - ) -> 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; @@ -261,13 +263,17 @@ 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 pub base_data: BaseModelData, /// Name of the calendar + #[index] pub name: String, /// Optional description of the calendar pub description: Option, diff --git a/heromodels/src/models/calendar/mod.rs b/heromodels/src/models/calendar/mod.rs index ec9f694..4bce0ce 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, AttendanceStatus, and EventStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs -pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event, EventStatus}; -pub use rhai::register_rhai_engine_functions as register_calendar_rhai_module; +pub use self::calendar::{AttendanceStatus, Attendee, Calendar, Event, EventStatus}; \ No newline at end of file diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs deleted file mode 100644 index 9dc1b77..0000000 --- a/heromodels/src/models/calendar/rhai.rs +++ /dev/null @@ -1,131 +0,0 @@ -use rhai::{Engine, EvalAltResult, ImmutableString, NativeCallContext}; -use std::sync::Arc; - -use super::calendar::{Attendee, Calendar, Event}; -use crate::db::hero::OurDB; -use adapter_macros::rhai_timestamp_helpers; -use adapter_macros::{adapt_rhai_i64_input_fn, adapt_rhai_i64_input_method}; -use heromodels_core::BaseModelData; - -// Helper function for get_all_calendars registration - -fn new_calendar_rhai(name: String) -> Result> { - Ok(Calendar::new(None, name)) -} - -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)) -} - -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. - - 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", - adapt_rhai_i64_input_method!(Event, add_attendee, u32), - ); - engine.register_fn( - "remove_attendee", - adapt_rhai_i64_input_method!(Event, remove_attendee, u32), - ); - - 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()); - - // 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()) - }, - ); - - // Register getter for Calendar.base_data - engine.register_get( - "base_data", - |c: &mut Calendar| -> Result> { Ok(c.base_data.clone()) }, - ); - - // Register getters for BaseModelData - engine.register_get( - "id", - |bmd: &mut BaseModelData| -> Result> { Ok(bmd.id.into()) }, - ); - - // 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. - - // 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 -} diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs new file mode 100644 index 0000000..d5e996d --- /dev/null +++ b/heromodels/src/models/circle/circle.rs @@ -0,0 +1,101 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents the visual theme for a circle. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +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, +} + +/// 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, + /// 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.) + pub theme: ThemeData, +} + +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, + members: Vec::new(), + theme: ThemeData::default(), + } + } + + /// 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 the entire theme for the circle + pub fn theme(mut self, theme: ThemeData) -> 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 + } + + /// 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 new file mode 100644 index 0000000..864b982 --- /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, ThemeData}; +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..d51c1cb --- /dev/null +++ b/heromodels/src/models/circle/rhai.rs @@ -0,0 +1,412 @@ +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, ThemeData}; +type RhaiCircle = Circle; +type RhaiThemeData = ThemeData; + +use crate::db::Collection; +use crate::db::hero::OurDB; +use serde::Serialize; +use serde_json; + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + T: CustomType + Clone + Serialize, +{ + let to_json_fn = |obj: &mut T| -> Result> { + serde_json::to_string(obj).map_err(|e| e.to_string().into()) + }; + 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_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 --- + #[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: RhaiThemeData, + ) -> 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> { + 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> { + let owned_circle = mem::take(circle); + *circle = owned_circle.add_member(added_member); + Ok(circle.clone()) + } + + // Circle Getters + #[rhai_fn(name = "get_id", pure)] + pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { + circle.base_data.id as i64 + } + #[rhai_fn(name = "get_created_at", pure)] + pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.created_at + } + #[rhai_fn(name = "get_modified_at", pure)] + pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { + circle.base_data.modified_at + } + + #[rhai_fn(name = "get_title", pure)] + pub fn get_circle_title(circle: &mut RhaiCircle) -> String { + circle.title.clone() + } + #[rhai_fn(name = "get_description", pure)] + pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { + circle.description.clone() + } + #[rhai_fn(name = "get_circles", pure)] + pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { + circle.circles.clone() + } + #[rhai_fn(name = "get_ws_url", pure)] + pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { + circle.ws_url.clone() + } + #[rhai_fn(name = "get_logo", pure)] + pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { + circle.logo.clone() + } + #[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) { + engine.build_type::(); + engine.build_type::(); + + 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> { + let result = db_clone_set_circle.set(&circle).map_err(|e| { + Box::new(EvalAltResult::ErrorRuntime( + format!("DB Error set_circle: {}", e).into(), + Position::NONE, + )) + })?; + Ok(result.1) + }, + ); + + let db_clone_delete_circle = db.clone(); + db_module.set_native_fn( + "delete_circle", + move |circle: Circle| -> Result<(), Box> { + let result = db_clone_delete_circle + .collection::() + .expect("can open circle collection") + .delete_by_id(circle.base_data.id) + .expect("can delete circle"); + Ok(result) + }, + ); + + let db_clone_get_circle = db.clone(); + db_module.set_native_fn( + "get_circle", + move || -> Result> { + 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, + ))) + } + }, + ); + + // --- 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)?; + 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, + )) + }) + }, + ); + + 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)) + }, + ); + + engine.register_global_module(db_module.into()); + + println!("Successfully registered circle 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..823276f --- /dev/null +++ b/heromodels/src/models/contact/contact.rs @@ -0,0 +1,130 @@ +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 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 { + 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 + } +} diff --git a/heromodels/src/models/contact/mod.rs b/heromodels/src/models/contact/mod.rs new file mode 100644 index 0000000..533e45a --- /dev/null +++ b/heromodels/src/models/contact/mod.rs @@ -0,0 +1,5 @@ +// Export contact module +pub mod contact; + +// Re-export contact, Group from the inner contact module (contact.rs) within src/models/contact/mod.rs +pub use self::contact::{Contact, Group}; diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index 28bcefd..715c2d3 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/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 d6f9648..eb72c8d 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -1,14 +1,14 @@ // 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; /// 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 + pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { + // TODO: implement + return None; } } diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 40a5acf..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)] @@ -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..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,7 +53,7 @@ impl Default for BidStatus { } /// Bid represents a bid on an auction listing -#[derive(Debug, Clone, Serialize, Deserialize, CustomType)] +#[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 @@ -64,32 +64,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 +124,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 +238,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 +331,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/mod.rs b/heromodels/src/models/finance/mod.rs index bf94b7e..a9706a7 100644 --- a/heromodels/src/models/finance/mod.rs +++ b/heromodels/src/models/finance/mod.rs @@ -4,9 +4,8 @@ pub mod account; pub mod asset; pub mod marketplace; -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 deleted file mode 100644 index 01cb4f1..0000000 --- a/heromodels/src/models/finance/rhai.rs +++ /dev/null @@ -1,416 +0,0 @@ -use rhai::{Engine, Array, Dynamic, ImmutableString, INT, EvalAltResult, NativeCallContext}; -use std::sync::{Arc, Mutex}; -use std::collections::HashMap; -use std::error::Error as StdError; // For Box - -// Custom error type for Rhai that wraps a String -#[derive(Debug)] -struct RhaiStringError(String); - -impl std::fmt::Display for RhaiStringError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl StdError for RhaiStringError {} - - -use chrono::{DateTime, Utc}; - -use crate::models::finance::account::Account; -use crate::models::finance::asset::{Asset, AssetType}; -use crate::models::finance::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; - -// --- 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) --- - 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); - - // --- 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 - }); - - 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 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 - }); - - 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 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 - }); - - 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()), - } - }); - - // Global timestamp function for scripts to get current time - engine.register_fn("timestamp", || Utc::now().timestamp()); -} diff --git a/heromodels/src/models/flow/flow.rs b/heromodels/src/models/flow/flow.rs index 71a0e46..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}; -use super::flow_step::FlowStep; /// Represents a signing flow. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[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. @@ -27,13 +29,13 @@ 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(), - 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/flow_step.rs b/heromodels/src/models/flow/flow_step.rs index 432279f..e5bc2b6 100644 --- a/heromodels/src/models/flow/flow_step.rs +++ b/heromodels/src/models/flow/flow_step.rs @@ -1,12 +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. @@ -20,6 +23,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/mod.rs b/heromodels/src/models/flow/mod.rs index f729594..fd3cb68 100644 --- a/heromodels/src/models/flow/mod.rs +++ b/heromodels/src/models/flow/mod.rs @@ -2,10 +2,8 @@ pub mod flow; pub mod flow_step; pub mod signature_requirement; -pub mod rhai; // 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; \ 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 dff653d..0000000 --- a/heromodels/src/models/flow/rhai.rs +++ /dev/null @@ -1,140 +0,0 @@ -use rhai::{Dynamic, Engine, EvalAltResult, NativeCallContext, Position}; -use std::sync::Arc; - -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. - -// 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| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Conversion error for {} in {} from i64 to u32", field_name, object_name), - context_pos, - )) - }) -} - -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)) - }); - - // 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)))? - .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 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))) - }); - - 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..318b8a5 100644 --- a/heromodels/src/models/flow/signature_requirement.rs +++ b/heromodels/src/models/flow/signature_requirement.rs @@ -1,12 +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)] +#[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. @@ -31,7 +34,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/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/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/proposal.rs b/heromodels/src/models/governance/proposal.rs index e051d47..2be8cab 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -3,9 +3,10 @@ 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 super::AttachedFile; +use crate::models::core::Comment; use heromodels_core::BaseModelData; // --- 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 a9bbe41..08ef5e6 100644 --- a/heromodels/src/models/legal/contract.rs +++ b/heromodels/src/models/legal/contract.rs @@ -62,7 +62,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, @@ -89,7 +89,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, @@ -206,7 +206,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/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 6b760f4..0000000 --- a/heromodels/src/models/legal/rhai.rs +++ /dev/null @@ -1,727 +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() }, - ); - - // Reminder functionality - engine.register_fn( - "last_reminder_mail_sent_at", - |context: NativeCallContext, - signer: ContractSigner, - timestamp_i64: i64| - -> Result> { - let timestamp_u64 = i64_to_u64( - timestamp_i64, - context.position(), - "timestamp", - "ContractSigner.last_reminder_mail_sent_at", - )?; - Ok(signer.last_reminder_mail_sent_at(timestamp_u64)) - }, - ); - engine.register_fn( - "clear_last_reminder_mail_sent_at", - |signer: ContractSigner| -> ContractSigner { signer.clear_last_reminder_mail_sent_at() }, - ); - - // Signature data functionality - engine.register_fn( - "signature_data", - |signer: ContractSigner, signature_data: String| -> ContractSigner { - signer.signature_data(signature_data) - }, - ); - engine.register_fn( - "clear_signature_data", - |signer: ContractSigner| -> ContractSigner { signer.clear_signature_data() }, - ); - - // Helper methods for reminder logic - engine.register_fn( - "can_send_reminder", - |context: NativeCallContext, - signer: &mut ContractSigner, - current_timestamp_i64: i64| - -> Result> { - let current_timestamp = i64_to_u64( - current_timestamp_i64, - context.position(), - "current_timestamp", - "ContractSigner.can_send_reminder", - )?; - Ok(signer.can_send_reminder(current_timestamp)) - }, - ); - - engine.register_fn( - "reminder_cooldown_remaining", - |context: NativeCallContext, - signer: &mut ContractSigner, - current_timestamp_i64: i64| - -> Result> { - let current_timestamp = i64_to_u64( - current_timestamp_i64, - context.position(), - "current_timestamp", - "ContractSigner.reminder_cooldown_remaining", - )?; - Ok(signer - .reminder_cooldown_remaining(current_timestamp) - .map_or(Dynamic::UNIT, |remaining| Dynamic::from(remaining as i64))) - }, - ); - - engine.register_fn( - "mark_reminder_sent", - |context: NativeCallContext, - signer: &mut ContractSigner, - current_timestamp_i64: i64| - -> Result<(), Box> { - let current_timestamp = i64_to_u64( - current_timestamp_i64, - context.position(), - "current_timestamp", - "ContractSigner.mark_reminder_sent", - )?; - signer.mark_reminder_sent(current_timestamp); - Ok(()) - }, - ); - - // Sign methods - engine.register_fn( - "sign", - |signer: &mut ContractSigner, signature_data: String, comments: String| { - signer.sign(Some(signature_data), Some(comments)); - }, - ); - engine.register_fn( - "sign_without_signature", - |signer: &mut ContractSigner, comments: String| { - signer.sign(None, Some(comments)); - }, - ); - engine.register_fn("sign_simple", |signer: &mut ContractSigner| { - signer.sign(None, None); - }); - - 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( - "last_reminder_mail_sent_at", - |signer: &mut ContractSigner| -> Result> { - Ok(signer - .last_reminder_mail_sent_at - .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64))) - }, - ); - engine.register_get( - "signature_data", - |signer: &mut ContractSigner| -> Result> { - Ok(signer - .signature_data - .as_ref() - .map_or(Dynamic::UNIT, |data| Dynamic::from(data.clone()))) - }, - ); - - // --- 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/library/collection.rs b/heromodels/src/models/library/collection.rs new file mode 100644 index 0000000..48d7a45 --- /dev/null +++ b/heromodels/src/models/library/collection.rs @@ -0,0 +1,96 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// 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() + } + + /// 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(); + 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..47fb6fe --- /dev/null +++ b/heromodels/src/models/library/items.rs @@ -0,0 +1,366 @@ +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() + } + + /// 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(); + 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() + } + + /// 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(); + 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, Default)] +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 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(); + 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, Default)] +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 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, Default)] +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 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(); + 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 Slideshow library item (collection of images for slideshow). +#[model] +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Slideshow { + /// 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 slides + pub slides: Vec, +} + +#[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 { + image_url: String::new(), + title: None, + description: None, + } + } + + 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 Slideshow { + /// Creates a new `Slideshow` 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(); + 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, slide: Slide) -> Self { + self.slides.push(slide); + self + } +} diff --git a/heromodels/src/models/library/mod.rs b/heromodels/src/models/library/mod.rs new file mode 100644 index 0000000..f13c08e --- /dev/null +++ b/heromodels/src/models/library/mod.rs @@ -0,0 +1,2 @@ +pub mod collection; +pub mod items; 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 0caa406..b65f030 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -2,12 +2,17 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing +pub mod access; 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 object; pub mod projects; // Re-export key types for convenience @@ -19,22 +24,12 @@ pub use calendar::{AttendanceStatus, Attendee, Calendar, Event}; pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; pub use finance::{Account, Asset, AssetType}; pub use flow::{Flow, FlowStep, SignatureRequirement}; -pub use governance::{ - Activity, ActivityType, Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, -}; pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; - -#[cfg(feature = "rhai")] -pub use biz::register_biz_rhai_module; -pub use calendar::register_calendar_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 calendar::{AttendanceStatus, Attendee, Calendar, Event}; -pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType}; -pub use finance::{Account, Asset, AssetType}; +pub use library::collection::Collection; +pub use library::items::{Image, Markdown, Pdf}; +pub use projects::{Project, ProjectStatus}; pub use governance::{ ActivityStatus, ActivityType, Ballot, GovernanceActivity, Proposal, ProposalStatus, VoteEventStatus, VoteOption, }; +pub use circle::{Circle, ThemeData}; 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..8ebca6d --- /dev/null +++ b/heromodels/src/models/object/object.rs @@ -0,0 +1,41 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +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, RhaiApi)] +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 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 new file mode 100644 index 0000000..657cbee --- /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 rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +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..6e58770 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 epic; +pub mod sprint; +pub mod sprint_enums; +pub mod task; +pub mod task_enums; // pub mod epic; // pub mod issue; // pub mod kanban; @@ -8,14 +13,13 @@ pub mod base; // pub mod story; pub use base::*; +pub use epic::*; +pub use sprint::*; +pub use sprint_enums::*; +pub use task::*; +pub use task_enums::*; // pub use epic::*; // pub use issue::*; // 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 27fd7ce..0000000 --- a/heromodels/src/models/projects/rhai.rs +++ /dev/null @@ -1,256 +0,0 @@ -// 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}; - - -// Import models from the projects::base module -use super::base::{Project, /* Label, */ Priority, Status, ItemType}; // 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::