From 00c4e6a1eb88bd4c165da04e5d4a2a768699eb6a Mon Sep 17 00:00:00 2001 From: timurgordon Date: Thu, 12 Jun 2025 05:22:17 +0300 Subject: [PATCH] improve and add models --- heromodels/Cargo.toml | 5 + heromodels/examples/library_rhai/example.rs | 38 ++ heromodels/examples/library_rhai/library.rhai | 78 +++ heromodels/src/models/biz/company.rs | 2 +- heromodels/src/models/biz/product.rs | 2 - heromodels/src/models/biz/shareholder.rs | 2 +- heromodels/src/models/calendar/calendar.rs | 1 - heromodels/src/models/calendar/rhai.rs | 11 - heromodels/src/models/circle/circle.rs | 86 +++ heromodels/src/models/circle/mod.rs | 7 + heromodels/src/models/circle/rhai.rs | 201 ++++++ heromodels/src/models/finance/account.rs | 6 +- heromodels/src/models/finance/rhai.rs | 13 +- heromodels/src/models/flow/rhai.rs | 11 - heromodels/src/models/library/collection.rs | 91 +++ heromodels/src/models/library/items.rs | 369 +++++++++++ heromodels/src/models/library/mod.rs | 4 + heromodels/src/models/library/rhai.rs | 626 ++++++++++++++++++ heromodels/src/models/mod.rs | 9 + 19 files changed, 1520 insertions(+), 42 deletions(-) create mode 100644 heromodels/examples/library_rhai/example.rs create mode 100644 heromodels/examples/library_rhai/library.rhai create mode 100644 heromodels/src/models/circle/circle.rs create mode 100644 heromodels/src/models/circle/mod.rs create mode 100644 heromodels/src/models/circle/rhai.rs create mode 100644 heromodels/src/models/library/collection.rs create mode 100644 heromodels/src/models/library/items.rs create mode 100644 heromodels/src/models/library/mod.rs create mode 100644 heromodels/src/models/library/rhai.rs diff --git a/heromodels/Cargo.toml b/heromodels/Cargo.toml index 392a73d..9b36157 100644 --- a/heromodels/Cargo.toml +++ b/heromodels/Cargo.toml @@ -81,3 +81,8 @@ required-features = ["rhai"] name = "biz_rhai" path = "examples/biz_rhai/example.rs" required-features = ["rhai"] + +[[example]] +name = "library_rhai" +path = "examples/library_rhai/example.rs" +required-features = ["rhai"] diff --git a/heromodels/examples/library_rhai/example.rs b/heromodels/examples/library_rhai/example.rs new file mode 100644 index 0000000..08b41e0 --- /dev/null +++ b/heromodels/examples/library_rhai/example.rs @@ -0,0 +1,38 @@ +use heromodels::db::hero::OurDB; +use heromodels::models::register_library_rhai_module; +use rhai::Engine; +use std::sync::Arc; +use std::{fs, path::Path}; + +fn main() -> Result<(), Box> { + // Initialize Rhai engine + let mut engine = Engine::new(); + + // Initialize database with OurDB + let db_path = "temp_library_db"; + // Clean up previous database file if it exists + if Path::new(db_path).exists() { + fs::remove_dir_all(db_path)?; + } + let db = Arc::new(OurDB::new(db_path, true).expect("Failed to create database")); + + // Register the library module with Rhai + register_library_rhai_module(&mut engine, db.clone()); + + // Load and evaluate the Rhai script + let script_path = Path::new(file!()).parent().unwrap().join("library.rhai"); + let script = fs::read_to_string(&script_path)?; + + println!("--- Running Library Rhai Script ---"); + match engine.eval::<()>(&script) { + Ok(_) => println!("\n--- Script executed successfully! ---"), + Err(e) => eprintln!("\n--- Script execution failed: {} ---", e), + } + + // Clean up the database file + fs::remove_dir_all(db_path)?; + println!("--- Cleaned up temporary database. ---"); + + + Ok(()) +} diff --git a/heromodels/examples/library_rhai/library.rhai b/heromodels/examples/library_rhai/library.rhai new file mode 100644 index 0000000..eb0a579 --- /dev/null +++ b/heromodels/examples/library_rhai/library.rhai @@ -0,0 +1,78 @@ +// heromodels/examples/library_rhai/library.rhai + +print("--- Testing Library Rhai Module ---"); + +// --- Image --- +print("\n1. Creating and saving an image..."); +let my_image = new_image() + .title("A Beautiful Sunset") + .description("A photo taken from a drone.") + .url("https://example.com/sunset.jpg") + .width(1920) + .height(1080); + +let saved_image = save_image(my_image); +print(" > Saved image with ID: " + saved_image.id); +let image_id = saved_image.id; + +// --- PDF --- +print("\n2. Creating and saving a PDF..."); +let my_pdf = new_pdf() + .title("Rust Programming Guide") + .description("A comprehensive guide to Rust.") + .url("https://example.com/rust.pdf") + .page_count(500); + +let saved_pdf = save_pdf(my_pdf); +print(" > Saved PDF with ID: " + saved_pdf.id); +let pdf_id = saved_pdf.id; + +// --- Markdown --- +print("\n3. Creating and saving a Markdown document..."); +let my_markdown = new_markdown() + .title("Meeting Notes") + .description("Notes from the weekly sync.") + .content("# Meeting Notes\n\n- Discussed project status.\n- Planned next sprint."); + +let saved_markdown = save_markdown(my_markdown); +print(" > Saved Markdown with ID: " + saved_markdown.id); +let markdown_id = saved_markdown.id; + +// --- Collection --- +print("\n4. Creating a collection and adding items..."); +let my_collection = new_collection() + .title("My Awesome Collection") + .description("A collection of various media.") + .add_image(image_id) + .add_pdf(pdf_id) + .add_markdown(markdown_id); + +let saved_collection = save_collection(my_collection); +print(" > Saved collection with ID: " + saved_collection.id); +let collection_id = saved_collection.id; + +// --- Verification --- +print("\n5. Verifying saved data..."); +let fetched_collection = get_collection(collection_id); +print(" > Fetched collection: '" + fetched_collection.title + "'"); +print(" > Collection contains " + fetched_collection.images + " image(s)."); +print(" > Collection contains " + fetched_collection.pdfs + " pdf(s)."); +print(" > Collection contains " + fetched_collection.markdowns + " markdown(s)."); + +let fetched_image = get_image(image_id); +print(" > Fetched image title: '" + fetched_image.title + "'"); +if (fetched_image.url != "https://example.com/sunset.jpg") { + throw "Image URL mismatch!"; +} +print(" > Image URL verified."); + +// --- Deletion --- +print("\n6. Cleaning up database..."); +delete_image(image_id); +print(" > Deleted image with ID: " + image_id); +delete_pdf(pdf_id); +print(" > Deleted PDF with ID: " + pdf_id); +delete_markdown(markdown_id); +print(" > Deleted Markdown with ID: " + markdown_id); +delete_collection(collection_id); +print(" > Deleted collection with ID: " + collection_id); \ No newline at end of file diff --git a/heromodels/src/models/biz/company.rs b/heromodels/src/models/biz/company.rs index 441e446..7393f2e 100644 --- a/heromodels/src/models/biz/company.rs +++ b/heromodels/src/models/biz/company.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, IndexKey, IndexKeyBuilder, Index}; +use heromodels_core::{BaseModelData, Index}; use rhai::{CustomType, TypeBuilder}; // For #[derive(CustomType)] use heromodels_core::BaseModelDataOps; use heromodels_derive::model; diff --git a/heromodels/src/models/biz/product.rs b/heromodels/src/models/biz/product.rs index a87cb2d..0d9f3d0 100644 --- a/heromodels/src/models/biz/product.rs +++ b/heromodels/src/models/biz/product.rs @@ -1,7 +1,5 @@ use serde::{Serialize, Deserialize}; use heromodels_core::BaseModelData; -use heromodels_core::Model; -use heromodels_core::BaseModelDataOps; use heromodels_derive::model; // ProductType represents the type of a product diff --git a/heromodels/src/models/biz/shareholder.rs b/heromodels/src/models/biz/shareholder.rs index ffc8f53..e6af823 100644 --- a/heromodels/src/models/biz/shareholder.rs +++ b/heromodels/src/models/biz/shareholder.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use heromodels_core::{BaseModelData, Model, BaseModelDataOps}; +use heromodels_core::BaseModelData; use heromodels_derive::model; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 2eca950..b9317a4 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -1,4 +1,3 @@ -use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; // Temporarily removed to fix compilation issues diff --git a/heromodels/src/models/calendar/rhai.rs b/heromodels/src/models/calendar/rhai.rs index 07de7c8..6e754c3 100644 --- a/heromodels/src/models/calendar/rhai.rs +++ b/heromodels/src/models/calendar/rhai.rs @@ -2,7 +2,6 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; use std::mem; -use chrono::{DateTime, Utc}; use crate::db::Db; use super::calendar::{Event, Attendee, Calendar, AttendanceStatus}; @@ -22,16 +21,6 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { ) } -// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers -fn val_from_i64_to_u64(val_i64: i64) -> Result> { - u64::try_from(val_i64).map_err(|_| - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert value '{}' to u64", val_i64).into(), - Position::NONE - )) - ) -} - #[export_module] mod rhai_calendar_module { // --- Event Functions --- diff --git a/heromodels/src/models/circle/circle.rs b/heromodels/src/models/circle/circle.rs new file mode 100644 index 0000000..43d4b11 --- /dev/null +++ b/heromodels/src/models/circle/circle.rs @@ -0,0 +1,86 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +// Temporarily removed to fix compilation issues +// use rhai_autobind_macros::rhai_model_export; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Represents an event in a calendar +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)] +pub struct Circle { + /// Base model data + pub base_data: BaseModelData, + #[index] + pub title: String, + pub ws_url: String, + /// Optional description of the circle + pub description: Option, + /// List of related circles + pub circles: Vec, + /// Logo URL or symbol for the circle + pub logo: Option, + /// Theme settings for the circle (colors, styling, etc.) + pub theme: HashMap, +} + +impl Circle { + /// Creates a new circle + pub fn new() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + ws_url: String::new(), + description: None, + circles: Vec::new(), + logo: None, + theme: HashMap::new(), + } + } + + /// Sets the title for the circle + pub fn title(mut self, title: impl ToString) -> Self { + self.title = title.to_string(); + self + } + + /// Sets the ws_url for the circle + pub fn ws_url(mut self, ws_url: impl ToString) -> Self { + self.ws_url = ws_url.to_string(); + self + } + + /// Sets the description for the circle + pub fn description(mut self, description: impl ToString) -> Self { + self.description = Some(description.to_string()); + self + } + + /// Sets the logo for the circle + pub fn logo(mut self, logo: impl ToString) -> Self { + self.logo = Some(logo.to_string()); + self + } + + /// Sets a theme property for the circle + pub fn theme_property(mut self, key: impl ToString, value: impl ToString) -> Self { + self.theme.insert(key.to_string(), value.to_string()); + self + } + + /// Sets the entire theme for the circle + pub fn theme(mut self, theme: HashMap) -> Self { + self.theme = theme; + self + } + + /// Adds a related circle + pub fn add_circle(mut self, circle: String) -> Self { + // Prevent duplicate circles + if !self.circles.iter().any(|a| *a == circle) { + self.circles.push(circle); + } + self + } +} \ No newline at end of file diff --git a/heromodels/src/models/circle/mod.rs b/heromodels/src/models/circle/mod.rs new file mode 100644 index 0000000..f761c82 --- /dev/null +++ b/heromodels/src/models/circle/mod.rs @@ -0,0 +1,7 @@ +// Export calendar module +pub mod circle; +pub mod rhai; + +// Re-export Calendar, Event, Attendee, and AttendanceStatus from the inner calendar module (calendar.rs) within src/models/calendar/mod.rs +pub use self::circle::{Circle}; +pub use rhai::register_circle_rhai_module; diff --git a/heromodels/src/models/circle/rhai.rs b/heromodels/src/models/circle/rhai.rs new file mode 100644 index 0000000..07f9f10 --- /dev/null +++ b/heromodels/src/models/circle/rhai.rs @@ -0,0 +1,201 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array, CustomType}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; + +use super::circle::{Circle}; +type RhaiCircle = Circle; +use crate::db::hero::OurDB; +use crate::db::Collection; +use serde::Serialize; +use std::collections::HashMap; +use serde_json; + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + // The type must be: + T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai +{ + // This is the function that will be called when a script runs '.json()' + let to_json_fn = |obj: &mut T| -> Result> { + // Use serde_json to serialize the object to a pretty-formatted string. + // The '?' will automatically convert any serialization error into a Rhai error. + serde_json::to_string(obj).map_err(|e| e.to_string().into()) + }; + + // Register the function as a method named "json" for the type 'T'. + engine.build_type::().register_fn("json", to_json_fn); +} + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorArithmetic( + format!("Failed to convert ID '{}' to u32", id_i64).into(), + Position::NONE + )) + ) +} + +#[export_module] +mod rhai_circle_module { + // --- Circle Functions --- + #[rhai_fn(name = "new_circle")] + pub fn new_circle() -> RhaiCircle { + Circle::new() + } + + /// Sets the circle title + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn circle_title(circle: &mut RhaiCircle, title: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.title(title); + Ok(circle.clone()) + } + + /// Sets the circle ws_url + #[rhai_fn(name = "ws_url", return_raw, global, pure)] + pub fn circle_ws_url(circle: &mut RhaiCircle, ws_url: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.ws_url(ws_url); + Ok(circle.clone()) + } + + /// Sets the circle description + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn circle_description(circle: &mut RhaiCircle, description: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.description(description); + Ok(circle.clone()) + } + + /// Sets the circle logo + #[rhai_fn(name = "logo", return_raw, global, pure)] + pub fn circle_logo(circle: &mut RhaiCircle, logo: String) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.logo(logo); + Ok(circle.clone()) + } + + /// Sets the circle theme + #[rhai_fn(name = "theme", return_raw, global, pure)] + pub fn circle_theme(circle: &mut RhaiCircle, theme: HashMap) -> Result> { + let owned_circle = mem::take(circle); + *circle = owned_circle.theme(theme); + Ok(circle.clone()) + } + + /// Adds an attendee to the circle + #[rhai_fn(name = "add_circle", return_raw, global, pure)] + pub fn circle_add_circle(circle: &mut RhaiCircle, added_circle: String) -> Result> { + // Use take to get ownership of the circle + let owned_circle = mem::take(circle); + *circle = owned_circle.add_circle(added_circle); + Ok(circle.clone()) + } + + // Circle Getters + #[rhai_fn(get = "id", pure)] + pub fn get_circle_id(circle: &mut RhaiCircle) -> i64 { circle.base_data.id as i64 } + #[rhai_fn(get = "created_at", pure)] + pub fn get_circle_created_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.created_at } + #[rhai_fn(get = "modified_at", pure)] + pub fn get_circle_modified_at(circle: &mut RhaiCircle) -> i64 { circle.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_circle_title(circle: &mut RhaiCircle) -> String { circle.title.clone() } + #[rhai_fn(get = "description", pure)] + pub fn get_circle_description(circle: &mut RhaiCircle) -> Option { circle.description.clone() } + #[rhai_fn(get = "circles", pure)] + pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec { circle.circles.clone() } + #[rhai_fn(get = "ws_url", pure)] + pub fn get_circle_ws_url(circle: &mut RhaiCircle) -> String { circle.ws_url.clone() } + #[rhai_fn(get = "logo", pure)] + pub fn get_circle_logo(circle: &mut RhaiCircle) -> Option { circle.logo.clone() } + #[rhai_fn(get = "theme", pure)] + pub fn get_circle_theme(circle: &mut RhaiCircle) -> HashMap { circle.theme.clone() } +} + +pub fn register_circle_rhai_module(engine: &mut Engine, db: Arc) { + // Register the exported module globally + let module = exported_module!(rhai_circle_module); + engine.register_global_module(module.into()); + + // Create a module for database functions + let mut db_module = Module::new(); + + // Manually register database functions as they need to capture 'db' + let db_clone_set_circle = db.clone(); + db_module.set_native_fn("save_circle", move |circle: Circle| -> Result> { + // Use the Collection trait method directly + let result = db_clone_set_circle.set(&circle) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error set_circle: {}", e).into(), Position::NONE)))?; + + // Return the updated circle with the correct ID + Ok(result.1) + }); + + register_json_method::(engine); + + // Manually register database functions as they need to capture 'db' + let db_clone_delete_circle = db.clone(); + db_module.set_native_fn("delete_circle", move |circle: Circle| -> Result<(), Box> { + // Use the Collection trait method directly + let result = db_clone_delete_circle.collection::() + .expect("can open circle collection") + .delete_by_id(circle.base_data.id) + .expect("can delete circle"); + + // Return the updated circle with the correct ID + Ok(result) + }); + + let db_clone_get_circle = db.clone(); + db_module.set_native_fn("get_circle", move || -> Result> { + // Use the Collection trait method directly + let all_circles: Vec = db_clone_get_circle.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle: {}", e).into(), Position::NONE)))?; + + if let Some(first_circle) = all_circles.first() { + Ok(first_circle.clone()) + } else { + Err(Box::new(EvalAltResult::ErrorRuntime("Circle not found".into(), Position::NONE))) + } + }); + + let db_clone_get_circle_by_id = db.clone(); + db_module.set_native_fn("get_circle_by_id", move |id_i64: INT| -> Result> { + let id_u32 = id_from_i64_to_u32(id_i64)?; + // Use the Collection trait method directly + db_clone_get_circle_by_id.get_by_id(id_u32) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error get_circle_by_id: {}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Circle with ID {} not found", id_u32).into(), Position::NONE))) + }); + + // Add list_circles function to get all circles + let db_clone_list_circles = db.clone(); + db_module.set_native_fn("list_circles", move || -> Result> { + let collection = db_clone_list_circles.collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get circle collection: {:?}", e).into(), + Position::NONE + )))?; + let circles = collection.get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime( + format!("Failed to get all circles: {:?}", e).into(), + Position::NONE + )))?; + let mut array = Array::new(); + for circle in circles { + array.push(Dynamic::from(circle)); + } + Ok(Dynamic::from(array)) + }); + + // Register the database module globally + engine.register_global_module(db_module.into()); + + println!("Successfully registered circle Rhai module using export_module approach."); +} diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index f225fc4..7bb9e6c 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -80,13 +80,13 @@ impl Account { /// Get the total value of all assets in the account pub fn total_value(&self) -> f64 { - /// TODO: implement + // TODO: implement 0.0 } /// Find an asset by name - pub fn find_asset_by_name(&self, name: &str) -> Option<&Asset> { - /// TODO: implement + pub fn find_asset_by_name(&self, _name: &str) -> Option<&Asset> { + // TODO: implement return None } } diff --git a/heromodels/src/models/finance/rhai.rs b/heromodels/src/models/finance/rhai.rs index d58db70..0633904 100644 --- a/heromodels/src/models/finance/rhai.rs +++ b/heromodels/src/models/finance/rhai.rs @@ -2,7 +2,7 @@ use rhai::plugin::*; use rhai::{Engine, EvalAltResult, Position, Module, INT, Dynamic, Array}; use std::sync::Arc; use std::mem; -use chrono::{DateTime, Utc}; +use chrono::Utc; use super::account::Account; use super::asset::{Asset, AssetType}; @@ -10,7 +10,6 @@ use super::marketplace::{Listing, Bid, ListingStatus, ListingType, BidStatus}; use crate::db::hero::OurDB; use crate::db::{Collection, Db}; -use heromodels_core::Model; type RhaiAccount = Account; type RhaiAsset = Asset; @@ -689,18 +688,8 @@ mod bid_module { Ok(bid.clone()) } } -use std::collections::HashMap; -use std::sync::Mutex; use rhai::ImmutableString; -// Custom error type for Rhai -struct RhaiStringError(String); -impl std::fmt::Debug for RhaiStringError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - // Register all Rhai functions for the finance module pub fn register_finance_rhai_module(engine: &mut Engine, db: Arc) { // --- Register model-specific modules with the engine --- diff --git a/heromodels/src/models/flow/rhai.rs b/heromodels/src/models/flow/rhai.rs index b564850..b0ea9eb 100644 --- a/heromodels/src/models/flow/rhai.rs +++ b/heromodels/src/models/flow/rhai.rs @@ -12,7 +12,6 @@ type RhaiSignatureRequirement = SignatureRequirement; use crate::db::hero::OurDB; use crate::db::Collection; use crate::db::Db; -use heromodels_core::Model; // Helper to convert i64 from Rhai to u32 for IDs fn id_from_i64_to_u32(id_i64: i64) -> Result> { @@ -24,16 +23,6 @@ fn id_from_i64_to_u32(id_i64: i64) -> Result> { ) } -// Helper to convert i64 from Rhai to u64 for timestamps or other large numbers -fn val_from_i64_to_u64(val_i64: i64) -> Result> { - u64::try_from(val_i64).map_err(|_| - Box::new(EvalAltResult::ErrorArithmetic( - format!("Failed to convert value '{}' to u64", val_i64).into(), - Position::NONE - )) - ) -} - #[export_module] mod rhai_flow_module { // --- Flow Functions --- diff --git a/heromodels/src/models/library/collection.rs b/heromodels/src/models/library/collection.rs new file mode 100644 index 0000000..220109c --- /dev/null +++ b/heromodels/src/models/library/collection.rs @@ -0,0 +1,91 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; +use rhai::{CustomType, TypeBuilder}; + +/// Represents a collection of library items. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Collection { + /// Base model data + pub base_data: BaseModelData, + /// Title of the collection + #[index] + pub title: String, + /// Optional description of the collection + pub description: Option, + /// List of image item IDs belonging to this collection + pub images: Vec, + /// List of PDF item IDs belonging to this collection + pub pdfs: Vec, + /// List of Markdown item IDs belonging to this collection + pub markdowns: Vec, + /// List of Book item IDs belonging to this collection + pub books: Vec, + /// List of Slides item IDs belonging to this collection + pub slides: Vec, +} + +impl Default for Collection { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + images: Vec::new(), + pdfs: Vec::new(), + markdowns: Vec::new(), + books: Vec::new(), + slides: Vec::new(), + } + } +} + +impl Collection { + /// Creates a new `Collection` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the collection. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the collection. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds an image ID to the collection. + pub fn add_image(mut self, image_id: u32) -> Self { + self.images.push(image_id); + self + } + + /// Adds a PDF ID to the collection. + pub fn add_pdf(mut self, pdf_id: u32) -> Self { + self.pdfs.push(pdf_id); + self + } + + /// Adds a markdown ID to the collection. + pub fn add_markdown(mut self, markdown_id: u32) -> Self { + self.markdowns.push(markdown_id); + self + } + + /// Adds a book ID to the collection. + pub fn add_book(mut self, book_id: u32) -> Self { + self.books.push(book_id); + self + } + + /// Adds a slides ID to the collection. + pub fn add_slides(mut self, slides_id: u32) -> Self { + self.slides.push(slides_id); + self + } +} diff --git a/heromodels/src/models/library/items.rs b/heromodels/src/models/library/items.rs new file mode 100644 index 0000000..aacd182 --- /dev/null +++ b/heromodels/src/models/library/items.rs @@ -0,0 +1,369 @@ +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use rhai::{CustomType, TypeBuilder}; +use serde::{Deserialize, Serialize}; + +/// Represents an Image library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Image { + /// Base model data + pub base_data: BaseModelData, + /// Title of the image + #[index] + pub title: String, + /// Optional description of the image + pub description: Option, + /// URL of the image + pub url: String, + /// Width of the image in pixels + pub width: u32, + /// Height of the image in pixels + pub height: u32, +} + +impl Default for Image { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + url: String::new(), + width: 0, + height: 0, + } + } +} + +impl Image { + /// Creates a new `Image` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the image. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the image. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the URL of the image. + pub fn url(mut self, url: impl Into) -> Self { + self.url = url.into(); + self + } + + /// Sets the width of the image. + pub fn width(mut self, width: u32) -> Self { + self.width = width; + self + } + + /// Sets the height of the image. + pub fn height(mut self, height: u32) -> Self { + self.height = height; + self + } +} + +/// Represents a PDF document library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Pdf { + /// Base model data + pub base_data: BaseModelData, + /// Title of the PDF + #[index] + pub title: String, + /// Optional description of the PDF + pub description: Option, + /// URL of the PDF file + pub url: String, + /// Number of pages in the PDF + pub page_count: u32, +} + +impl Default for Pdf { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + url: String::new(), + page_count: 0, + } + } +} + +impl Pdf { + /// Creates a new `Pdf` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the PDF. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the PDF. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the URL of the PDF. + pub fn url(mut self, url: impl Into) -> Self { + self.url = url.into(); + self + } + + /// Sets the page count of the PDF. + pub fn page_count(mut self, page_count: u32) -> Self { + self.page_count = page_count; + self + } +} + +/// Represents a Markdown document library item. +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Markdown { + /// Base model data + pub base_data: BaseModelData, + /// Title of the document + #[index] + pub title: String, + /// Optional description of the document + pub description: Option, + /// The markdown content + pub content: String, +} + +impl Default for Markdown { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + content: String::new(), + } + } +} + +impl Markdown { + /// Creates a new `Markdown` document with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the document. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the document. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Sets the content of the document. + pub fn content(mut self, content: impl Into) -> Self { + self.content = content.into(); + self + } +} + +/// Represents a table of contents entry for a book. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct TocEntry { + /// Title of the chapter/section + pub title: String, + /// Page number (index in the pages array) + pub page: u32, + /// Optional subsections + pub subsections: Vec, +} + +impl Default for TocEntry { + fn default() -> Self { + Self { + title: String::new(), + page: 0, + subsections: Vec::new(), + } + } +} + +impl TocEntry { + /// Creates a new `TocEntry` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the TOC entry. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the page number of the TOC entry. + pub fn page(mut self, page: u32) -> Self { + self.page = page; + self + } + + /// Adds a subsection to the TOC entry. + pub fn add_subsection(mut self, subsection: TocEntry) -> Self { + self.subsections.push(subsection); + self + } +} + +/// Represents a Book library item (collection of markdown pages with TOC). +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Book { + /// Base model data + pub base_data: BaseModelData, + /// Title of the book + #[index] + pub title: String, + /// Optional description of the book + pub description: Option, + /// Table of contents + pub table_of_contents: Vec, + /// Pages content (markdown strings) + pub pages: Vec, +} + +impl Default for Book { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + table_of_contents: Vec::new(), + pages: Vec::new(), + } + } +} + +impl Book { + /// Creates a new `Book` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the book. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the book. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds a page to the book. + pub fn add_page(mut self, content: impl Into) -> Self { + self.pages.push(content.into()); + self + } + + /// Adds a TOC entry to the book. + pub fn add_toc_entry(mut self, entry: TocEntry) -> Self { + self.table_of_contents.push(entry); + self + } + + /// Sets the table of contents. + pub fn table_of_contents(mut self, toc: Vec) -> Self { + self.table_of_contents = toc; + self + } + + /// Sets all pages at once. + pub fn pages(mut self, pages: Vec) -> Self { + self.pages = pages; + self + } +} + +/// Represents a Slides library item (collection of images for slideshow). +#[model] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)] +pub struct Slides { + /// Base model data + pub base_data: BaseModelData, + /// Title of the slideshow + #[index] + pub title: String, + /// Optional description of the slideshow + pub description: Option, + /// List of slide image URLs + pub slide_urls: Vec, + /// Optional slide titles/captions + pub slide_titles: Vec>, +} + +impl Default for Slides { + fn default() -> Self { + Self { + base_data: BaseModelData::new(), + title: String::new(), + description: None, + slide_urls: Vec::new(), + slide_titles: Vec::new(), + } + } +} + +impl Slides { + /// Creates a new `Slides` with default values. + pub fn new() -> Self { + Self::default() + } + + /// Sets the title of the slideshow. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Sets the description of the slideshow. + pub fn description(mut self, description: impl Into) -> Self { + self.description = Some(description.into()); + self + } + + /// Adds a slide with URL and optional title. + pub fn add_slide(mut self, url: impl Into, title: Option) -> Self { + self.slide_urls.push(url.into()); + self.slide_titles.push(title); + self + } + + /// Sets all slide URLs at once. + pub fn slide_urls(mut self, urls: Vec) -> Self { + self.slide_urls = urls; + self + } + + /// Sets all slide titles at once. + pub fn slide_titles(mut self, titles: Vec>) -> Self { + self.slide_titles = titles; + self + } +} diff --git a/heromodels/src/models/library/mod.rs b/heromodels/src/models/library/mod.rs new file mode 100644 index 0000000..a9bcd44 --- /dev/null +++ b/heromodels/src/models/library/mod.rs @@ -0,0 +1,4 @@ +pub mod collection; +pub mod items; +pub mod rhai; +pub use rhai::register_library_rhai_module; diff --git a/heromodels/src/models/library/rhai.rs b/heromodels/src/models/library/rhai.rs new file mode 100644 index 0000000..1bd2cdd --- /dev/null +++ b/heromodels/src/models/library/rhai.rs @@ -0,0 +1,626 @@ +use rhai::plugin::*; +use rhai::{Engine, EvalAltResult, CustomType, TypeBuilder, Position, Module, Dynamic, Array}; +use std::sync::Arc; +use std::mem; +use crate::db::Db; +use serde::Serialize; +use serde_json; + + +use super::collection::{Collection as RhaiCollection}; +use super::items::{Image as RhaiImage, Pdf as RhaiPdf, Markdown as RhaiMarkdown, Book as RhaiBook, Slides as RhaiSlides, TocEntry as RhaiTocEntry}; +use crate::db::hero::OurDB; +use crate::db::Collection as DbCollectionTrait; + +// Helper to convert i64 from Rhai to u32 for IDs +fn id_from_i64_to_u32(id_i64: i64) -> Result> { + u32::try_from(id_i64).map_err(|_| + Box::new(EvalAltResult::ErrorMismatchDataType( + "u32".to_string(), // Expected type + format!("i64 value ({}) that cannot be represented as u32", id_i64), // Actual type/value description + Position::NONE + )) + ) +} + +/// Registers a `.json()` method for any type `T` that implements the required traits. +fn register_json_method(engine: &mut Engine) +where + // The type must be: + T: CustomType + Clone + Serialize, // A clonable, serializable, custom type for Rhai +{ + // This is the function that will be called when a script runs '.json()' + let to_json_fn = |obj: &mut T| -> Result> { + // Use serde_json to serialize the object to a pretty-formatted string. + // The '?' will automatically convert any serialization error into a Rhai error. + serde_json::to_string(obj).map_err(|e| e.to_string().into()) + }; + + // Register the function as a method named "json" for the type 'T'. + engine.build_type::().register_fn("json", to_json_fn); +} + +// Wrapper type for a list of collections to enable .json() method via register_json_method +#[derive(Debug, Clone, Serialize, CustomType)] +#[rhai_type(name = "CollectionArray")] +pub struct RhaiCollectionArray(pub Vec); + + +#[export_module] +mod rhai_library_module { + // --- Collection Functions --- + #[rhai_fn(name = "new_collection")] + pub fn new_collection() -> RhaiCollection { + RhaiCollection::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn collection_title(collection: &mut RhaiCollection, title: String) -> Result> { + let owned = mem::take(collection); + *collection = owned.title(title); + Ok(collection.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn collection_description(collection: &mut RhaiCollection, description: String) -> Result> { + let owned = mem::take(collection); + *collection = owned.description(description); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_image", return_raw, global, pure)] + pub fn collection_add_image(collection: &mut RhaiCollection, image_id: i64) -> Result> { + let id = id_from_i64_to_u32(image_id)?; + let owned = mem::take(collection); + *collection = owned.add_image(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_pdf", return_raw, global, pure)] + pub fn collection_add_pdf(collection: &mut RhaiCollection, pdf_id: i64) -> Result> { + let id = id_from_i64_to_u32(pdf_id)?; + let owned = mem::take(collection); + *collection = owned.add_pdf(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_markdown", return_raw, global, pure)] + pub fn collection_add_markdown(collection: &mut RhaiCollection, markdown_id: i64) -> Result> { + let id = id_from_i64_to_u32(markdown_id)?; + let owned = mem::take(collection); + *collection = owned.add_markdown(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_book", return_raw, global, pure)] + pub fn collection_add_book(collection: &mut RhaiCollection, book_id: i64) -> Result> { + let id = id_from_i64_to_u32(book_id)?; + let owned = mem::take(collection); + *collection = owned.add_book(id); + Ok(collection.clone()) + } + + #[rhai_fn(name = "add_slides", return_raw, global, pure)] + pub fn collection_add_slides(collection: &mut RhaiCollection, slides_id: i64) -> Result> { + let id = id_from_i64_to_u32(slides_id)?; + let owned = mem::take(collection); + *collection = owned.add_slides(id); + Ok(collection.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_collection_id(collection: &mut RhaiCollection) -> i64 { collection.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_collection_created_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_collection_modified_at(collection: &mut RhaiCollection) -> i64 { collection.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_collection_title(collection: &mut RhaiCollection) -> String { collection.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_collection_description(collection: &mut RhaiCollection) -> Option { collection.description.clone() } + + #[rhai_fn(get = "images", pure)] + pub fn get_collection_images(collection: &mut RhaiCollection) -> Vec { collection.images.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "pdfs", pure)] + pub fn get_collection_pdfs(collection: &mut RhaiCollection) -> Vec { collection.pdfs.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "markdowns", pure)] + pub fn get_collection_markdowns(collection: &mut RhaiCollection) -> Vec { collection.markdowns.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "books", pure)] + pub fn get_collection_books(collection: &mut RhaiCollection) -> Vec { collection.books.clone().into_iter().map(|id| id as i64).collect() } + + #[rhai_fn(get = "slides", pure)] + pub fn get_collection_slides(collection: &mut RhaiCollection) -> Vec { collection.slides.clone().into_iter().map(|id| id as i64).collect() } + + // --- Image Functions --- + #[rhai_fn(name = "new_image")] + pub fn new_image() -> RhaiImage { + RhaiImage::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn image_title(image: &mut RhaiImage, title: String) -> Result> { + let owned = mem::take(image); + *image = owned.title(title); + Ok(image.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn image_description(image: &mut RhaiImage, description: String) -> Result> { + let owned = mem::take(image); + *image = owned.description(description); + Ok(image.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn image_url(image: &mut RhaiImage, url: String) -> Result> { + let owned = mem::take(image); + *image = owned.url(url); + Ok(image.clone()) + } + + #[rhai_fn(name = "width", return_raw, global, pure)] + pub fn image_width(image: &mut RhaiImage, width: i64) -> Result> { + let owned = mem::take(image); + *image = owned.width(width as u32); + Ok(image.clone()) + } + + #[rhai_fn(name = "height", return_raw, global, pure)] + pub fn image_height(image: &mut RhaiImage, height: i64) -> Result> { + let owned = mem::take(image); + *image = owned.height(height as u32); + Ok(image.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_image_id(image: &mut RhaiImage) -> i64 { image.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_image_created_at(image: &mut RhaiImage) -> i64 { image.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_image_modified_at(image: &mut RhaiImage) -> i64 { image.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_image_title(image: &mut RhaiImage) -> String { image.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_image_description(image: &mut RhaiImage) -> Option { image.description.clone() } + + #[rhai_fn(get = "url", pure)] + pub fn get_image_url(image: &mut RhaiImage) -> String { image.url.clone() } + + #[rhai_fn(get = "width", pure)] + pub fn get_image_width(image: &mut RhaiImage) -> u32 { image.width } + + #[rhai_fn(get = "height", pure)] + pub fn get_image_height(image: &mut RhaiImage) -> u32 { image.height } + + // --- Pdf Functions --- + #[rhai_fn(name = "new_pdf")] + pub fn new_pdf() -> RhaiPdf { + RhaiPdf::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn pdf_title(pdf: &mut RhaiPdf, title: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.title(title); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn pdf_description(pdf: &mut RhaiPdf, description: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.description(description); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "url", return_raw, global, pure)] + pub fn pdf_url(pdf: &mut RhaiPdf, url: String) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.url(url); + Ok(pdf.clone()) + } + + #[rhai_fn(name = "page_count", return_raw, global, pure)] + pub fn pdf_page_count(pdf: &mut RhaiPdf, page_count: i64) -> Result> { + let owned = mem::take(pdf); + *pdf = owned.page_count(page_count as u32); + Ok(pdf.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_pdf_id(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_pdf_created_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_pdf_modified_at(pdf: &mut RhaiPdf) -> i64 { pdf.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_pdf_title(pdf: &mut RhaiPdf) -> String { pdf.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_pdf_description(pdf: &mut RhaiPdf) -> Option { pdf.description.clone() } + + #[rhai_fn(get = "url", pure)] + pub fn get_pdf_url(pdf: &mut RhaiPdf) -> String { pdf.url.clone() } + + #[rhai_fn(get = "page_count", pure)] + pub fn get_pdf_page_count(pdf: &mut RhaiPdf) -> u32 { pdf.page_count } + + // --- Markdown Functions --- + #[rhai_fn(name = "new_markdown")] + pub fn new_markdown() -> RhaiMarkdown { + RhaiMarkdown::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn markdown_title(markdown: &mut RhaiMarkdown, title: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.title(title); + Ok(markdown.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn markdown_description(markdown: &mut RhaiMarkdown, description: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.description(description); + Ok(markdown.clone()) + } + + #[rhai_fn(name = "content", return_raw, global, pure)] + pub fn markdown_content(markdown: &mut RhaiMarkdown, content: String) -> Result> { + let owned = mem::take(markdown); + *markdown = owned.content(content); + Ok(markdown.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_markdown_id(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_markdown_created_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_markdown_modified_at(markdown: &mut RhaiMarkdown) -> i64 { markdown.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_markdown_title(markdown: &mut RhaiMarkdown) -> String { markdown.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_markdown_description(markdown: &mut RhaiMarkdown) -> Option { markdown.description.clone() } + + #[rhai_fn(get = "content", pure)] + pub fn get_markdown_content(markdown: &mut RhaiMarkdown) -> String { markdown.content.clone() } + + // --- TocEntry Functions --- + #[rhai_fn(name = "new_toc_entry")] + pub fn new_toc_entry() -> RhaiTocEntry { + RhaiTocEntry::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn toc_entry_title(entry: &mut RhaiTocEntry, title: String) -> Result> { + let owned = mem::take(entry); + *entry = owned.title(title); + Ok(entry.clone()) + } + + #[rhai_fn(name = "page", return_raw, global, pure)] + pub fn toc_entry_page(entry: &mut RhaiTocEntry, page: i64) -> Result> { + let owned = mem::take(entry); + *entry = owned.page(page as u32); + Ok(entry.clone()) + } + + #[rhai_fn(name = "add_subsection", return_raw, global, pure)] + pub fn toc_entry_add_subsection(entry: &mut RhaiTocEntry, subsection: RhaiTocEntry) -> Result> { + let owned = mem::take(entry); + *entry = owned.add_subsection(subsection); + Ok(entry.clone()) + } + + #[rhai_fn(get = "title", pure)] + pub fn get_toc_entry_title(entry: &mut RhaiTocEntry) -> String { entry.title.clone() } + + #[rhai_fn(get = "page", pure)] + pub fn get_toc_entry_page(entry: &mut RhaiTocEntry) -> u32 { entry.page } + + #[rhai_fn(get = "subsections", pure)] + pub fn get_toc_entry_subsections(entry: &mut RhaiTocEntry) -> Vec { entry.subsections.clone() } + + // --- Book Functions --- + #[rhai_fn(name = "new_book")] + pub fn new_book() -> RhaiBook { + RhaiBook::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn book_title(book: &mut RhaiBook, title: String) -> Result> { + let owned = mem::take(book); + *book = owned.title(title); + Ok(book.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn book_description(book: &mut RhaiBook, description: String) -> Result> { + let owned = mem::take(book); + *book = owned.description(description); + Ok(book.clone()) + } + + #[rhai_fn(name = "add_page", return_raw, global, pure)] + pub fn book_add_page(book: &mut RhaiBook, content: String) -> Result> { + let owned = mem::take(book); + *book = owned.add_page(content); + Ok(book.clone()) + } + + #[rhai_fn(name = "add_toc_entry", return_raw, global, pure)] + pub fn book_add_toc_entry(book: &mut RhaiBook, entry: RhaiTocEntry) -> Result> { + let owned = mem::take(book); + *book = owned.add_toc_entry(entry); + Ok(book.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_book_id(book: &mut RhaiBook) -> i64 { book.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_book_created_at(book: &mut RhaiBook) -> i64 { book.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_book_modified_at(book: &mut RhaiBook) -> i64 { book.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_book_title(book: &mut RhaiBook) -> String { book.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_book_description(book: &mut RhaiBook) -> Option { book.description.clone() } + + #[rhai_fn(get = "table_of_contents", pure)] + pub fn get_book_table_of_contents(book: &mut RhaiBook) -> Vec { book.table_of_contents.clone() } + + #[rhai_fn(get = "pages", pure)] + pub fn get_book_pages(book: &mut RhaiBook) -> Vec { book.pages.clone() } + + // --- Slides Functions --- + #[rhai_fn(name = "new_slides")] + pub fn new_slides() -> RhaiSlides { + RhaiSlides::new() + } + + #[rhai_fn(name = "title", return_raw, global, pure)] + pub fn slides_title(slides: &mut RhaiSlides, title: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.title(title); + Ok(slides.clone()) + } + + #[rhai_fn(name = "description", return_raw, global, pure)] + pub fn slides_description(slides: &mut RhaiSlides, description: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.description(description); + Ok(slides.clone()) + } + + #[rhai_fn(name = "add_slide", return_raw, global, pure)] + pub fn slides_add_slide(slides: &mut RhaiSlides, url: String, title: String) -> Result> { + let owned = mem::take(slides); + let title_opt = if title.is_empty() { None } else { Some(title) }; + *slides = owned.add_slide(url, title_opt); + Ok(slides.clone()) + } + + #[rhai_fn(name = "add_slide", return_raw, global, pure)] + pub fn slides_add_slide_no_title(slides: &mut RhaiSlides, url: String) -> Result> { + let owned = mem::take(slides); + *slides = owned.add_slide(url, None); + Ok(slides.clone()) + } + + #[rhai_fn(get = "id", pure)] + pub fn get_slides_id(slides: &mut RhaiSlides) -> i64 { slides.base_data.id as i64 } + + #[rhai_fn(get = "created_at", pure)] + pub fn get_slides_created_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.created_at } + + #[rhai_fn(get = "modified_at", pure)] + pub fn get_slides_modified_at(slides: &mut RhaiSlides) -> i64 { slides.base_data.modified_at } + + #[rhai_fn(get = "title", pure)] + pub fn get_slides_title(slides: &mut RhaiSlides) -> String { slides.title.clone() } + + #[rhai_fn(get = "description", pure)] + pub fn get_slides_description(slides: &mut RhaiSlides) -> Option { slides.description.clone() } + + #[rhai_fn(get = "slide_urls", pure)] + pub fn get_slides_slide_urls(slides: &mut RhaiSlides) -> Vec { slides.slide_urls.clone() } + + #[rhai_fn(get = "slide_titles", pure)] + pub fn get_slides_slide_titles(slides: &mut RhaiSlides) -> Vec> { slides.slide_titles.clone() } +} + +pub fn register_library_rhai_module(engine: &mut Engine, db: Arc) { + let module = exported_module!(rhai_library_module); + engine.register_global_module(module.into()); + + let mut db_module = Module::new(); + + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + register_json_method::(engine); + + // Register .json() method for our custom CollectionArray type + register_json_method::(engine); + + // --- Collection DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_collection", move |collection: RhaiCollection| -> Result> { + let result = db_clone.set(&collection) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_collection", move |id: i64| -> Result> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(collection_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Collection with ID {} not found", collection_id).into(), Position::NONE))) + }); + + let db_clone_list_collections = db.clone(); + db_module.set_native_fn("list_collections", move || -> Result> { + let collections_vec: Vec = db_clone_list_collections + .collection::() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - access): {:?}", e).into(), Position::NONE)))? + .get_all() + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error (list_collections - get_all): {:?}", e).into(), Position::NONE)))?; + Ok(RhaiCollectionArray(collections_vec)) // Wrap in RhaiCollectionArray + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_collection", move |id: i64| -> Result<(), Box> { + let collection_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(collection_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Image DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_image", move |image: RhaiImage| -> Result> { + let result = db_clone.set(&image) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_image", move |id: i64| -> Result> { + let image_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(image_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Image with ID {} not found", image_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_image", move |id: i64| -> Result<(), Box> { + let image_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(image_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Pdf DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_pdf", move |pdf: RhaiPdf| -> Result> { + let result = db_clone.set(&pdf) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_pdf", move |id: i64| -> Result> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(pdf_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Pdf with ID {} not found", pdf_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_pdf", move |id: i64| -> Result<(), Box> { + let pdf_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(pdf_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Markdown DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_markdown", move |markdown: RhaiMarkdown| -> Result> { + let result = db_clone.set(&markdown) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_markdown", move |id: i64| -> Result> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(markdown_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Markdown with ID {} not found", markdown_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_markdown", move |id: i64| -> Result<(), Box> { + let markdown_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(markdown_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Book DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_book", move |book: RhaiBook| -> Result> { + let result = db_clone.set(&book) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_book", move |id: i64| -> Result> { + let book_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(book_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Book with ID {} not found", book_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_book", move |id: i64| -> Result<(), Box> { + let book_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(book_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + // --- Slides DB Functions --- + let db_clone = db.clone(); + db_module.set_native_fn("save_slides", move |slides: RhaiSlides| -> Result> { + let result = db_clone.set(&slides) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(result.1) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("get_slides", move |id: i64| -> Result> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone.get_by_id(slides_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))? + .ok_or_else(|| Box::new(EvalAltResult::ErrorRuntime(format!("Slides with ID {} not found", slides_id).into(), Position::NONE))) + }); + + let db_clone = db.clone(); + db_module.set_native_fn("delete_slides", move |id: i64| -> Result<(), Box> { + let slides_id = id_from_i64_to_u32(id)?; + db_clone.collection::().unwrap().delete_by_id(slides_id) + .map_err(|e| Box::new(EvalAltResult::ErrorRuntime(format!("DB Error: {:?}", e).into(), Position::NONE)))?; + Ok(()) + }); + + engine.register_global_module(db_module.into()); +} diff --git a/heromodels/src/models/mod.rs b/heromodels/src/models/mod.rs index b4c8db2..eae5ca6 100644 --- a/heromodels/src/models/mod.rs +++ b/heromodels/src/models/mod.rs @@ -3,8 +3,10 @@ pub mod core; pub mod userexample; // pub mod productexample; // Temporarily remove as files are missing pub mod calendar; +pub mod circle; pub mod governance; pub mod finance; +pub mod library; pub mod legal; pub mod flow; pub mod biz; @@ -15,18 +17,25 @@ pub use core::Comment; pub use userexample::User; // pub use productexample::Product; // Temporarily remove pub use calendar::{Calendar, Event, Attendee, AttendanceStatus}; +pub use circle::{Circle}; pub use governance::{Proposal, ProposalStatus, VoteEventStatus, Ballot, VoteOption, AttachedFile}; pub use finance::{Account, Asset, AssetType}; pub use finance::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus}; pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus}; pub use flow::{Flow, FlowStep, SignatureRequirement}; pub use biz::{Sale, SaleItem, SaleStatus}; +pub use library::items::{Image, Pdf, Markdown}; +pub use library::collection::Collection; pub use flow::register_flow_rhai_module; #[cfg(feature = "rhai")] pub use calendar::register_calendar_rhai_module; +#[cfg(feature = "rhai")] +pub use circle::register_circle_rhai_module; pub use legal::register_legal_rhai_module; #[cfg(feature = "rhai")] pub use biz::register_biz_rhai_module; #[cfg(feature = "rhai")] pub use projects::register_projects_rhai_module; +#[cfg(feature = "rhai")] +pub use library::register_library_rhai_module;