merge branches and cleanup db

This commit is contained in:
timurgordon
2025-06-27 12:11:04 +03:00
parent 5563d7e27e
commit 1f9ec01934
177 changed files with 1202 additions and 174 deletions

View File

@@ -323,16 +323,6 @@ where
assigned_id
};
// Always create a primary key index entry for this model type
// This ensures get_all() can find all objects of this type, even if they have no explicit indexed fields
let primary_index_key = format!("{}::primary", M::db_prefix());
let mut primary_ids: HashSet<u32> =
Self::get_tst_value(&mut index_db, &primary_index_key)?
.unwrap_or_else(HashSet::new);
primary_ids.insert(assigned_id);
let raw_primary_ids = bincode::serde::encode_to_vec(&primary_ids, BINCODE_CONFIG)?;
index_db.set(&primary_index_key, raw_primary_ids)?;
// Now add the new indices
for index_key in indices_to_add {
let key = Self::index_key(M::db_prefix(), index_key.name, &index_key.value);
@@ -430,22 +420,6 @@ where
}
}
// Also remove from the primary key index
let primary_index_key = format!("{}::primary", M::db_prefix());
if let Some(mut primary_ids) =
Self::get_tst_value::<HashSet<u32>>(&mut index_db, &primary_index_key)?
{
primary_ids.remove(&id);
if primary_ids.is_empty() {
// This was the last object of this type, remove the primary index entirely
index_db.delete(&primary_index_key)?;
} else {
// There are still other objects of this type, write back updated set
let raw_primary_ids = bincode::serde::encode_to_vec(&primary_ids, BINCODE_CONFIG)?;
index_db.set(&primary_index_key, raw_primary_ids)?;
}
}
// Finally delete the object itself
Ok(data_db.delete(id)?)
}
@@ -476,7 +450,18 @@ where
}
}
}
};
}
Err(tst::Error::PrefixNotFound(_)) => {
// No index entries found for this prefix, meaning no objects of this type exist.
// Note: tst::getall might return Ok(vec![]) in this case instead of PrefixNotFound.
// Depending on tst implementation, this arm might be redundant if getall returns empty vec.
return Ok(Vec::new());
}
Err(e) => {
// Other TST errors.
return Err(super::Error::DB(e));
}
}
let mut results: Vec<M> = Vec::with_capacity(all_object_ids.len());
for obj_id in all_object_ids {

View File

@@ -0,0 +1,65 @@
# Access Control Model
The `access` model provides a system for managing permissions, defining which users or groups can access specific resources (objects) within the application.
## `Access` Struct
The core of this module is the `Access` struct, which acts as an Access Control Entry (ACE). It creates a link between a resource and an entity being granted permission.
### Fields
- `base_data`: Standard `BaseModelData` for a unique ID and timestamps.
- `object_type`: A `String` identifying the type of the resource (e.g., `"Project"`, `"Document"`).
- `object_id`: The `u32` unique ID of the resource instance.
- `circle_pk`: The public key (`String`) of the user or entity being granted access.
- `contact_id`: The ID of a `Contact` being granted access.
- `group_id`: The ID of a `Group` being granted access.
- `expires_at`: An optional `u64` timestamp for when the access grant expires.
All key fields are indexed for efficient lookups.
## Core Functions
The module provides functions to check permissions based on the created `Access` records.
- `can_access_resource(db, public_key, object_id, object_type) -> bool`:
This is the primary function for permission checking. It determines if a user, identified by their `public_key`, can access a given object. The current logic is as follows:
1. It first checks if the `public_key` belongs to a member of a globally defined `Circle`. If so, access is granted (this acts as a super-admin or owner role).
2. If the user is not a global member, it queries for all `Access` records associated with the `object_id`.
3. It returns `true` if it finds any `Access` record where the `circle_pk` matches the user's `public_key`.
- `is_circle_member(db, public_key) -> bool`:
A helper function that checks if a user is part of the global `Circle`, effectively checking for super-admin privileges.
## Usage Example
Here is a conceptual walkthrough of how to grant and check access:
1. **A resource is created**, for example, a `Project` with ID `789`.
2. **To grant access** to a user with the public key `"pubkey_of_user_b"`, you create an `Access` record:
```rust
use heromodels::models::access::Access;
let access_grant = Access::new()
.object_type("Project".to_string())
.object_id(789)
.circle_pk("pubkey_of_user_b".to_string());
// This record would then be saved to the database.
```
3. **To check access**, when `user_b` attempts to view the project, the application would call `can_access_resource`:
```rust
// let can_access = can_access_resource(
// db_connection,
// "pubkey_of_user_b",
// 789,
// "Project"
// );
// assert!(can_access);
```
This system allows for flexible, object-by-object permission management.

View File

@@ -1,8 +1,8 @@
use std::sync::Arc;
use crate::db::{Collection, Db, hero::OurDB};
use crate::models::Circle;
use crate::db::{hero::OurDB, Collection, Db};
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use std::sync::Arc;
// Temporarily removed to fix compilation issues
// use rhai_autobind_macros::rhai_model_export;
use rhai::{CustomType, TypeBuilder};
@@ -71,17 +71,16 @@ impl Access {
}
}
/// 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(
@@ -94,7 +93,8 @@ pub fn can_access_resource(
.collection::<Circle>()
.expect("Failed to get Circle collection")
.get_all()
.unwrap()[0].clone();
.unwrap()[0]
.clone();
// Circle members can access everything
if circle.members.contains(&public_key.to_string()) {
@@ -121,18 +121,18 @@ pub fn can_access_resource(
println!("Access records: {:#?}", access_records);
// if circle_pk is in access records true
return access_records.iter().any(|record| record.circle_pk == public_key)
return access_records
.iter()
.any(|record| record.circle_pk == public_key);
}
pub fn is_circle_member(
db: Arc<OurDB>,
public_key: &str,
) -> bool {
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();
.unwrap()[0]
.clone();
// Circle members can access everything
if circle.members.contains(&public_key.to_string()) {

View File

@@ -0,0 +1,58 @@
# Business Models (`biz`)
The `biz` module provides a suite of models for handling core business operations, including company management, product catalogs, sales, payments, and shareholder records.
## Core Models
### `Company`
The `Company` struct is the central model, representing a business entity.
- **Key Fields**: `name`, `registration_number`, `incorporation_date`, `address`, `business_type`, and `status`.
- **Enums**:
- `CompanyStatus`: Tracks the company's state (`PendingPayment`, `Active`, `Suspended`, `Inactive`).
- `BusinessType`: Categorizes the company (e.g., `Coop`, `Single`, `Global`).
- **Functionality**: Provides a foundation for linking other business models like products, sales, and shareholders.
### `Product`
The `Product` model defines goods or services offered by a company.
- **Key Fields**: `name`, `description`, `price`, `category`, `status`, and `components`.
- **Nested Struct**: `ProductComponent` allows for defining complex products with sub-parts.
- **Enums**:
- `ProductType`: Differentiates between a `Product` and a `Service`.
- `ProductStatus`: Indicates if a product is `Available` or `Unavailable`.
### `Sale`
The `Sale` struct records a transaction, linking a buyer to products.
- **Key Fields**: `company_id`, `buyer_id`, `total_amount`, `sale_date`, and `status`.
- **Nested Struct**: `SaleItem` captures a snapshot of each product at the time of sale, including `product_id`, `quantity`, and `unit_price`.
- **Enum**: `SaleStatus` tracks the state of the sale (`Pending`, `Completed`, `Cancelled`).
### `Payment`
The `Payment` model handles financial transactions, often linked to sales or subscriptions.
- **Key Fields**: `payment_intent_id` (e.g., for Stripe), `company_id`, `total_amount`, `currency`, and `status`.
- **Functionality**: Includes methods to manage the payment lifecycle (`process_payment`, `complete_payment`, `fail_payment`, `refund_payment`).
- **Enum**: `PaymentStatus` provides a detailed state of the payment (`Pending`, `Processing`, `Completed`, `Failed`, `Refunded`).
### `Shareholder`
The `Shareholder` model tracks ownership of a company.
- **Key Fields**: `company_id`, `user_id`, `name`, `shares`, and `percentage`.
- **Enum**: `ShareholderType` distinguishes between `Individual` and `Corporate` shareholders.
## Workflow Example
1. A `Company` is created.
2. The company defines several `Product` models representing its offerings.
3. A customer (buyer) initiates a purchase, which creates a `Sale` record containing multiple `SaleItem`s.
4. A `Payment` record is generated to process the transaction for the `Sale`'s total amount.
5. As the company grows, `Shareholder` records are created to track equity distribution.
All models use the builder pattern for easy and readable instance creation.

View File

@@ -17,10 +17,3 @@ pub use shareholder::{Shareholder, ShareholderType};
pub mod sale;
pub use sale::{Sale, SaleItem, SaleStatus};
// pub use user::{User}; // Assuming a simple User model for now
#[cfg(feature = "rhai")]
pub mod rhai;
#[cfg(feature = "rhai")]
pub use rhai::register_biz_rhai_module;

View File

@@ -1,6 +1,6 @@
use heromodels_core::BaseModelData;
use rhai::{CustomType, TypeBuilder};
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
// ProductType represents the type of a product

View File

@@ -0,0 +1,70 @@
# Calendar Model
The `calendar` model provides the data structures for managing calendars, events, and attendees.
## Core Components
### 1. `Calendar`
Represents a calendar, which is a collection of events. Each calendar has a name, an optional description, and a list of event IDs.
- `name`: The name of the calendar (e.g., "Work Calendar", "Personal Calendar").
- `description`: An optional text description for the calendar.
- `events`: A `Vec<i64>` containing the IDs of the `Event` models associated with this calendar.
- `owner_id`: The ID of the user who owns the calendar.
- `is_public`: A boolean indicating if the calendar is visible to others.
### 2. `Event`
Represents a single event within a calendar. It contains all the details for a specific appointment or occasion.
- `title`: The title of the event.
- `description`: An optional detailed description.
- `start_time` & `end_time`: Unix timestamps for when the event begins and ends.
- `attendees`: A `Vec<Attendee>` listing who is invited to the event and their status.
- `location`: The physical or virtual location of the event.
- `status`: The current state of the event, defined by the `EventStatus` enum.
### 3. `Attendee`
Represents a person invited to an `Event`.
- `contact_id`: The ID of the user or contact who is the attendee.
- `status`: The attendee's response to the invitation, defined by the `AttendanceStatus` enum.
## Enums
### `EventStatus`
Defines the lifecycle of an `Event`:
- `Draft`: The event is being planned and is not yet visible to attendees.
- `Published`: The event is confirmed and visible.
- `Cancelled`: The event has been cancelled.
### `AttendanceStatus`
Defines the status of an `Attendee` for an event:
- `Accepted`: The attendee has confirmed they will attend.
- `Declined`: The attendee has declined the invitation.
- `Tentative`: The attendee is unsure if they will attend.
- `NoResponse`: The attendee has not yet responded.
## Usage
The `Calendar` model uses a builder pattern for creating and modifying instances. You can create a new `Calendar` or `Event` and chain methods to set its properties.
```rust
use heromodels::models::calendar::{Calendar, Event, Attendee, AttendanceStatus};
// Create a new event
let event = Event::new()
.title("Team Meeting")
.description("Weekly sync-up.")
.reschedule(1672531200, 1672534800) // Set start and end times
.add_attendee(Attendee::new(101).status(AttendanceStatus::Accepted));
// Create a new calendar and add the event to it (assuming event has been saved and has an ID)
let calendar = Calendar::new(None, "Work Events")
.owner_id(1)
.add_event(event.base_data.id); // Add event by ID
```

View File

@@ -1,12 +1,12 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
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, Default)]
pub enum AttendanceStatus {
#[default]
Accepted = 0,
Declined = 1,
Tentative = 2,
@@ -14,8 +14,9 @@ pub enum AttendanceStatus {
}
/// Represents the status of an event
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum EventStatus {
#[default]
Draft = 0,
Published = 1,
Cancelled = 2,
@@ -87,7 +88,7 @@ impl Attendee {
/// Represents an event in a calendar
#[model]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType, Default)]
pub struct Event {
/// Base model data
pub base_data: BaseModelData,
@@ -224,10 +225,14 @@ impl Event {
}
/// Adds an attendee ID to the event
pub fn add_attendee(mut self, attendee_id: u32) -> Self {
pub fn add_attendee(mut self, attendee: Attendee) -> Self {
// Prevent duplicate attendees by ID
if !self.attendees.iter().any(|&a_id| a_id == attendee_id) {
self.attendees.push(attendee_id);
if !self
.attendees
.iter()
.any(|a| a.contact_id == attendee.contact_id)
{
self.attendees.push(attendee);
}
self
}

View File

@@ -2,4 +2,4 @@
pub mod calendar;
// 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 self::calendar::{AttendanceStatus, Attendee, Calendar, Event, EventStatus};

View File

@@ -0,0 +1,59 @@
# Circle Model
The `circle` model defines a `Circle` struct, which represents a group or community of members. It includes metadata for customization and relationship mapping between different circles.
## `Circle` Struct
The `Circle` struct is the primary model in this module.
### Fields
- `base_data`: Standard `BaseModelData` for a unique ID and timestamps.
- `title`: The name of the circle.
- `ws_url`: A WebSocket URL associated with the circle.
- `description`: An optional, longer description of the circle's purpose.
- `logo`: An optional URL or symbol for the circle's logo.
- `members`: A `Vec<String>` containing the public keys of the members of the circle.
- `circles`: A `Vec<String>` containing the titles or IDs of other related circles, allowing for a network of circles.
- `theme`: A `ThemeData` struct for customizing the visual appearance of the circle.
### `ThemeData` Struct
This struct holds visual customization options for a circle:
- `primary_color`: The primary color for the circle's theme.
- `background_color`: The background color.
- `background_pattern`: A pattern for the background.
- `logo_symbol`: A symbol to use for the logo.
- `logo_url`: A URL for the logo image.
- `nav_dashboard_visible`: A boolean to control the visibility of the dashboard navigation.
- `nav_timeline_visible`: A boolean to control the visibility of the timeline navigation.
## Usage Example
Here's how you might create a new circle and add members to it:
```rust
use heromodels::models::circle::{Circle, ThemeData};
let mut my_circle = Circle::new()
.title("My Awesome Circle".to_string())
.description("A circle for awesome people.".to_string())
.ws_url("wss://example.com/my_circle".to_string());
my_circle = my_circle.add_member("pubkey_of_member_1".to_string());
my_circle = my_circle.add_member("pubkey_of_member_2".to_string());
let theme = ThemeData {
primary_color: "#FF5733".to_string(),
background_color: "#FFFFFF".to_string(),
// ... other theme fields
..Default::default()
};
my_circle = my_circle.theme(theme);
// The circle is now ready to be saved to the database.
```
The `Circle` model is useful for creating social groups, teams, or any other collection of users who need to be grouped together.

View File

@@ -0,0 +1,66 @@
# Contact and Group Models
The `contact` module provides models for managing a personal or organizational address book. It includes the `Contact` struct for individual entries and the `Group` struct for organizing contacts.
## `Contact` Struct
The `Contact` model stores detailed information about a single contact.
### Fields
- `base_data`: Standard `BaseModelData` for a unique ID and timestamps.
- `name`: The contact's name (indexed for easy searching).
- `description`: An optional, longer description.
- `address`: The physical or mailing address.
- `phone`: The contact's phone number.
- `email`: The contact's email address.
- `notes`: Optional field for any additional notes.
- `circle`: A `String` to associate the contact with a specific `Circle` or social group.
## `Group` Struct
The `Group` model allows for the creation of contact lists, making it easy to manage related contacts together.
### Fields
- `base_data`: Standard `BaseModelData`.
- `name`: The name of the group (e.g., "Family", "Work Colleagues").
- `description`: An optional description of the group.
- `contacts`: A `Vec<u32>` containing the unique IDs of the `Contact` models that belong to this group.
## Usage Example
Here is a conceptual example of how to create contacts and organize them into a group:
1. **Create individual contacts**:
```rust
use heromodels::models::contact::Contact;
let contact1 = Contact::new()
.name("Alice")
.email("alice@example.com");
let contact2 = Contact::new()
.name("Bob")
.email("bob@example.com");
// Save contact1 and contact2 to the database and get their IDs (e.g., 1 and 2).
```
2. **Create a group and add the contacts**:
```rust
use heromodels::models::contact::Group;
let mut friends_group = Group::new()
.name("Friends")
.description("My closest friends.");
friends_group = friends_group.add_contact(1); // Add Alice's ID
friends_group = friends_group.add_contact(2); // Add Bob's ID
// Save the group to the database.
```
Both models use the builder pattern, providing a fluent and readable way to construct instances.

View File

@@ -0,0 +1,50 @@
# Core Model
The `core` model contains fundamental, reusable components that are shared across various other models in the `heromodels` library. The primary component in this module is the `Comment` struct.
## `Comment` Struct
The `Comment` struct is designed to provide a generic commenting functionality that can be associated with any other model. It supports threaded conversations.
### Fields
- `base_data`: The standard `BaseModelData`, which provides a unique ID and timestamps for each comment.
- `user_id`: The ID of the user who posted the comment. This field is indexed.
- `content`: The text content of the comment.
- `parent_comment_id`: An `Option<u32>` that holds the ID of the parent comment. If this is `None`, the comment is a top-level comment. If it contains an ID, it is a reply to another comment.
## Usage
The `Comment` model uses a builder pattern for easy instantiation. You can create top-level comments or replies.
### Creating a Top-Level Comment
```rust
use heromodels::models::core::Comment;
let top_level_comment = Comment::new()
.user_id(101) // ID of the user posting
.content("This is the first comment on the topic.");
assert!(top_level_comment.parent_comment_id.is_none());
```
### Creating a Threaded Reply
To create a reply, you set the `parent_comment_id` to the ID of the comment you are replying to.
```rust
use heromodels::models::core::Comment;
// Assume the top_level_comment from the previous example was saved and has ID 1
let top_level_comment_id = 1;
let reply_comment = Comment::new()
.user_id(102)
.content("This is a reply to the first comment.")
.parent_comment_id(Some(top_level_comment_id));
assert_eq!(reply_comment.parent_comment_id, Some(1));
```
This `Comment` model can be linked from other models (like `User`, `Article`, `Project`, etc.) by storing a `Vec<u32>` of comment IDs within them, as demonstrated by the `add_comment` method in the `userexample` model.

View File

@@ -0,0 +1,89 @@
# Finance Model
The `finance` model provides a suite of data structures for managing financial accounts, digital assets, and a marketplace for trading them.
## Core Components
### 1. `Account`
Represents a financial account, typically owned by a user. It acts as a container for various assets.
- `name`: An internal name for the account (e.g., "My Savings").
- `user_id`: The ID of the user who owns the account.
- `ledger`: The blockchain or financial system where the account exists (e.g., "Ethereum").
- `address`: The account's public address.
- `assets`: A `Vec<u32>` of asset IDs associated with this account.
### 2. `Asset`
Represents a digital or tokenized asset.
- `name`: The name of the asset (e.g., "Bitcoin", "MyToken").
- `amount`: The quantity of the asset held.
- `asset_type`: The type of the asset, defined by the `AssetType` enum.
- `address`: The contract address of the token (if applicable).
### 3. `Marketplace`
The marketplace components facilitate the trading of assets.
- **`Listing`**: Represents an asset listed for sale. It can be a fixed-price sale, an auction, or an exchange.
- `title`: The title of the listing.
- `asset_id`: The ID of the asset being sold.
- `seller_id`: The ID of the user selling the asset.
- `price`: The asking price or starting bid.
- `listing_type`: The type of sale, defined by `ListingType`.
- `status`: The current state of the listing, defined by `ListingStatus`.
- **`Bid`**: Represents a bid made on an auction-style `Listing`.
- `bidder_id`: The ID of the user placing the bid.
- `amount`: The value of the bid.
- `status`: The current state of the bid, defined by `BidStatus`.
## Enums
### `AssetType`
- `Erc20`, `Erc721`, `Erc1155`: Standard Ethereum token types.
- `Native`: The native currency of a blockchain (e.g., ETH).
### `ListingType`
- `FixedPrice`: The asset is sold for a set price.
- `Auction`: The asset is sold to the highest bidder.
- `Exchange`: The asset is offered in trade for other assets.
### `ListingStatus`
- `Active`, `Sold`, `Cancelled`, `Expired`: Defines the lifecycle of a listing.
### `BidStatus`
- `Active`, `Accepted`, `Rejected`, `Cancelled`: Defines the lifecycle of a bid.
## Usage
The models use a builder pattern for easy instantiation.
```rust
use heromodels::models::finance::{Account, Asset, Listing, ListingType};
// 1. Create a user account
let account = Account::new()
.name("Trading Account")
.user_id(101)
.ledger("Ethereum")
.address("0x123...");
// 2. Create an asset (assuming it's saved and has an ID)
let asset = Asset::new()
.name("Hero Token")
.amount(1000.0);
// In a real scenario, you would save the asset to get an ID.
let asset_id = asset.base_data.id.to_string();
// 3. Create a marketplace listing for the asset
let listing = Listing::new()
.title("1000 Hero Tokens for Sale")
.asset_id(asset_id)
.seller_id(account.user_id.to_string())
.price(0.5)
.currency("USD")
.listing_type(ListingType::FixedPrice);
```

View File

@@ -0,0 +1,60 @@
# Flow Model
The `flow` model provides a framework for creating and managing multi-step workflows, particularly those requiring digital signatures. It is designed to orchestrate a sequence of actions that must be completed in a specific order.
## Core Components
### 1. `Flow`
The top-level container for a workflow.
- `flow_uuid`: A unique identifier for the entire flow, used for external references.
- `name`: A human-readable name for the flow (e.g., "Document Approval Process").
- `status`: The overall status of the flow (e.g., "Pending", "InProgress", "Completed").
- `steps`: A `Vec<FlowStep>` that defines the sequence of steps in the workflow.
### 2. `FlowStep`
Represents a single, distinct step within a `Flow`.
- `step_order`: A `u32` that determines the position of this step in the sequence.
- `description`: An optional text description of what this step entails.
- `status`: The status of this individual step.
### 3. `SignatureRequirement`
Defines a requirement for a digital signature within a `FlowStep`. A single step can have multiple signature requirements.
- `flow_step_id`: A foreign key linking the requirement to its parent `FlowStep`.
- `public_key`: The public key of the entity that is required to sign.
- `message`: The plaintext message that needs to be signed.
- `signature`: The resulting signature, once provided.
- `status`: The status of the signature requirement (e.g., "Pending", "Signed", "Failed").
## Usage
The models use a builder pattern to construct complex flows. You create a `Flow`, add `FlowStep`s to it, and associate `SignatureRequirement`s with each step.
```rust
use heromodels::models::flow::{Flow, FlowStep, SignatureRequirement};
use uuid::Uuid;
// 1. Define a signature requirement
let requirement = SignatureRequirement::new(
0, // ID is managed by the database
1, // Belongs to flow step 1
"0xPublicKey1...",
"I approve this document."
);
// 2. Create a flow step
// In a real application, you would add the signature requirement to the step.
let step1 = FlowStep::new(0, 1) // ID, step_order
.description("Initial review and approval");
// 3. Create the main flow and add the step
let flow = Flow::new(Uuid::new_v4().to_string())
.name("Contract Signing Flow")
.add_step(step1);
```

View File

@@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
#[model]
pub struct Flow {
/// Base model data (id, created_at, updated_at).
#[rhai_type(skip)]
#[rhai_type(skip)]
pub base_data: BaseModelData,
/// A unique UUID for the flow, for external reference.

View File

@@ -9,7 +9,7 @@ use std::default::Default;
#[model]
pub struct FlowStep {
/// Base model data.
#[rhai_type(skip)]
#[rhai_type(skip)]
pub base_data: BaseModelData,
/// Optional description for the step.

View File

@@ -6,4 +6,4 @@ pub mod signature_requirement;
// Re-export key types for convenience
pub use flow::Flow;
pub use flow_step::FlowStep;
pub use signature_requirement::SignatureRequirement;
pub use signature_requirement::SignatureRequirement;

View File

@@ -9,7 +9,7 @@ use std::default::Default;
#[model]
pub struct SignatureRequirement {
/// Base model data.
#[rhai_type(skip)]
#[rhai_type(skip)]
pub base_data: BaseModelData,
/// Foreign key to the FlowStep this requirement belongs to.

View File

@@ -0,0 +1,64 @@
# Corporate Governance (`gov`) Model
The `gov` module provides a comprehensive suite of models for managing corporate governance structures and processes. It allows for the detailed representation of companies, their ownership, governing bodies, and decision-making workflows.
## Core Models
### `Company`
The `Company` struct is the central entity in this module. It is similar to the `Company` model in the `biz` module but is specifically tailored for governance, with direct implementation of the `Model` trait for database interaction.
- **Key Fields**: `name`, `registration_number`, `incorporation_date`, `status`, `business_type`.
- **Enums**: `CompanyStatus`, `BusinessType`.
### `Shareholder`
The `Shareholder` model is used to track ownership of a company.
- **Key Fields**: `company_id`, `name`, `shares`, `percentage`, `shareholder_type`.
- **Enums**: `ShareholderType` (e.g., Individual, Corporate).
### `Committee`
Companies can have `Committee`s to oversee specific functions (e.g., Audit Committee, Compensation Committee). Each committee is composed of `CommitteeMember`s.
- **`Committee` Fields**: `company_id`, `name`, `description`, `members`.
- **`CommitteeMember` Fields**: `user_id`, `name`, `role`.
- **Enums**: `CommitteeRole` (e.g., Chair, Member, Advisor).
### `Meeting`
The `Meeting` model is used to schedule and document official meetings for a company or its committees.
- **Key Fields**: `company_id`, `title`, `meeting_type`, `status`, `start_time`, `end_time`, `agenda`, `minutes`, `attendees`.
- **`Attendee` Fields**: `user_id`, `name`, `status`.
- **Enums**: `MeetingStatus`, `MeetingType`, `AttendanceStatus`.
### `Resolution`
A `Resolution` represents a formal proposal or decision that requires a vote.
- **Key Fields**: `company_id`, `title`, `description`, `resolution_type`, `status`, `proposed_date`, `effective_date`.
- **Enums**: `ResolutionStatus`, `ResolutionType` (e.g., Ordinary, Special).
### `Vote` and `Ballot`
The `Vote` model facilitates the voting process for a specific `Resolution`. Each `Vote` consists of multiple `Ballot`s cast by voters.
- **`Vote` Fields**: `company_id`, `resolution_id`, `title`, `status`, `start_date`, `end_date`, `ballots`.
- **`Ballot` Fields**: `user_id`, `option`, `weight`, `cast_at`.
- **Enums**: `VoteStatus`, `VoteOption` (Yes, No, Abstain).
## Workflow Example
A typical governance workflow might look like this:
1. A `Company` is established with several `Shareholder`s.
2. A `Committee` (e.g., the Board of Directors) is formed by adding `CommitteeMember`s.
3. The committee proposes a `Resolution` to approve the annual budget.
4. A `Vote` is created for this resolution, with a defined start and end date.
5. A `Meeting` is scheduled to discuss the resolution.
6. During the voting period, shareholders or committee members cast their `Ballot`s.
7. Once the `Vote` is closed, the results are tallied, and the `Resolution` status is updated to `Approved` or `Rejected`.
This module provides the foundational data structures for building robust corporate governance applications.

View File

@@ -0,0 +1,79 @@
# Governance Model
The `governance` model provides a robust framework for managing decentralized governance processes, including proposals, voting, and activity tracking.
## Core Components
### 1. `Proposal`
The central element of the governance model. A `Proposal` represents a formal suggestion submitted to the community for a vote.
- `title` & `description`: The substance of the proposal.
- `creator_id`: The ID of the user who submitted the proposal.
- `status`: The current state of the proposal (e.g., `Draft`, `Active`, `Approved`), defined by the `ProposalStatus` enum.
- `options`: A `Vec<VoteOption>` defining the choices voters can select (e.g., "For", "Against", "Abstain").
- `vote_start_date` & `vote_end_date`: Timestamps that define the voting period.
### 2. `Ballot`
Represents a single vote cast by a user on a specific `Proposal`.
- `user_id`: The ID of the voter.
- `vote_option_id`: The specific `VoteOption` the user selected.
- `shares_count`: The voting power or weight of the vote.
- `comment`: An optional comment from the voter.
### 3. `GovernanceActivity`
A detailed record of every significant event that occurs within the governance system. This is crucial for transparency and auditing.
- `activity_type`: The type of event that occurred (e.g., `ProposalCreated`, `VoteCast`), defined by the `ActivityType` enum.
- `actor_id` & `actor_name`: Who performed the action.
- `target_id` & `target_type`: The object the action was performed on (e.g., a `Proposal`).
- `title` & `description`: A summary of the activity.
### 4. `AttachedFile`
A simple struct to link external documents or files to a proposal, such as technical specifications or legal drafts.
## Enums
The model includes several enums to manage the state of proposals, voting, and activities:
- `ProposalStatus`: Tracks the lifecycle of a proposal (`Draft`, `Active`, `Approved`, `Rejected`).
- `VoteEventStatus`: Tracks the status of the voting period (`Upcoming`, `Open`, `Closed`).
- `ActivityType`: Categorizes different governance actions.
- `ActivityStatus`: Tracks the status of a recorded activity (`Pending`, `Completed`, `Failed`).
## Usage
```rust
use heromodels::models::governance::{Proposal, Ballot, VoteOption, ProposalStatus};
use chrono::Utc;
// 1. Create a new proposal
let mut proposal = Proposal::new(
None, // ID is managed by the database
"user-123".to_string(),
"Alice".to_string(),
"Adopt New Logo".to_string(),
"Proposal to update the community logo.".to_string(),
ProposalStatus::Draft,
vec![],
None
);
// 2. Add voting options
proposal = proposal.add_option(1, "Approve New Logo", None);
proposal = proposal.add_option(2, "Reject New Logo", None);
// 3. An eligible user casts a vote
// This would typically be done by finding the proposal and then calling cast_vote.
let proposal_after_vote = proposal.cast_vote(
None, // Ballot ID
456, // Voter's user_id
1, // Voting for option 1
100 // With 100 shares/votes
);
```

View File

@@ -3,7 +3,6 @@
use chrono::{DateTime, Utc};
use heromodels_derive::model;
use rhai::{CustomType, TypeBuilder};
use rhai_autobind_macros::rhai_model_export;
use serde::{Deserialize, Serialize};
// use std::collections::HashMap;
@@ -47,7 +46,6 @@ impl Default for ActivityStatus {
/// GovernanceActivity represents a single activity or event in the governance system
/// This model tracks all significant actions and changes for audit and transparency purposes
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[rhai_model_export(db_type = "std::sync::Arc<crate::db::hero::OurDB>")]
#[model]
pub struct GovernanceActivity {
pub base_data: BaseModelData,

View File

@@ -1,7 +1,9 @@
// heromodels/src/models/governance/mod.rs
// This module will contain the Proposal model and related types.
pub mod activity;
pub mod attached_file;
pub mod proposal;
pub use self::activity::{ActivityStatus, ActivityType, GovernanceActivity};
pub use self::attached_file::AttachedFile;
pub use self::proposal::{Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption};

View File

@@ -343,7 +343,6 @@ impl ToString for ActivityType {
/// Represents a governance activity in the system
#[derive(Debug, Clone, Serialize, Deserialize, CustomType)]
#[rhai_model_export(db_type = "std::sync::Arc<crate::db::hero::OurDB>")]
#[model] // Has base.Base in V spec
pub struct Activity {
/// Base model data

View File

@@ -0,0 +1,79 @@
# Legal Model
The `legal` model provides a structured way to create, manage, and track the lifecycle of digital contracts.
## Core Components
### 1. `Contract`
The main struct representing a legal agreement. It serves as a container for all contract-related data.
- `contract_id`: A unique identifier for the contract.
- `title` & `description`: A summary of the contract's purpose.
- `status`: The current state of the contract (e.g., `Draft`, `Active`), managed by the `ContractStatus` enum.
- `signers`: A `Vec<ContractSigner>` listing all parties required to sign.
- `revisions`: A `Vec<ContractRevision>` that provides a history of the contract's content, allowing for version control.
- `current_version`: The active version of the contract.
### 2. `ContractSigner`
Represents an individual or entity required to sign the contract.
- `id`, `name`, `email`: Identifying information for the signer.
- `status`: The signer's current status (`Pending`, `Signed`, `Rejected`), defined by the `SignerStatus` enum.
- `signed_at`: A timestamp indicating when the signature was provided.
- `signature_data`: Stores the actual signature, for example, as a Base64 encoded image.
### 3. `ContractRevision`
Represents a specific version of the contract's text.
- `version`: A number identifying the revision.
- `content`: The full text of the contract for that version.
- `created_at` & `created_by`: Audit fields to track who created the revision and when.
## Enums
The model uses two key enums to manage state:
- `ContractStatus`: Defines the lifecycle of the entire contract, from `Draft` to `PendingSignatures`, `Active`, `Expired`, or `Cancelled`.
- `SignerStatus`: Tracks the state of each individual signer (`Pending`, `Signed`, `Rejected`).
## Usage
The models are constructed using a builder pattern, allowing for clear and flexible creation of complex contracts.
```rust
use heromodels::models::legal::{Contract, ContractSigner, ContractRevision, ContractStatus};
use std::time::{SystemTime, UNIX_EPOCH};
fn current_timestamp_secs() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
// 1. Create a signer
let signer1 = ContractSigner::new(
"signer-uuid-1".to_string(),
"Alice".to_string(),
"alice@example.com".to_string()
);
// 2. Create a revision
let revision1 = ContractRevision::new(
1,
"This is the first version of the contract...".to_string(),
current_timestamp_secs(),
"creator-uuid-1".to_string()
);
// 3. Create the contract
let contract = Contract::new(1, "contract-uuid-1".to_string())
.title("Service Agreement")
.status(ContractStatus::PendingSignatures)
.add_signer(signer1)
.add_revision(revision1)
.current_version(1);
```

View File

@@ -0,0 +1,49 @@
# Library Model
The `library` model provides a flexible system for managing various types of digital assets and organizing them into collections.
## Library Item Types
The model supports several distinct types of library items, each with its own specific metadata:
- `Image`: Represents an image file with properties like `title`, `url`, `width`, and `height`.
- `Pdf`: Represents a PDF document with a `title`, `url`, and `page_count`.
- `Markdown`: Represents a text document written in Markdown, with `title` and `content`.
- `Book`: A more complex item that consists of a `title`, a `table_of_contents` (a nested structure of `TocEntry` items), and a `Vec<String>` of pages (in Markdown).
- `Slideshow`: Represents a presentation, containing a `title` and a `Vec<Slide>`, where each slide has an `image_url` and optional text.
All items share a common `BaseModelData` field, providing them with a unique ID and timestamps.
## `Collection`
The `Collection` struct is used to group various library items together. It does not store the items directly but rather holds vectors of their unique IDs.
- `title` & `description`: To name and describe the collection.
- `images`: A `Vec<u32>` of `Image` item IDs.
- `pdfs`: A `Vec<u32>` of `Pdf` item IDs.
- `markdowns`: A `Vec<u32>` of `Markdown` item IDs.
- `books`: A `Vec<u32>` of `Book` item IDs.
- `slides`: A `Vec<u32>` of `Slideshow` item IDs.
## Usage
First, you create individual library items. Then, you create a collection and add the IDs of those items to it.
```rust
use heromodels::models::library::{Image, Collection};
// 1. Create a library item (e.g., an Image)
let image1 = Image::new()
.title("Company Logo")
.url("https://example.com/logo.png");
// In a real app, this would be saved to a database, and we'd get an ID.
let image1_id = image1.id(); // Assuming this ID is now 1
// 2. Create a collection
let mut marketing_assets = Collection::new()
.title("Marketing Assets");
// 3. Add the item's ID to the collection
marketing_assets = marketing_assets.add_image(image1_id);
```

View File

@@ -112,10 +112,10 @@ impl Pdf {
Self::default()
}
/// Gets the ID of the image.
pub fn id(&self) -> u32 {
self.base_data.id
}
/// 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 {
@@ -163,10 +163,10 @@ impl Markdown {
Self::default()
}
/// Gets the ID of the image.
pub fn id(&self) -> u32 {
self.base_data.id
}
/// 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 {

View File

@@ -1,3 +1,36 @@
## Object Model
# Log Model
This is a generic object model mostly used for testing purposes.
The `log` model provides a generic `Log` struct for creating audit trails, recording events, or tracking activities within the system. It is designed to be flexible, linking a subject (who performed the action) to an object (what was affected).
## `Log` Struct
The `Log` struct is the core of this module.
### Fields
- `base_data`: Standard `BaseModelData` for a unique ID and timestamps.
- `title`: A short, descriptive title for the log entry (e.g., "User Login", "File Deletion"). This field is indexed.
- `description`: A more detailed description of the event.
- `subject_pk`: The public key of the user or entity that initiated the event. This field is indexed.
- `object_id`: The unique ID of the object or resource that was the target of the event. This field is indexed.
## Usage Example
Here is how you might create a log entry when a user updates a contact:
```rust
use heromodels::models::log::Log;
let user_public_key = "pubkey_of_acting_user";
let updated_contact_id = 123;
let log_entry = Log::new()
.title("Contact Updated".to_string())
.description(format!("User {} updated contact with ID {}.", user_public_key, updated_contact_id))
.subject_pk(user_public_key.to_string())
.object_id(updated_contact_id);
// Save the log_entry to the database.
```
By indexing `title`, `subject_pk`, and `object_id`, the `Log` model allows for efficient querying of activities, such as retrieving all actions performed by a specific user or all events related to a particular object.

View File

@@ -21,15 +21,15 @@ pub use userexample::User;
// pub use productexample::Product; // Temporarily remove
pub use biz::{Payment, PaymentStatus, Sale, SaleItem, SaleStatus};
pub use calendar::{AttendanceStatus, Attendee, Calendar, Event};
pub use circle::{Circle, ThemeData};
pub use finance::marketplace::{Bid, BidStatus, Listing, ListingStatus, ListingType};
pub use finance::{Account, Asset, AssetType};
pub use flow::{Flow, FlowStep, SignatureRequirement};
pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
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};
pub use legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus};
pub use library::collection::Collection;
pub use library::items::{Image, Markdown, Pdf};
pub use projects::{Project, Status};

View File

@@ -1,3 +1,27 @@
## Object Model
# Object Model
This is a generic object model mostly used for testing purposes.
The `object` model provides a simple, generic `Object` struct. It is intended for use in situations where a basic, identifiable data container is needed, such as for testing, prototyping, or representing simple items that do not require a more complex, specific model.
## `Object` Struct
The `Object` struct contains the following fields:
- `base_data`: Standard `BaseModelData` for a unique ID and timestamps.
- `title`: A string for the object's title or name. This field is indexed for efficient lookups.
- `description`: A string for a more detailed description of the object.
## Usage Example
Creating a new `Object` is straightforward using the builder pattern:
```rust
use heromodels::models::object::Object;
let my_object = Object::new()
.title("My Test Object".to_string())
.description("This is an object created for a test scenario.".to_string());
// The object is now ready to be saved to the database.
```
Due to its simplicity, the `Object` model is a versatile tool for various development and testing needs.

View File

@@ -1,9 +1,9 @@
use heromodels_core::BaseModelData;
use heromodels_derive::model;
use rhai::CustomType;
use rhai::TypeBuilder;
use rhailib_derive::RhaiApi;
use serde::{Deserialize, Serialize};
use rhai::TypeBuilder;
/// Represents an event in a contact
#[model]
@@ -13,7 +13,7 @@ pub struct Object {
pub base_data: BaseModelData,
#[index]
pub title: String,
pub description: String
pub description: String,
}
impl Object {
@@ -28,7 +28,7 @@ impl Object {
pub fn id(&self) -> u32 {
self.base_data.id
}
pub fn title(mut self, title: String) -> Self {
self.title = title;
self
@@ -38,4 +38,4 @@ impl Object {
self.description = description;
self
}
}
}

View File

@@ -0,0 +1,76 @@
# Projects Model
The `projects` model provides a comprehensive suite of tools for managing software development projects, based on common agile methodologies.
## Core Components
The model is built around a hierarchy of work items:
- **`Project`**: The highest-level container. A `Project` holds information about its members, and contains lists of IDs for associated epics, sprints, and boards.
- **`Epic`**: Represents a large body of work or a major feature. An `Epic` is broken down into smaller tasks and can be associated with a project. It tracks its own status, start/due dates, and a list of `child_task_ids`.
- **`Sprint`**: A time-boxed iteration (e.g., two weeks) during which a team works to complete a set of tasks. A `Sprint` has a goal, a start and end date, its own status (`Planned`, `Active`, `Completed`), and a list of `task_ids`.
- **`Task`**: The most granular unit of work. A `Task` has a title, description, status, priority, and can be assigned to a user. It can be linked to a parent `Project`, `Epic`, and `Sprint`. Tasks can also be nested using the `parent_task_id` field.
- **`Label`**: A simple struct for creating tags with a name and a color, which can be used to categorize items.
## Enums and Statuses
The model uses several enums to manage the state of work items:
- **`Priority`**: A general enum (`Critical`, `High`, `Medium`, `Low`) used across different models.
- **`Status`**: A general status enum (`Todo`, `InProgress`, `Done`, etc.) for projects.
- **`ItemType`**: Describes the type of work item (`Epic`, `Story`, `Task`, `Bug`).
- **`SprintStatus`**: Specific statuses for sprints (`Planned`, `Active`, `Completed`).
- **`TaskStatus`** and **`TaskPriority`**: Specific enums for the detailed states and priorities of individual tasks.
## Usage
The typical workflow involves creating a `Project`, then populating it with `Epic`s and `Sprint`s. `Task`s are then created and associated with these epics and sprints.
```rust
use heromodels::models::projects::{Project, Epic, Sprint, Task, task_enums::{TaskStatus, TaskPriority}};
// 1. Create a Project
let project = Project::new(1, "New Website".to_string(), "Build a new company website".to_string(), 101);
// 2. Create an Epic for a major feature
let mut epic = Epic::new(
"User Authentication".to_string(),
Some("Implement login, registration, and profile management".to_string()),
Default::default(),
Some(project.get_id()),
None, None, vec![]
);
// 3. Create a Sprint
let mut sprint = Sprint::new(
"Sprint 1".to_string(),
None,
Default::default(),
Some("Focus on core auth endpoints".to_string()),
Some(project.get_id()),
None, None
);
// 4. Create a Task and link it to the Epic and Sprint
let task = Task::new(
"Create login endpoint".to_string(),
None,
TaskStatus::Todo,
TaskPriority::High,
Some(102), // assignee_id
Some(101), // reporter_id
None, // parent_task_id
Some(epic.get_id()),
Some(sprint.get_id()),
Some(project.get_id()),
None, None, None, vec!["backend".to_string()]
);
// 5. Add the task ID to the epic and sprint
epic = epic.add_task_id(task.get_id());
sprint = sprint.add_task_id(task.get_id());
```

View File

@@ -0,0 +1,55 @@
# User Example Model
The `userexample` model provides a basic but complete example of a model within the `heromodels` ecosystem. It defines a `User` struct that can be used as a template or reference for creating more complex models.
## `User` Struct
The `User` struct represents a user in the system and contains the following fields:
- `base_data`: The standard `BaseModelData` struct, providing a unique ID, timestamps, and comment tracking.
- `username`: The user's unique username.
- `email`: The user's email address.
- `full_name`: The user's full name.
- `is_active`: A boolean flag to indicate if the user's account is active.
The `username`, `email`, and `is_active` fields are indexed for efficient database lookups.
## Usage
The `User` model uses the builder pattern for easy and readable instance creation and modification.
### Creating a User
You can create a new user and set their properties fluently.
```rust
use heromodels::models::userexample::User;
// Create a new user and set their details
let mut user = User::new()
.username("jdoe")
.email("jdoe@example.com")
.full_name("John Doe");
// The user is active by default
assert_eq!(user.is_active, true);
// Deactivate the user
user.deactivate();
assert_eq!(user.is_active, false);
// Activate the user again
user.activate();
assert_eq!(user.is_active, true);
```
### Adding Comments
The model also demonstrates how to interact with the underlying `BaseModelData` to add associated comment IDs.
```rust
use heromodels::models::userexample::User;
let mut user = User::new().username("jdoe");
user = user.add_comment(101); // Add the ID of a comment
```