Merge branch 'development_rhai'

This commit is contained in:
timurgordon
2025-06-25 20:46:15 +03:00
137 changed files with 7274 additions and 6786 deletions

View File

@@ -57,7 +57,9 @@ where
fn get_all(&self) -> Result<Vec<V>, Error<Self::Error>>;
/// Begin a transaction for this collection
fn begin_transaction(&self) -> Result<Box<dyn Transaction<Error = Self::Error>>, Error<Self::Error>>;
fn begin_transaction(
&self,
) -> Result<Box<dyn Transaction<Error = Self::Error>>, Error<Self::Error>>;
}
/// Errors returned by the DB implementation

View File

@@ -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<u32> =
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<u32> = HashSet::new();
// Use getall to find all index entries (values are serialized HashSet<u32>) 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<u32> of object IDs.
match bincode::serde::decode_from_slice::<HashSet<u32>, _>(
&raw_ids_set_bytes,
BINCODE_CONFIG,
) {
Ok((ids_set, _)) => {
// Destructure the tuple (HashSet<u32>, 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));
}
}
}
};

View File

@@ -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);

View File

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

View File

@@ -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<u64>,
}
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<u64>) -> 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<OurDB>` 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<OurDB>,
public_key: &str,
object_id: u32,
_object_type: &str,
) -> bool {
let circle = db
.collection::<Circle>()
.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::<Access>()
.expect("Failed to get Access collection")
.get::<access_index::object_id, _>(&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<OurDB>,
public_key: &str,
) -> bool {
let circle = db
.collection::<Circle>()
.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;
}

View File

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

View File

@@ -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<Self, Box<EvalAltResult>> {
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
}

View File

@@ -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<ProductComponent>,
}
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<ProductComponent>) -> 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<u32>) -> Self {
self.base_data.comments = comment_ids;
self
}
// BaseModelData field operations are now handled by BaseModelDataOps trait
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<String>) -> 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<u32>) -> Self {
self.base_data.comments = comments;
self
}
// BaseModelData operations are now handled by BaseModelDataOps trait
}

View File

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

View File

@@ -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<Self, String> {
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<String>,
/// Start time of the event
pub start_time: DateTime<Utc>,
/// End time of the event
pub end_time: DateTime<Utc>,
/// List of attendee IDs for the event
pub attendees: Vec<u32>,
/// 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<Attendee>,
/// Optional location of the event
pub location: Option<String>,
/// Color for the event (hex color code)
@@ -110,46 +132,18 @@ impl Event {
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
}
/// Creates a new event with auto-generated ID
pub fn new(title: impl ToString, start_time: DateTime<Utc>, end_time: DateTime<Utc>) -> 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<u32>,
title: impl ToString,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
) -> 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<Utc>,
new_end_time: DateTime<Utc>,
) -> 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<crate::db::hero::OurDB>")]
// Temporarily removed rhai_model_export macro to fix compilation issues
// #[rhai_model_export(
// db_type = "std::sync::Arc<crate::db::hero::OurDB>",
// )]
#[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<String>,

View File

@@ -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};

View File

@@ -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<Calendar, Box<EvalAltResult>> {
Ok(Calendar::new(None, name))
}
fn new_event_rhai(
context: NativeCallContext,
title_rhai: ImmutableString,
start_time_ts: i64,
end_time_ts: i64,
) -> Result<Event, Box<EvalAltResult>> {
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<OurDB>) {
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<Event, Box<EvalAltResult>> {
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<Calendar, Box<EvalAltResult>> { 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<i64, Box<EvalAltResult>> { Ok(c.base_data.id as i64) },
);
engine.register_get(
"name",
|c: &mut Calendar| -> Result<String, Box<EvalAltResult>> {
// println!("Rhai attempting to get Calendar.name: {}", c.name); // Debug print
Ok(c.name.clone())
},
);
engine.register_get(
"description",
|c: &mut Calendar| -> Result<Option<String>, Box<EvalAltResult>> {
Ok(c.description.clone())
},
);
// Register getter for Calendar.base_data
engine.register_get(
"base_data",
|c: &mut Calendar| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(c.base_data.clone()) },
);
// Register getters for BaseModelData
engine.register_get(
"id",
|bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { 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<i64, Box<EvalAltResult>> {
Ok(e.base_data.id as i64)
});
engine.register_get(
"title",
|e: &mut Event| -> Result<String, Box<EvalAltResult>> { Ok(e.title.clone()) },
);
// Add more getters for Event fields as needed
}

View File

@@ -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<String>,
/// List of related circles
pub circles: Vec<String>,
/// List of members in the circle (their public keys)
pub members: Vec<String>,
/// Logo URL or symbol for the circle
pub logo: Option<String>,
/// 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
}
}

View File

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

View File

@@ -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<T>(engine: &mut Engine)
where
T: CustomType + Clone + Serialize,
{
let to_json_fn = |obj: &mut T| -> Result<String, Box<EvalAltResult>> {
serde_json::to_string(obj).map_err(|e| e.to_string().into())
};
engine.build_type::<T>().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, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiThemeData, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<RhaiCircle, Box<EvalAltResult>> {
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<String> {
circle.description.clone()
}
#[rhai_fn(name = "get_circles", pure)]
pub fn get_circle_circles(circle: &mut RhaiCircle) -> Vec<String> {
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<String> {
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<OurDB>) {
engine.build_type::<RhaiCircle>();
engine.build_type::<RhaiThemeData>();
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::<Circle>(engine);
register_json_method::<ThemeData>(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<Circle, Box<EvalAltResult>> {
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<EvalAltResult>> {
let result = db_clone_delete_circle
.collection::<Circle>()
.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<Circle, Box<EvalAltResult>> {
let all_circles: Vec<Circle> = 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<RhaiCircle, Box<EvalAltResult>> {
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<Circle, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
let collection = db_clone_list_circles.collection::<Circle>().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.");
}

View File

@@ -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<String>,
pub address: String,
pub phone: String,
pub email: String,
pub notes: Option<String>,
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<String>,
pub contacts: Vec<u32>,
}
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<u32>) -> Self {
self.contacts = contacts;
self
}
pub fn add_contact(mut self, contact: u32) -> Self {
self.contacts.push(contact);
self
}
}

View File

@@ -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};

View File

@@ -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<u32>, // 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<u32>) -> Self {
self.parent_comment_id = parent_id;
self
}
}

View File

@@ -4,4 +4,3 @@ pub mod model;
// Re-export key types for convenience
pub use comment::Comment;

View File

@@ -0,0 +1 @@

View File

@@ -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<Asset>, // list of assets in this account
pub assets: Vec<u32>, // 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<u32>,
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<impl ToString>,
description: Option<impl ToString>,
address: Option<impl ToString>,
pubkey: Option<impl ToString>,
) -> 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;
}
}

View File

@@ -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<u32>,
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);

View File

@@ -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<u32>,
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<DateTime<Utc>>,
tags: Vec<String>,
image_url: Option<impl ToString>,
) -> 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<DateTime<Utc>>) -> Self {
self.expires_at = expires_at;
self
}
/// Set the image URL of the listing
pub fn image_url(mut self, image_url: Option<impl ToString>) -> 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<Self, &'static str> {
// 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<DateTime<Utc>>) -> 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<Self, &'static str> {
pub fn complete_sale(mut self) -> Result<Self, &'static str> {
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<impl ToString>) -> 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<impl ToString>,
description: Option<impl ToString>,
price: Option<f64>,
image_url: Option<impl ToString>,
) -> 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
}

View File

@@ -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};

View File

@@ -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<dyn StdError>
// 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<AssetType, Box<EvalAltResult>> {
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<ListingStatus, Box<EvalAltResult>> {
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<ListingType, Box<EvalAltResult>> {
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<BidStatus, Box<EvalAltResult>> {
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<Mutex<HashMap<u32, Account>>>,
db_assets: Arc<Mutex<HashMap<u32, Asset>>>,
db_listings: Arc<Mutex<HashMap<u32, Listing>>>,
) {
// --- Account model ---
engine.register_type_with_name::<Account>("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<Array, Box<EvalAltResult>> {
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<Account, Box<EvalAltResult>> {
acc.name = name.to_string(); Ok(acc)
});
engine.register_fn("set_user_id", |mut acc: Account, user_id: INT| -> Result<Account, Box<EvalAltResult>> {
acc.user_id = user_id as u32; Ok(acc)
});
engine.register_fn("set_description", |mut acc: Account, description: ImmutableString| -> Result<Account, Box<EvalAltResult>> {
acc.description = description.to_string(); Ok(acc)
});
engine.register_fn("set_ledger", |mut acc: Account, ledger: ImmutableString| -> Result<Account, Box<EvalAltResult>> {
acc.ledger = ledger.to_string(); Ok(acc)
});
engine.register_fn("set_address", |mut acc: Account, address: ImmutableString| -> Result<Account, Box<EvalAltResult>> {
acc.address = address.to_string(); Ok(acc)
});
engine.register_fn("set_pubkey", |mut acc: Account, pubkey: ImmutableString| -> Result<Account, Box<EvalAltResult>> {
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<Account, Box<EvalAltResult>> {
acc.assets.push(asset);
Ok(acc)
});
// --- Asset model ---
engine.register_type_with_name::<Asset>("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, Box<EvalAltResult>> {
asset.name = name.to_string(); Ok(asset)
});
engine.register_fn("set_description", |mut asset: Asset, description: ImmutableString| -> Result<Asset, Box<EvalAltResult>> {
asset.description = description.to_string(); Ok(asset)
});
engine.register_fn("set_amount", |mut asset: Asset, amount: f64| -> Result<Asset, Box<EvalAltResult>> {
asset.amount = amount; Ok(asset)
});
engine.register_fn("set_address", |mut asset: Asset, address: ImmutableString| -> Result<Asset, Box<EvalAltResult>> {
asset.address = address.to_string(); Ok(asset)
});
engine.register_fn("set_asset_type", |mut asset: Asset, asset_type_str: ImmutableString| -> Result<Asset, Box<EvalAltResult>> {
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, Box<EvalAltResult>> {
asset.decimals = decimals as u8; Ok(asset)
});
// --- Listing model ---
engine.register_type_with_name::<Listing>("Listing");
engine.register_fn("new_listing", || -> Listing {
Listing::new(None, "", "", "", AssetType::Native, "", 0.0, "", ListingType::FixedPrice, None, Vec::new(), None::<String>)
});
// 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<Array, Box<EvalAltResult>> {
Ok(l.tags.iter().map(|s| Dynamic::from(s.clone())).collect())
});
engine.register_get("image_url", |l: &mut Listing| -> Option<ImmutableString> { l.image_url.as_ref().map(|s| s.clone().into()) });
engine.register_get("bids_list", |l: &mut Listing| -> Result<Array, Box<EvalAltResult>> {
Ok(l.bids.iter().cloned().map(rhai::Dynamic::from).collect())
});
// Setters (Builder Pattern)
engine.register_fn("set_title", |mut l: Listing, title: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
l.title = title.to_string(); Ok(l)
});
engine.register_fn("set_description", |mut l: Listing, description: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
l.description = description.to_string(); Ok(l)
});
engine.register_fn("set_asset_id", |mut l: Listing, asset_id: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
l.asset_id = asset_id.to_string(); Ok(l)
});
engine.register_fn("set_asset_type", |mut l: Listing, asset_type_str: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
l.seller_id = seller_id.to_string(); Ok(l)
});
engine.register_fn("set_price", |mut l: Listing, price: f64| -> Result<Listing, Box<EvalAltResult>> {
l.price = price; Ok(l)
});
engine.register_fn("set_currency", |mut l: Listing, currency: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
l.currency = currency.to_string(); Ok(l)
});
engine.register_fn("set_listing_type", |mut l: Listing, listing_type: ImmutableString| -> Result<Listing, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
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<INT>| -> Result<Listing, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
l.tags.push(tag.to_string());
Ok(l)
});
engine.register_fn("set_image_url", |mut l: Listing, image_url: Option<ImmutableString>| -> Result<Listing, Box<EvalAltResult>> {
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, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
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, Box<EvalAltResult>> {
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>("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<Bid, Box<EvalAltResult>> {
// 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<Bid, Box<EvalAltResult>> {
// 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<Bid, Box<EvalAltResult>> {
// 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<Account, Box<EvalAltResult>> {
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<Asset, Box<EvalAltResult>> {
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<Listing, Box<EvalAltResult>> {
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());
}

View File

@@ -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(),
}

View File

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

View File

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

View File

@@ -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<u32, Box<EvalAltResult>> {
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<OurDB>) {
// --- 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<Flow, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> { Ok(flow.base_data.id as i64) });
engine.register_get("base_data", |flow: &mut Flow| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(flow.base_data.clone()) });
engine.register_get("flow_uuid", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.flow_uuid.clone()) });
engine.register_get("name", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.name.clone()) });
engine.register_get("status", |flow: &mut Flow| -> Result<String, Box<EvalAltResult>> { Ok(flow.status.clone()) });
engine.register_get("steps", |flow: &mut Flow| -> Result<rhai::Array, Box<EvalAltResult>> {
let rhai_array = flow.steps.iter().cloned().map(Dynamic::from).collect::<rhai::Array>();
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<FlowStep, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> { Ok(step.base_data.id as i64) });
engine.register_get("base_data", |step: &mut FlowStep| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(step.base_data.clone()) });
engine.register_get("description", |step: &mut FlowStep| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match step.description.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("step_order", |step: &mut FlowStep| -> Result<i64, Box<EvalAltResult>> { Ok(step.step_order as i64) });
engine.register_get("status", |step: &mut FlowStep| -> Result<String, Box<EvalAltResult>> { 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<SignatureRequirement, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> { Ok(sr.base_data.id as i64) });
engine.register_get("base_data", |sr: &mut SignatureRequirement| -> Result<BaseModelData, Box<EvalAltResult>> { Ok(sr.base_data.clone()) });
engine.register_get("flow_step_id", |sr: &mut SignatureRequirement| -> Result<i64, Box<EvalAltResult>> { Ok(sr.flow_step_id as i64) });
engine.register_get("public_key", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { Ok(sr.public_key.clone()) });
engine.register_get("message", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { Ok(sr.message.clone()) });
engine.register_get("signed_by", |sr: &mut SignatureRequirement| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match sr.signed_by.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("signature", |sr: &mut SignatureRequirement| -> Result<Dynamic, Box<EvalAltResult>> { Ok(match sr.signature.clone() { Some(s) => Dynamic::from(s), None => Dynamic::from(()) }) });
engine.register_get("status", |sr: &mut SignatureRequirement| -> Result<String, Box<EvalAltResult>> { 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<i64, Box<EvalAltResult>> { Ok(bmd.id as i64) });
engine.register_get("created_at", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.created_at) });
engine.register_get("modified_at", |bmd: &mut BaseModelData| -> Result<i64, Box<EvalAltResult>> { Ok(bmd.modified_at) });
// engine.register_get("comments", |bmd: &mut BaseModelData| Ok(bmd.comments.clone())); // Rhai might need specific handling for Vec<String>
// --- Database Interaction Functions ---
let captured_db_for_set_flow = Arc::clone(&db);
engine.register_fn("set_flow", move |flow: Flow| -> Result<(), Box<EvalAltResult>> {
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<Flow, Box<EvalAltResult>> {
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<EvalAltResult>> {
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<SignatureRequirement, Box<EvalAltResult>> {
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.");
}

View File

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

View File

@@ -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<Utc>,
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<Utc>) -> 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<Utc>,
pub members: Vec<CommitteeMember>,
}
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<Utc>) -> 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<IndexKey> {
vec![
IndexKey {
name: "company_id",
value: self.company_id.to_string(),
},
IndexKey {
name: "name",
value: self.name.clone(),
},
]
}
}

View File

@@ -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<Utc>,
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<Utc>) -> 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<IndexKey> {
vec![
IndexKey {
name: "name",
value: self.name.clone(),
},
IndexKey {
name: "registration_number",
value: self.registration_number.clone(),
},
]
}
}

View File

@@ -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<Utc>,
pub end_time: DateTime<Utc>,
pub location: String,
pub agenda: String,
pub minutes: String,
pub attendees: Vec<Attendee>,
}
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<Utc>) -> Self {
self.start_time = start_time;
self
}
/// Set the end time
pub fn end_time(mut self, end_time: DateTime<Utc>) -> 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<IndexKey> {
vec![
IndexKey {
name: "company_id",
value: self.company_id.to_string(),
},
IndexKey {
name: "title",
value: self.title.clone(),
},
]
}
}

View File

@@ -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};

View File

@@ -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<Utc>,
pub effective_date: Option<DateTime<Utc>>,
pub expiry_date: Option<DateTime<Utc>>,
pub approvals: Vec<String>,
}
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<Utc>) -> Self {
self.proposed_date = proposed_date;
self
}
/// Set the effective date
pub fn effective_date(mut self, effective_date: Option<DateTime<Utc>>) -> Self {
self.effective_date = effective_date;
self
}
/// Set the expiry date
pub fn expiry_date(mut self, expiry_date: Option<DateTime<Utc>>) -> 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<IndexKey> {
vec![
IndexKey {
name: "company_id",
value: self.company_id.to_string(),
},
IndexKey {
name: "title",
value: self.title.clone(),
},
]
}
}

View File

@@ -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<IndexKey> {
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(),
},
]
}
}

View File

@@ -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<IndexKey> {
vec![
IndexKey {
name: "name",
value: self.name.clone(),
},
IndexKey {
name: "email",
value: self.email.clone(),
},
]
}
}

View File

@@ -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<Utc>,
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<Utc>) -> 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<Utc>,
pub end_date: DateTime<Utc>,
pub ballots: Vec<Ballot>,
}
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<Utc>) -> Self {
self.start_date = start_date;
self
}
/// Set the end date
pub fn end_date(mut self, end_date: DateTime<Utc>) -> 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<IndexKey> {
vec![
IndexKey {
name: "company_id",
value: self.company_id.to_string(),
},
IndexKey {
name: "title",
value: self.title.clone(),
},
]
}
}

View File

@@ -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,
}
}
}

View File

@@ -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<crate::db::hero::OurDB>")]
#[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<crate::db::hero::OurDB>")]
// 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<Utc>,
pub updated_at: DateTime<Utc>,
// Voting event aspects
pub vote_start_date: DateTime<Utc>,
pub vote_end_date: DateTime<Utc>,
@@ -123,6 +122,35 @@ pub struct Proposal {
pub options: Vec<VoteOption>,
pub ballots: Vec<Ballot>, // This will store actual Ballot structs
pub private_group: Option<Vec<u32>>, // Optional list of eligible user IDs
pub tags: Vec<String>,
pub comments: Vec<Comment>,
pub attached_files: Vec<AttachedFile>,
pub urgency_score: Option<f32>,
}
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<Utc>,
updated_at: DateTime<Utc>,
vote_start_date: DateTime<Utc>,
vote_end_date: DateTime<Utc>,
tags: Vec<String>,
urgency_score: Option<f32>,
) -> 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,
}
}

View File

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

View File

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

View File

@@ -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<u32, Box<EvalAltResult>> {
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<u64, Box<EvalAltResult>> {
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<i32, Box<EvalAltResult>> {
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<OurDB>) {
// --- 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>("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>("SignerStatus"); // Expose the type itself
// --- ContractRevision ---
engine.register_type_with_name::<ContractRevision>("ContractRevision");
engine.register_fn(
"new_contract_revision",
move |context: NativeCallContext,
version_i64: i64,
content: String,
created_at_i64: i64,
created_by: String|
-> Result<ContractRevision, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> {
Ok(revision.version as i64)
},
);
engine.register_get(
"content",
|revision: &mut ContractRevision| -> Result<String, Box<EvalAltResult>> {
Ok(revision.content.clone())
},
);
engine.register_get(
"created_at",
|revision: &mut ContractRevision| -> Result<i64, Box<EvalAltResult>> {
Ok(revision.created_at as i64)
},
);
engine.register_get(
"created_by",
|revision: &mut ContractRevision| -> Result<String, Box<EvalAltResult>> {
Ok(revision.created_by.clone())
},
);
engine.register_get(
"comments",
|revision: &mut ContractRevision| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(revision
.comments
.clone()
.map_or(Dynamic::UNIT, Dynamic::from))
},
);
// --- ContractSigner ---
engine.register_type_with_name::<ContractSigner>("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<ContractSigner, Box<EvalAltResult>> {
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<ContractSigner, Box<EvalAltResult>> {
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<bool, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
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<EvalAltResult>> {
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<String, Box<EvalAltResult>> {
Ok(signer.id.clone())
},
);
engine.register_get(
"name",
|signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
Ok(signer.name.clone())
},
);
engine.register_get(
"email",
|signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
Ok(signer.email.clone())
},
);
engine.register_get(
"status",
|signer: &mut ContractSigner| -> Result<SignerStatus, Box<EvalAltResult>> {
Ok(signer.status.clone())
},
);
engine.register_get(
"signed_at_ts",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer
.signed_at
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"comments",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer.comments.clone().map_or(Dynamic::UNIT, Dynamic::from))
},
);
engine.register_get(
"signed_at",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(signer
.signed_at
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts)))
},
);
engine.register_get(
"last_reminder_mail_sent_at",
|signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
Ok(signer
.signature_data
.as_ref()
.map_or(Dynamic::UNIT, |data| Dynamic::from(data.clone())))
},
);
// --- Contract ---
engine.register_type_with_name::<Contract>("Contract");
engine.register_fn(
"new_contract",
move |context: NativeCallContext,
base_id_i64: i64,
contract_id: String|
-> Result<Contract, Box<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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::<ContractSigner>())
.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::<ContractRevision>())
.collect();
contract.revisions(revisions_vec)
},
);
engine.register_fn(
"current_version",
|context: NativeCallContext,
contract: Contract,
version_i64: i64|
-> Result<Contract, Box<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> {
Ok(contract.base_data.id as i64)
},
);
engine.register_get(
"created_at_ts",
|contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> {
Ok(contract.base_data.created_at as i64)
},
);
engine.register_get(
"updated_at_ts",
|contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> {
Ok(contract.base_data.modified_at as i64)
},
);
engine.register_get(
"contract_id",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.contract_id.clone())
},
);
engine.register_get(
"title",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.title.clone())
},
);
engine.register_get(
"description",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.description.clone())
},
);
engine.register_get(
"contract_type",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.contract_type.clone())
},
);
engine.register_get(
"status",
|contract: &mut Contract| -> Result<ContractStatus, Box<EvalAltResult>> {
Ok(contract.status.clone())
},
);
engine.register_get(
"created_by",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.created_by.clone())
},
);
engine.register_get(
"terms_and_conditions",
|contract: &mut Contract| -> Result<String, Box<EvalAltResult>> {
Ok(contract.terms_and_conditions.clone())
},
);
engine.register_get(
"start_date",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract
.start_date
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"end_date",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
Ok(contract
.end_date
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"renewal_period_days",
|contract: &mut Contract| -> Result<Dynamic, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
Ok(contract
.last_signed_date
.map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
},
);
engine.register_get(
"current_version",
|contract: &mut Contract| -> Result<i64, Box<EvalAltResult>> {
Ok(contract.current_version as i64)
},
);
engine.register_get(
"signers",
|contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> {
let rhai_array = contract
.signers
.iter()
.cloned()
.map(Dynamic::from)
.collect::<Array>();
Ok(rhai_array)
},
);
engine.register_get(
"revisions",
|contract: &mut Contract| -> Result<Array, Box<EvalAltResult>> {
let rhai_array = contract
.revisions
.iter()
.cloned()
.map(Dynamic::from)
.collect::<Array>();
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<EvalAltResult>> {
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<Contract, Box<EvalAltResult>> {
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,
))
})
},
);
}

View File

@@ -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<String>,
/// List of image item IDs belonging to this collection
pub images: Vec<u32>,
/// List of PDF item IDs belonging to this collection
pub pdfs: Vec<u32>,
/// List of Markdown item IDs belonging to this collection
pub markdowns: Vec<u32>,
/// List of Book item IDs belonging to this collection
pub books: Vec<u32>,
/// List of Slides item IDs belonging to this collection
pub slides: Vec<u32>,
}
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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the collection.
pub fn description(mut self, description: impl Into<String>) -> 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
}
}

View File

@@ -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<String>,
/// 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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the image.
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Sets the URL of the image.
pub fn url(mut self, url: impl Into<String>) -> 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<String>,
/// 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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the PDF.
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Sets the URL of the PDF.
pub fn url(mut self, url: impl Into<String>) -> 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<String>,
/// 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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the document.
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Sets the content of the document.
pub fn content(mut self, content: impl Into<String>) -> 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<TocEntry>,
}
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<String>) -> 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<String>,
/// Table of contents
pub table_of_contents: Vec<TocEntry>,
/// Pages content (markdown strings)
pub pages: Vec<String>,
}
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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the book.
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Adds a page to the book.
pub fn add_page(mut self, content: impl Into<String>) -> 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<TocEntry>) -> Self {
self.table_of_contents = toc;
self
}
/// Sets all pages at once.
pub fn pages(mut self, pages: Vec<String>) -> 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<String>,
/// List of slides
pub slides: Vec<Slide>,
}
#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, CustomType)]
pub struct Slide {
pub image_url: String,
pub title: Option<String>,
pub description: Option<String>,
}
impl Slide {
pub fn new() -> Self {
Self {
image_url: String::new(),
title: None,
description: None,
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.image_url = url.into();
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> 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<String>) -> Self {
self.title = title.into();
self
}
/// Sets the description of the slideshow.
pub fn description(mut self, description: impl Into<String>) -> 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
}
}

View File

@@ -0,0 +1,2 @@
pub mod collection;
pub mod items;

View File

@@ -0,0 +1,3 @@
## Object Model
This is a generic object model mostly used for testing purposes.

View File

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

View File

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

View File

@@ -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};

View File

@@ -0,0 +1,3 @@
## Object Model
This is a generic object model mostly used for testing purposes.

View File

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

View File

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

View File

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

View File

@@ -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<String>,
pub status: ProjectStatus, // Or a new EpicStatus enum if needed
pub project_id: Option<u32>, // Link to a general project/board
pub start_date: Option<DateTime<Utc>>,
pub due_date: Option<DateTime<Utc>>,
pub tags: Vec<String>,
// 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<u32>,
}
impl Epic {
pub fn new(
name: String,
description: Option<String>,
status: ProjectStatus,
project_id: Option<u32>,
start_date: Option<DateTime<Utc>>,
due_date: Option<DateTime<Utc>>,
tags: Vec<String>,
) -> 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(),
}
}
}

View File

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

View File

@@ -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<u32, Box<EvalAltResult>> {
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<OurDB>) {
// 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>("Priority");
engine.register_fn("to_string", |p: &mut Priority| ToString::to_string(p));
engine.register_type_with_name::<Status>("Status");
engine.register_fn("to_string", |s: &mut Status| ToString::to_string(s));
engine.register_type_with_name::<ItemType>("ItemType");
engine.register_fn("to_string", |it: &mut ItemType| ToString::to_string(it));
// --- Project Registration ---
engine.register_type_with_name::<Project>("Project");
// Constructor for Project
// Zero-argument constructor
engine.register_fn("new_project", || -> Result<Project, Box<EvalAltResult>> {
// 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<Project, Box<EvalAltResult>> {
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<i64, Box<EvalAltResult>> { Ok(p.get_id() as i64) });
engine.register_get("name", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.name.clone()) });
engine.register_get("description", |p: &mut Project| -> Result<String, Box<EvalAltResult>> { Ok(p.description.clone()) });
engine.register_get("owner_id", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.owner_id as i64) });
engine.register_get("member_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.member_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("board_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.board_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("sprint_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.sprint_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("epic_ids", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.epic_ids.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("tags", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.tags.iter().map(|tag| rhai::Dynamic::from(tag.clone())).collect())
});
engine.register_get("created_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.created_at) });
engine.register_get("modified_at", |p: &mut Project| -> Result<i64, Box<EvalAltResult>> { Ok(p.base_data.modified_at) });
engine.register_get("comments", |p: &mut Project| -> Result<rhai::Array, Box<EvalAltResult>> {
Ok(p.base_data.comments.iter().map(|&id| rhai::Dynamic::from(id as i64)).collect())
});
engine.register_get("status", |p: &mut Project| -> Result<Status, Box<EvalAltResult>> { Ok(p.status.clone()) });
engine.register_get("priority", |p: &mut Project| -> Result<Priority, Box<EvalAltResult>> { Ok(p.priority.clone()) });
engine.register_get("item_type", |p: &mut Project| -> Result<ItemType, Box<EvalAltResult>> { 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<Project, Box<EvalAltResult>> { Ok(p.name(name)) });
engine.register_fn("description", |p: Project, description: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.description(description)) });
engine.register_fn("owner_id", |p: Project, owner_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.owner_id(id_from_i64(owner_id_i64)?)) });
engine.register_fn("add_member_id", |p: Project, member_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_member_id(id_from_i64(member_id_i64)?)) });
engine.register_fn("member_ids", |p: Project, member_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = member_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().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::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.member_ids(ids))
});
engine.register_fn("add_board_id", |p: Project, board_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_board_id(id_from_i64(board_id_i64)?)) });
engine.register_fn("board_ids", |p: Project, board_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = board_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().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::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.board_ids(ids))
});
engine.register_fn("add_sprint_id", |p: Project, sprint_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_sprint_id(id_from_i64(sprint_id_i64)?)) });
engine.register_fn("sprint_ids", |p: Project, sprint_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = sprint_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().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::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.sprint_ids(ids))
});
engine.register_fn("add_epic_id", |p: Project, epic_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_epic_id(id_from_i64(epic_id_i64)?)) });
engine.register_fn("epic_ids", |p: Project, epic_ids_i64: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
let ids = epic_ids_i64
.into_iter()
.map(|id_dyn: Dynamic| {
let val_i64 = id_dyn.clone().try_cast::<i64>().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::<Result<Vec<u32>, Box<EvalAltResult>>>()?;
Ok(p.epic_ids(ids))
});
engine.register_fn("add_tag", |p: Project, tag: String| -> Result<Project, Box<EvalAltResult>> { Ok(p.add_tag(tag)) });
engine.register_fn("tags", |p: Project, tags_dyn: rhai::Array| -> Result<Project, Box<EvalAltResult>> {
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::<Result<Vec<String>, Box<EvalAltResult>>>()?;
Ok(p.tags(tags_vec))
});
engine.register_fn("status", |p: Project, status: Status| -> Result<Project, Box<EvalAltResult>> { Ok(p.status(status)) });
engine.register_fn("priority", |p: Project, priority: Priority| -> Result<Project, Box<EvalAltResult>> { Ok(p.priority(priority)) });
engine.register_fn("item_type", |p: Project, item_type: ItemType| -> Result<Project, Box<EvalAltResult>> { Ok(p.item_type(item_type)) });
// Base ModelData builders
engine.register_fn("add_base_comment", |p: Project, comment_id_i64: i64| -> Result<Project, Box<EvalAltResult>> { 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<EvalAltResult>> {
let collection = db_clone_set.collection::<Project>().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<Dynamic, Box<EvalAltResult>> {
let id = id_from_i64(id_i64)?;
let collection = db_clone_get.collection::<Project>().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::<Label>("Label")
// .register_fn("new_label", Label::new) // Simplified
// // ... other Label methods and getters ...
// ;
// TODO: Add DB interaction functions like set_project, get_project_by_id etc.
}

View File

@@ -0,0 +1,81 @@
// heromodels/src/models/projects/sprint.rs
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::sprint_enums::SprintStatus; // Import our new enum
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
#[model]
pub struct Sprint {
pub base_data: BaseModelData,
pub name: String,
pub description: Option<String>,
pub status: SprintStatus,
pub goal: Option<String>, // Sprint goal
pub project_id: Option<u32>, // Link to a general project/board
pub start_date: Option<DateTime<Utc>>,
pub end_date: Option<DateTime<Utc>>, // Changed from due_date for sprints
// Explicitly list task IDs belonging to this sprint
pub task_ids: Vec<u32>,
}
impl Sprint {
pub fn new(
name: String,
description: Option<String>,
status: SprintStatus,
goal: Option<String>,
project_id: Option<u32>,
start_date: Option<DateTime<Utc>>,
end_date: Option<DateTime<Utc>>,
) -> Self {
Self {
base_data: BaseModelData::new(),
name,
description,
status,
goal,
project_id,
start_date,
end_date,
task_ids: Vec::new(), // Initialize as empty
}
}
// Method to add a task to this sprint
pub fn add_task_id(mut self, task_id: u32) -> Self {
if !self.task_ids.contains(&task_id) {
self.task_ids.push(task_id);
}
self
}
// Method to remove a task from this sprint
pub fn remove_task_id(mut self, task_id: u32) -> Self {
self.task_ids.retain(|&id| id != task_id);
self
}
}
impl Default for Sprint {
fn default() -> Self {
Self {
base_data: BaseModelData::new(),
name: String::new(),
description: None,
status: SprintStatus::default(),
goal: None,
project_id: None,
start_date: None,
end_date: None,
task_ids: Vec::new(),
}
}
}

View File

@@ -0,0 +1,16 @@
// heromodels/src/models/projects/sprint_enums.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SprintStatus {
Planning,
Active,
Completed,
Paused,
}
impl Default for SprintStatus {
fn default() -> Self {
SprintStatus::Planning
}
}

View File

@@ -0,0 +1,93 @@
// heromodels/src/models/projects/task.rs
use chrono::{DateTime, Utc};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize}; // Assuming rhai might be used
use super::task_enums::{TaskPriority, TaskStatus}; // Import our new enums
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
#[model] // This will provide id, created_at, updated_at via base_data
pub struct Task {
pub base_data: BaseModelData,
pub title: String,
pub description: Option<String>,
pub status: TaskStatus,
pub priority: TaskPriority,
pub assignee_id: Option<u32>, // User ID
pub reporter_id: Option<u32>, // User ID
pub parent_task_id: Option<u32>, // For subtasks
pub epic_id: Option<u32>,
pub sprint_id: Option<u32>,
pub project_id: Option<u32>, // Link to a general project/board if applicable
pub due_date: Option<DateTime<Utc>>,
pub estimated_time_hours: Option<f32>,
pub logged_time_hours: Option<f32>,
pub tags: Vec<String>,
}
impl Task {
#[allow(clippy::too_many_arguments)]
pub fn new(
title: String,
description: Option<String>,
status: TaskStatus,
priority: TaskPriority,
assignee_id: Option<u32>,
reporter_id: Option<u32>,
parent_task_id: Option<u32>,
epic_id: Option<u32>,
sprint_id: Option<u32>,
project_id: Option<u32>,
due_date: Option<DateTime<Utc>>,
estimated_time_hours: Option<f32>,
logged_time_hours: Option<f32>,
tags: Vec<String>,
) -> Self {
Self {
base_data: BaseModelData::new(),
title,
description,
status,
priority,
assignee_id,
reporter_id,
parent_task_id,
epic_id,
sprint_id,
project_id,
due_date,
estimated_time_hours,
logged_time_hours,
tags,
}
}
}
// Add Default implementation
impl Default for Task {
fn default() -> Self {
Self {
base_data: BaseModelData::new(),
title: String::new(),
description: None,
status: TaskStatus::default(),
priority: TaskPriority::default(),
assignee_id: None,
reporter_id: None,
parent_task_id: None,
epic_id: None,
sprint_id: None,
project_id: None,
due_date: None,
estimated_time_hours: None,
logged_time_hours: None,
tags: Vec::new(),
}
}
}

View File

@@ -0,0 +1,32 @@
// heromodels/src/models/projects/task_enums.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TaskStatus {
ToDo,
InProgress,
InReview,
Done,
Blocked,
Backlog,
}
impl Default for TaskStatus {
fn default() -> Self {
TaskStatus::ToDo
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TaskPriority {
Low,
Medium,
High,
Urgent,
}
impl Default for TaskPriority {
fn default() -> Self {
TaskPriority::Medium
}
}

View File

@@ -2,4 +2,4 @@
pub mod user;
// Re-export User for convenience
pub use user::User;
pub use user::User;

View File

@@ -3,7 +3,7 @@ use heromodels_derive::model;
use serde::{Deserialize, Serialize};
/// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[model]
pub struct User {
/// Base model data