This commit is contained in:
Timur Gordon
2025-10-29 16:52:33 +01:00
parent 03e5615541
commit 87c556df7a
76 changed files with 10186 additions and 47 deletions

View File

@@ -11,6 +11,14 @@ path = "src/lib.rs"
name = "runner" name = "runner"
path = "src/bin/runner.rs" path = "src/bin/runner.rs"
[[example]]
name = "engine"
path = "examples/engine/main.rs"
[[example]]
name = "freezone"
path = "examples/freezone/main.rs"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
redis = { version = "0.24", features = ["aio", "tokio-comp"] } redis = { version = "0.24", features = ["aio", "tokio-comp"] }
@@ -24,8 +32,10 @@ uuid = { version = "1.6", features = ["v4", "serde"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
osiris_derive = { path = "osiris_derive" } osiris_derive = { path = "osiris_derive" }
lettre = "0.11"
rhai = { version = "1.21.0", features = ["std", "sync", "serde"] } rhai = { version = "1.21.0", features = ["std", "sync", "serde"] }
env_logger = "0.10" env_logger = "0.10"
reqwest = { version = "0.11", features = ["json"] }
[dev-dependencies] [dev-dependencies]
tempfile = "3.8" tempfile = "3.8"

View File

@@ -0,0 +1,232 @@
# Guide: Creating New OSIRIS Objects
This guide explains how to properly create new object types in OSIRIS that integrate with the Rhai scripting engine and context storage.
## Step-by-Step Process
### 1. Create the Object Module
Create a new file in the appropriate directory under `src/objects/`:
- `src/objects/legal/` for legal objects
- `src/objects/money/` for financial objects
- `src/objects/heroledger/` for HeroLedger objects
- etc.
### 2. Define the Object Struct
**CRITICAL**: The struct MUST derive `crate::DeriveObject` to automatically implement the `Object` trait.
```rust
use crate::store::BaseData;
use serde::{Deserialize, Serialize};
/// Your object description
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, crate::DeriveObject)]
pub struct YourObject {
/// Base data for object storage (REQUIRED)
pub base_data: BaseData,
/// Your custom fields
pub title: String,
pub status: YourStatus,
// ... other fields
}
```
**Required traits:**
- `Debug` - for debugging
- `Clone` - required by Object trait
- `Serialize, Deserialize` - for JSON serialization
- `PartialEq` - for comparisons
- `crate::DeriveObject` - **CRITICAL** - auto-implements Object trait
**Required field:**
- `base_data: BaseData` - MUST be present for object storage
### 3. Implement Constructor and Methods
```rust
impl YourObject {
/// Create a new object
pub fn new(id: u32) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
title: String::new(),
status: YourStatus::default(),
// ... initialize other fields
}
}
/// Fluent builder methods
pub fn title(mut self, title: impl ToString) -> Self {
self.title = title.to_string();
self
}
// ... other methods
}
```
### 4. Create Rhai Bindings Module
Create `rhai.rs` in the same directory:
```rust
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::{YourObject, YourStatus};
/// Register your modules with the Rhai engine
pub fn register_your_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<YourObject>("YourObject");
parent_module.set_custom_type::<YourStatus>("YourStatus");
// Merge functions
let your_module = exported_module!(rhai_your_module);
parent_module.merge(&your_module);
}
#[export_module]
mod rhai_your_module {
use super::YourObject;
use ::rhai::EvalAltResult;
// Constructor
#[rhai_fn(name = "new_your_object", return_raw)]
pub fn new_your_object(id: i64) -> Result<YourObject, Box<EvalAltResult>> {
Ok(YourObject::new(id as u32))
}
// Builder methods
#[rhai_fn(name = "title", return_raw)]
pub fn set_title(
obj: YourObject,
title: String,
) -> Result<YourObject, Box<EvalAltResult>> {
Ok(obj.title(title))
}
// Getters
#[rhai_fn(name = "title", pure)]
pub fn get_title(obj: &mut YourObject) -> String {
obj.title.clone()
}
}
// CustomType implementations
impl CustomType for YourObject {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("YourObject");
}
}
impl CustomType for YourStatus {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("YourStatus");
}
}
```
### 5. Update Module Exports
In `mod.rs` of your object category:
```rust
pub mod your_object;
pub mod rhai;
pub use your_object::{YourObject, YourStatus};
pub use rhai::register_your_modules;
```
In `src/objects/mod.rs`:
```rust
pub mod your_category;
pub use your_category::{YourObject, YourStatus};
```
### 6. Register in Engine (CRITICAL STEP)
In `src/engine.rs`, add the save registration in the `OsirisPackage` definition:
```rust
def_package! {
pub OsirisPackage(module) : StandardPackage {
// ... existing registrations ...
// Add your object's save method
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, obj: crate::objects::YourObject| ctx.save_object(obj));
// ... existing registrations ...
// Register your modules
register_your_modules(module);
}
}
```
Also add the import at the top of `engine.rs`:
```rust
use crate::objects::your_category::rhai::register_your_modules;
```
### 7. Create Example Script
Create `examples/engine/XX_your_object.rhai`:
```rhai
print("=== Your Object Example ===\n");
// Get context
let ctx = get_context(["alice", "bob"]);
// Create object
let obj = new_your_object(1)
.title("Example Title");
print("Object created: " + obj.title());
// Store in context
ctx.save(obj);
print("Object stored");
print("\n=== Example Complete ===");
```
## Checklist
Before considering your object complete, verify:
- [ ] Struct derives `crate::DeriveObject`
- [ ] Struct has `base_data: BaseData` field
- [ ] Rhai module created with `register_*_modules` function
- [ ] Custom types registered with `set_custom_type`
- [ ] Module exported in `mod.rs` files
- [ ] Save method registered in `src/engine.rs`
- [ ] Module registration added to `OsirisPackage` in `src/engine.rs`
- [ ] Example script created and tested
- [ ] Example runs successfully with `cargo run --example engine examples/engine/XX_your_object.rhai`
## Common Mistakes to Avoid
1. **Forgetting `crate::DeriveObject`** - Without this, the Object trait won't be implemented
2. **Missing `base_data` field** - Required for all storable objects
3. **Not registering save in engine.rs** - The save method MUST be in engine.rs, not context.rs
4. **Not calling `set_custom_type`** - Rhai won't recognize your type
5. **Not merging the exported module** - Your functions won't be available
## Example: Contract Object
See the Contract object implementation as a reference:
- Struct: `src/objects/legal/contract.rs`
- Rhai bindings: `src/objects/legal/rhai.rs`
- Module exports: `src/objects/legal/mod.rs` and `src/objects/mod.rs`
- Engine registration: `src/engine.rs` (line ~110 and ~138)
- Example: `examples/engine/12_contract.rhai`

View File

@@ -0,0 +1,113 @@
# Freezone Implementation TODO
## Summary
The freezone.rhai example has been created and demonstrates a complete registration flow. However, some Rhai bindings need to be implemented to make it fully functional.
## Required Implementations
### 1. Ethereum Wallet Function
**Location:** `src/objects/money/rhai.rs`
Add a `new_ethereum_wallet()` function that creates an Ethereum wallet:
```rust
#[rhai_fn(name = "new_ethereum_wallet", return_raw)]
pub fn new_ethereum_wallet() -> Result<EthereumWallet, Box<EvalAltResult>> {
// Generate new Ethereum wallet
// Return wallet with address, private key (encrypted), network
Ok(EthereumWallet::new())
}
```
The wallet should have:
- `.owner_id(id)` - set owner
- `.network(network)` - set network (mainnet/testnet)
- `.get_address()` - get Ethereum address
### 2. Accounting Module
**Location:** `src/objects/accounting/`
Create Invoice and Expense objects (files were created but need to be integrated):
**Invoice:**
- `new_invoice(id)` - constructor
- `.invoice_number(num)`, `.customer_id(id)`, `.amount(amt)`, `.currency(cur)`, `.description(desc)`
- `.send()`, `.mark_paid()`, `.mark_overdue()`, `.cancel()`
- Getters: `.invoice_number()`, `.customer_id()`, `.amount()`, `.status()`
**Expense:**
- `new_expense(id)` - constructor
- `.user_id(id)`, `.amount(amt)`, `.currency(cur)`, `.description(desc)`, `.category(cat)`, `.invoice_id(id)`
- `.approve()`, `.mark_paid()`, `.reject()`
- Getters: `.user_id()`, `.amount()`, `.status()`, `.category()`
### 3. Payment Request Enhancements
**Location:** `src/objects/money/payments.rs` and `rhai.rs`
Add `new_payment_request()` function with builder API:
- `.amount(amt)`
- `.currency(cur)`
- `.description(desc)`
- `.callback_url(url)`
- `.merchant_reference(ref)`
### 4. KYC Info Enhancements
**Location:** `src/objects/kyc/info.rs` and `rhai.rs`
Add missing builder methods:
- `.document_type(type)` - passport, id_card, drivers_license
- `.document_number(num)`
- `.verified(bool)` - mark as verified
### 5. Engine Registration
**Location:** `src/engine.rs`
Add to `OsirisPackage`:
```rust
// Register Accounting modules
register_accounting_modules(module);
// Add save methods for Invoice and Expense
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, invoice: crate::objects::Invoice| ctx.save_object(invoice));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, expense: crate::objects::Expense| ctx.save_object(expense));
```
### 6. Module Exports
**Location:** `src/objects/mod.rs`
Add:
```rust
pub mod accounting;
pub use accounting::{Invoice, Expense};
```
## Current Freezone Flow
The freezone.rhai example demonstrates:
1. **Public Key Registration** - User provides public key
2. **Personal Information** - Collect name, email, create KYC info
3. **Terms & Conditions** - Create and sign contract
4. **Email Verification** - Generate code, send email, verify
5. **Crypto Wallet Creation** - Create TFT account + Ethereum wallet
6. **Payment Processing** - Pesapal payment session, user pays, record transaction
7. **KYC Verification** - KYC session, user completes verification, callback with results
8. **User Registration** - Create final user object
## Testing
Once implementations are complete, run:
```bash
cargo run --example freezone
```
Expected output: Complete freezone registration flow with all 8 steps executing successfully.
## Notes
- The example uses simulated callbacks for payment and KYC
- Ethereum wallet generation should use a proper library (e.g., ethers-rs)
- Payment integration with Pesapal is mocked but shows the expected flow
- KYC callback demonstrates how verified data would be received and stored

View File

@@ -0,0 +1,53 @@
// Example 1: Creating and working with Notes
print("=== Note Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create notes with builder pattern
print("\n2. Creating notes...");
let meeting_note = note("notes")
.title("Meeting Notes")
.content("Discussed OSIRIS integration and Rhai packages")
.tag("category", "work")
.tag("priority", "high")
.mime("text/markdown");
print(" ✓ Created meeting note with tags and mime type");
let quick_note = note("notes")
.title("Quick Reminder")
.content("Remember to push changes")
.tag("category", "personal");
print(" ✓ Created quick reminder note");
let project_note = note("notes")
.title("Project Plan")
.content("1. Design\n2. Implement\n3. Test")
.tag("project", "osiris")
.tag("status", "in-progress");
print(" ✓ Created project plan note");
// Save notes to context
print("\n3. Saving notes to context...");
ctx.save(meeting_note);
print(" ✓ Saved meeting note");
ctx.save(quick_note);
print(" ✓ Saved quick note");
ctx.save(project_note);
print(" ✓ Saved project note");
// List all notes in context
print("\n4. Listing notes in context...");
let note_ids = ctx.list("notes");
print(" ✓ Found " + note_ids.len() + " notes in context");
print("\n=== Note Example Complete ===");

View File

@@ -0,0 +1,44 @@
// Example 2: Creating and working with Events
print("=== Event Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "charlie"]);
print(" ✓ Context created: " + ctx.context_id());
// Create events with builder pattern
print("\n2. Creating events...");
let standup = event("calendar", "Daily Standup")
.description("Quick sync on progress and blockers");
print(" ✓ Created daily standup event");
let review = event("calendar", "Sprint Review")
.description("Demo completed features to stakeholders");
print(" ✓ Created sprint review event");
let workshop = event("calendar", "OSIRIS Workshop")
.description("Deep dive into OSIRIS architecture and Rhai integration");
print(" ✓ Created workshop event");
// Save events to context
print("\n3. Saving events to context...");
ctx.save(standup);
print(" ✓ Saved standup event");
ctx.save(review);
print(" ✓ Saved review event");
ctx.save(workshop);
print(" ✓ Saved workshop event");
// List all events in context
print("\n4. Listing events in context...");
let event_ids = ctx.list("calendar");
print(" ✓ Found " + event_ids.len() + " events in context");
print("\n=== Event Example Complete ===");

View File

@@ -0,0 +1,54 @@
// Example 3: Creating and working with Users
print("=== User Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create users with builder pattern
print("\n2. Creating users...");
let alice = new_user()
.username("alice")
.add_email("alice@example.com");
print(" ✓ Created user: alice");
let bob = new_user()
.username("bob")
.add_email("bob@example.com");
print(" ✓ Created user: bob");
let charlie = new_user()
.username("charlie")
.add_email("charlie@example.com");
print(" ✓ Created user: charlie");
// Display user info
print("\n3. User information...");
print(" Alice ID: " + alice.get_id());
print(" Alice username: " + alice.get_username());
print(" Alice email: " + alice.get_email());
print(" Bob ID: " + bob.get_id());
print(" Bob username: " + bob.get_username());
print(" Charlie ID: " + charlie.get_id());
print(" Charlie username: " + charlie.get_username());
// Save users to context
print("\n4. Saving users to context...");
let alice_id = ctx.save(alice);
print(" ✓ Saved alice with ID: " + alice_id);
let bob_id = ctx.save(bob);
print(" ✓ Saved bob with ID: " + bob_id);
let charlie_id = ctx.save(charlie);
print(" ✓ Saved charlie with ID: " + charlie_id);
print("\n=== User Example Complete ===");

View File

@@ -0,0 +1,54 @@
// Example 4: Creating and working with Groups
print("=== Group Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create groups with builder pattern
print("\n2. Creating groups...");
let dev_team = new_group()
.name("Development Team")
.description("Core development team members");
print(" ✓ Created group: Development Team");
let ops_team = new_group()
.name("Operations Team")
.description("Infrastructure and operations team");
print(" ✓ Created group: Operations Team");
let admin_group = new_group()
.name("Administrators")
.description("System administrators with full access");
print(" ✓ Created group: Administrators");
// Display group info
print("\n3. Group information...");
print(" Dev Team ID: " + dev_team.get_id());
print(" Dev Team name: " + dev_team.get_name());
print(" Dev Team description: " + dev_team.get_description());
print(" Ops Team ID: " + ops_team.get_id());
print(" Ops Team name: " + ops_team.get_name());
print(" Admin Group ID: " + admin_group.get_id());
print(" Admin Group name: " + admin_group.get_name());
// Save groups to context
print("\n4. Saving groups to context...");
let dev_id = ctx.save(dev_team);
print(" ✓ Saved Development Team with ID: " + dev_id);
let ops_id = ctx.save(ops_team);
print(" ✓ Saved Operations Team with ID: " + ops_id);
let admin_id = ctx.save(admin_group);
print(" ✓ Saved Administrators with ID: " + admin_id);
print("\n=== Group Example Complete ===");

View File

@@ -0,0 +1,55 @@
// Example 5: Creating and working with Accounts
print("=== Account Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create accounts with builder pattern
print("\n2. Creating accounts...");
let checking = new_account()
.address("checking_001")
.currency("USD");
print(" ✓ Created checking account");
let savings = new_account()
.address("savings_001")
.currency("USD");
print(" ✓ Created savings account");
let crypto = new_account()
.address("0x1234567890abcdef")
.currency("ETH");
print(" ✓ Created crypto account");
// Display account info
print("\n3. Account information...");
print(" Checking ID: " + checking.get_id());
print(" Checking address: " + checking.get_address());
print(" Checking currency: " + checking.get_currency());
print(" Savings ID: " + savings.get_id());
print(" Savings address: " + savings.get_address());
print(" Crypto ID: " + crypto.get_id());
print(" Crypto address: " + crypto.get_address());
print(" Crypto currency: " + crypto.get_currency());
// Save accounts to context
print("\n4. Saving accounts to context...");
let checking_id = ctx.save(checking);
print(" ✓ Saved checking account with ID: " + checking_id);
let savings_id = ctx.save(savings);
print(" ✓ Saved savings account with ID: " + savings_id);
let crypto_id = ctx.save(crypto);
print(" ✓ Saved crypto account with ID: " + crypto_id);
print("\n=== Account Example Complete ===");

View File

@@ -0,0 +1,50 @@
// Example 6: Creating and working with DNS Zones
print("=== DNS Zone Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create DNS zones with builder pattern
print("\n2. Creating DNS zones...");
let example_zone = new_dns_zone()
.domain("example.com");
print(" ✓ Created DNS zone: example.com");
let test_zone = new_dns_zone()
.domain("test.org");
print(" ✓ Created DNS zone: test.org");
let dev_zone = new_dns_zone()
.domain("dev.local");
print(" ✓ Created DNS zone: dev.local");
// Display DNS zone info
print("\n3. DNS Zone information...");
print(" Example Zone ID: " + example_zone.get_id());
print(" Example Zone domain: " + example_zone.get_domain());
print(" Test Zone ID: " + test_zone.get_id());
print(" Test Zone domain: " + test_zone.get_domain());
print(" Dev Zone ID: " + dev_zone.get_id());
print(" Dev Zone domain: " + dev_zone.get_domain());
// Save DNS zones to context
print("\n4. Saving DNS zones to context...");
let example_id = ctx.save(example_zone);
print(" ✓ Saved example.com with ID: " + example_id);
let test_id = ctx.save(test_zone);
print(" ✓ Saved test.org with ID: " + test_id);
let dev_id = ctx.save(dev_zone);
print(" ✓ Saved dev.local with ID: " + dev_id);
print("\n=== DNS Zone Example Complete ===");

104
examples/engine/07_kyc.rhai Normal file
View File

@@ -0,0 +1,104 @@
// Example 7: KYC Info and Session Management
print("=== KYC Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create KYC info objects with builder pattern
print("\n2. Creating KYC info objects...");
let john_info = new_kyc_info()
.client_id("CLIENT_001")
.first_name("John")
.last_name("Doe")
.email("john.doe@example.com")
.phone("+1234567890")
.date_of_birth("1990-01-15")
.nationality("US")
.address("123 Main Street")
.city("New York")
.country("US")
.postal_code("10001")
.provider("idenfy");
print(" ✓ Created KYC info for John Doe");
let jane_info = new_kyc_info()
.client_id("CLIENT_002")
.first_name("Jane")
.last_name("Smith")
.email("jane.smith@example.com")
.phone("+9876543210")
.date_of_birth("1985-05-20")
.nationality("GB")
.provider("idenfy");
print(" ✓ Created KYC info for Jane Smith");
// Display info
print("\n3. KYC information...");
print(" John - ID: " + john_info.get_id());
print(" John - Client ID: " + john_info.get_client_id());
print(" John - Name: " + john_info.get_first_name() + " " + john_info.get_last_name());
print(" John - Email: " + john_info.get_email());
print(" John - Provider: " + john_info.get_provider());
print(" Jane - ID: " + jane_info.get_id());
print(" Jane - Client ID: " + jane_info.get_client_id());
print(" Jane - Name: " + jane_info.get_first_name() + " " + jane_info.get_last_name());
// Save KYC info to context
print("\n4. Saving KYC info to context...");
let john_id = ctx.save(john_info);
print(" ✓ Saved John's KYC info with ID: " + john_id);
let jane_id = ctx.save(jane_info);
print(" ✓ Saved Jane's KYC info with ID: " + jane_id);
// Create KYC verification sessions
print("\n5. Creating KYC verification sessions...");
let john_session = new_kyc_session("CLIENT_001", "idenfy")
.callback_url("https://example.com/kyc/callback")
.success_url("https://example.com/kyc/success")
.error_url("https://example.com/kyc/error")
.locale("en");
print(" ✓ Created verification session for John");
let jane_session = new_kyc_session("CLIENT_002", "idenfy")
.callback_url("https://example.com/kyc/callback")
.success_url("https://example.com/kyc/success")
.error_url("https://example.com/kyc/error")
.locale("en");
print(" ✓ Created verification session for Jane");
// Display session info
print("\n6. Session information...");
print(" John's session - ID: " + john_session.get_id());
print(" John's session - Client ID: " + john_session.get_client_id());
print(" John's session - Provider: " + john_session.get_provider());
// Save KYC sessions to context
print("\n7. Saving KYC sessions to context...");
let john_session_id = ctx.save(john_session);
print(" ✓ Saved John's session with ID: " + john_session_id);
let jane_session_id = ctx.save(jane_session);
print(" ✓ Saved Jane's session with ID: " + jane_session_id);
// List all KYC clients in context
print("\n8. Listing KYC clients in context...");
let client_ids = ctx.list("kyc_clients");
print(" ✓ Found " + client_ids.len() + " KYC clients in context");
// List all KYC sessions in context
print("\n9. Listing KYC sessions in context...");
let session_ids = ctx.list("kyc_sessions");
print(" ✓ Found " + session_ids.len() + " KYC sessions in context");
print("\n=== KYC Example Complete ===");

View File

@@ -0,0 +1,90 @@
// Example 8: Flow Template and Instance Management
print("=== Flow Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create a registration flow template
print("\n2. Creating registration flow template...");
let registration_flow = new_flow()
.name("registration_flow")
.description("User Registration Flow with KYC");
registration_flow.add_step("registration", "User PK Registration");
registration_flow.add_step("kyc", "KYC Verification");
registration_flow.add_step("email", "Email Verification");
print(" ✓ Created registration flow template");
print(" Template: " + registration_flow.get_name());
print(" Description: " + registration_flow.get_description());
// Save the template
print("\n3. Saving flow template to context...");
let template_id = ctx.save(registration_flow.build());
print(" ✓ Saved template with ID: " + template_id);
// Create a flow instance for a user
print("\n4. Creating flow instance for user...");
let user_id = "user_123";
let flow_instance = new_flow_instance(
"registration_flow_user_123",
"registration_flow",
user_id
);
// Initialize steps (in real scenario, would get from template)
flow_instance.start();
print(" ✓ Created and started flow instance");
print(" Instance name: " + flow_instance.get_name());
print(" Template: " + flow_instance.get_template_name());
print(" Entity ID: " + flow_instance.get_entity_id());
print(" Status: " + flow_instance.get_status());
// Save the instance
print("\n5. Saving flow instance to context...");
let instance_id = ctx.save(flow_instance);
print(" ✓ Saved instance with ID: " + instance_id);
// Simulate completing steps
print("\n6. Simulating step completion...");
// Complete registration step
flow_instance.complete_step("registration");
print(" ✓ Completed 'registration' step");
// Save updated instance
ctx.save(flow_instance);
// Complete KYC step
flow_instance.complete_step("kyc");
print(" ✓ Completed 'kyc' step");
// Save updated instance
ctx.save(flow_instance);
// Complete email step
flow_instance.complete_step("email");
print(" ✓ Completed 'email' step");
// Save final instance
ctx.save(flow_instance);
print(" Final status: " + flow_instance.get_status());
// List all flow templates
print("\n7. Listing flow templates in context...");
let template_ids = ctx.list("flow_templates");
print(" ✓ Found " + template_ids.len() + " flow templates");
// List all flow instances
print("\n8. Listing flow instances in context...");
let instance_ids = ctx.list("flow_instances");
print(" ✓ Found " + instance_ids.len() + " flow instances");
print("\n=== Flow Example Complete ===");

View File

@@ -0,0 +1,119 @@
// Example 9: Money - Accounts, Assets, and Transactions
print("=== Money Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob", "charlie"]);
print(" ✓ Context created: " + ctx.context_id());
// Create an asset (e.g., a token)
print("\n2. Creating a digital asset...");
let tft_asset = new_asset()
.address("TFT_ASSET_001")
.asset_type("utility_token")
.issuer(1)
.supply(1000000.0);
print(" ✓ Created TFT asset");
print(" Address: " + tft_asset.get_address());
print(" Type: " + tft_asset.get_asset_type());
print(" Supply: " + tft_asset.get_supply());
// Save the asset
print("\n3. Saving asset to context...");
let asset_id = ctx.save(tft_asset);
print(" ✓ Saved asset with ID: " + asset_id);
// Create accounts for users
print("\n4. Creating user accounts...");
let alice_account = new_account()
.owner_id(100)
.address("ALICE_TFT_ACCOUNT")
.balance(10000.0)
.currency("TFT")
.assetid(1);
print(" ✓ Created Alice's account");
print(" Owner ID: " + alice_account.get_owner_id());
print(" Address: " + alice_account.get_address());
print(" Balance: " + alice_account.get_balance() + " " + alice_account.get_currency());
let bob_account = new_account()
.owner_id(101)
.address("BOB_TFT_ACCOUNT")
.balance(5000.0)
.currency("TFT")
.assetid(1);
print(" ✓ Created Bob's account");
print(" Owner ID: " + bob_account.get_owner_id());
print(" Address: " + bob_account.get_address());
print(" Balance: " + bob_account.get_balance() + " " + bob_account.get_currency());
// Save accounts
print("\n5. Saving accounts to context...");
let alice_account_id = ctx.save(alice_account);
print(" ✓ Saved Alice's account with ID: " + alice_account_id);
let bob_account_id = ctx.save(bob_account);
print(" ✓ Saved Bob's account with ID: " + bob_account_id);
// Create a transaction
print("\n6. Creating a transaction...");
let transaction = new_transaction()
.source(100)
.destination(101)
.amount(500.0)
.assetid(1);
print(" ✓ Created transaction");
print(" From: " + transaction.get_source());
print(" To: " + transaction.get_destination());
print(" Amount: " + transaction.get_amount());
print(" Asset ID: " + transaction.get_assetid());
// Save transaction
print("\n7. Saving transaction to context...");
let tx_id = ctx.save(transaction);
print(" ✓ Saved transaction with ID: " + tx_id);
// Create a multi-currency account
print("\n8. Creating multi-currency account...");
let charlie_usd_account = new_account()
.owner_id(102)
.address("CHARLIE_USD_ACCOUNT")
.balance(25000.0)
.currency("USD")
.assetid(2);
print(" ✓ Created Charlie's USD account");
print(" Balance: " + charlie_usd_account.get_balance() + " " + charlie_usd_account.get_currency());
let charlie_id = ctx.save(charlie_usd_account);
print(" ✓ Saved Charlie's account with ID: " + charlie_id);
// List all accounts
print("\n9. Listing all accounts in context...");
let account_ids = ctx.list("accounts");
print(" ✓ Found " + account_ids.len() + " accounts");
// List all assets
print("\n10. Listing all assets in context...");
let asset_ids = ctx.list("assets");
print(" ✓ Found " + asset_ids.len() + " assets");
// List all transactions
print("\n11. Listing all transactions in context...");
let tx_ids = ctx.list("transactions");
print(" ✓ Found " + tx_ids.len() + " transactions");
print("\n=== Money Example Complete ===");
print("Summary:");
print(" - Created 1 asset (TFT)");
print(" - Created 3 accounts (Alice TFT, Bob TFT, Charlie USD)");
print(" - Created 1 transaction (Alice → Bob: 500 TFT)");

View File

@@ -0,0 +1,103 @@
// Example 10: Communication - Email Verification
print("=== Communication & Email Verification Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create verification for a user
print("\n2. Creating verification...");
let verification = new_verification(
"user_123",
"alice@example.com"
)
.callback_url("https://example.com/verify");
print(" ✓ Created verification");
print(" Entity ID: " + verification.get_entity_id());
print(" Contact: " + verification.get_contact());
print(" Code: " + verification.get_code());
print(" Nonce: " + verification.get_nonce());
print(" Verification URL: " + verification.get_verification_url());
print(" Status: " + verification.get_status());
// Create email client
print("\n3. Creating email client...");
let email_client = new_email_client();
print(" ✓ Email client created");
// Send verification email with link
print("\n4. Sending verification email...");
email_client.send_verification_link(verification);
verification.mark_sent();
print(" ✓ Verification email sent");
print(" Status: " + verification.get_status());
// Save the verification
print("\n5. Saving verification to context...");
let verification_id = ctx.save(verification);
print(" ✓ Saved verification with ID: " + verification_id);
// Simulate user clicking link (nonce verification)
print("\n6. Simulating URL click verification...");
let nonce = verification.get_nonce();
print(" User clicks: " + verification.get_verification_url());
verification.verify_nonce(nonce);
print(" ✓ Verification successful via URL!");
print(" Status: " + verification.get_status());
// Save verified status
ctx.save(verification);
// Create another verification for code-based flow
print("\n7. Creating code-based verification for Bob...");
let bob_verification = new_verification(
"user_456",
"bob@example.com"
);
print(" ✓ Created verification for Bob");
print(" Contact: " + bob_verification.get_contact());
print(" Code: " + bob_verification.get_code());
// Send code via email
print("\n8. Sending code-based verification email...");
email_client.send_verification_code(bob_verification);
bob_verification.mark_sent();
print(" ✓ Code sent");
// Save Bob's verification
let bob_verification_id = ctx.save(bob_verification);
print(" ✓ Saved with ID: " + bob_verification_id);
// Simulate user entering wrong code
print("\n9. Simulating code verification attempts...");
print(" Attempt 1 with wrong code...");
try {
bob_verification.verify_code("000000");
print(" ✗ Unexpected success");
} catch(err) {
print(" ✗ Verification failed (expected): " + err);
print(" Attempts: " + bob_verification.get_attempts());
}
// Verify with correct code
print("\n10. Verifying with correct code...");
let correct_code = bob_verification.get_code();
print(" Using code: " + correct_code);
bob_verification.verify_code(correct_code);
print(" ✓ Verification successful!");
print(" Status: " + bob_verification.get_status());
ctx.save(bob_verification);
// List all verifications
print("\n11. Listing all verifications in context...");
let verification_ids = ctx.list("verifications");
print(" ✓ Found " + verification_ids.len() + " verifications");
print("\n=== Communication & Email Verification Example Complete ===");

View File

@@ -0,0 +1,104 @@
// Example 11: Payment Provider - Pesapal Integration
print("=== Payment Provider & Pesapal Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob"]);
print(" ✓ Context created: " + ctx.context_id());
// Create Pesapal payment client (sandbox mode for testing)
print("\n2. Creating Pesapal payment client (sandbox)...");
let payment_client = new_payment_client_pesapal_sandbox(
1, // Client ID
"qkio1BGGYAXTu2JOfm7XSXNruoZsrqEW", // Consumer key
"osGQ364R49cXKeOYSpaOnT++rHs=" // Consumer secret
);
print(" ✓ Payment client created");
// Create a payment request
print("\n3. Creating payment request...");
let payment_request = new_payment_request(
"ORDER_" + timestamp(), // Merchant reference
2500.00, // Amount
"KES", // Currency (Kenyan Shillings)
"Payment for Premium Subscription", // Description
"https://example.com/payment/callback" // Callback URL
);
// Add customer details
payment_request
.customer_email("customer@example.com")
.customer_phone("+254712345678")
.customer_name("John", "Doe")
.redirect_url("https://example.com/payment/success");
print(" ✓ Payment request created");
print(" Merchant Reference: ORDER_" + timestamp());
print(" Amount: 2500.00 KES");
print(" Description: Payment for Premium Subscription");
// Create payment link
print("\n4. Creating payment link...");
let payment_response = payment_client.create_payment_link(payment_request);
print(" ✓ Payment link created successfully!");
print(" Payment URL: " + payment_response.get_payment_url());
print(" Order Tracking ID: " + payment_response.get_order_tracking_id());
print(" Merchant Reference: " + payment_response.get_merchant_reference());
print(" Status: " + payment_response.get_status());
// In a real scenario, you would:
// 1. Redirect the user to payment_response.get_payment_url()
// 2. User completes payment on Pesapal
// 3. Pesapal sends callback to your callback URL
// 4. You check payment status using the order tracking ID
// Simulate checking payment status
print("\n5. Checking payment status...");
let order_tracking_id = payment_response.get_order_tracking_id();
let payment_status = payment_client.get_payment_status(order_tracking_id);
print(" ✓ Payment status retrieved");
print(" Order Tracking ID: " + order_tracking_id);
print(" Status: " + payment_status.get_status());
print(" Amount: " + payment_status.get_amount() + " " + payment_status.get_currency());
print(" Payment Method: " + payment_status.get_payment_method());
print(" Transaction ID: " + payment_status.get_transaction_id());
// Create another payment for a different scenario
print("\n6. Creating another payment (smaller amount)...");
let small_payment = new_payment_request(
"ORDER_SMALL_" + timestamp(),
150.00,
"KES",
"Coffee Purchase",
"https://example.com/payment/callback"
);
small_payment
.customer_email("coffee@example.com")
.customer_phone("+254798765432");
let small_payment_response = payment_client.create_payment_link(small_payment);
print(" ✓ Small payment link created");
print(" Amount: 150.00 KES");
print(" Payment URL: " + small_payment_response.get_payment_url());
// Example with production client (commented out - requires real credentials)
print("\n7. Production client example (not executed)...");
print(" // For production, use:");
print(" // let prod_client = new_payment_client_pesapal(");
print(" // \"your_production_consumer_key\",");
print(" // \"your_production_consumer_secret\"");
print(" // );");
print("\n=== Payment Provider & Pesapal Example Complete ===");
print("\nNote: This example makes REAL API calls to Pesapal sandbox.");
print("The credentials used are for testing purposes only.");
print("\nPesapal Payment Flow:");
print("1. Create payment request with amount, currency, and customer details");
print("2. Get payment URL from Pesapal");
print("3. Redirect customer to payment URL");
print("4. Customer completes payment (M-PESA, Card, etc.)");
print("5. Pesapal sends callback to your server");
print("6. Check payment status using order tracking ID");
print("7. Fulfill order if payment is successful");

View File

@@ -0,0 +1,95 @@
// Example 12: Legal Contract with Signatures
print("=== Legal Contract Example ===\n");
// Get a context to work with
print("1. Getting context...");
let ctx = get_context(["alice", "bob", "charlie"]);
print(" ✓ Context created: " + ctx.context_id());
// Create a contract
print("\n2. Creating a new contract...");
let contract = new_contract(1)
.title("Service Agreement")
.content("This agreement is made between Party A and Party B for the provision of software development services...")
.creator_id(101);
print(" ✓ Contract created");
print(" Title: " + contract.title());
print(" Status: " + contract.status());
print(" Creator ID: " + contract.creator_id());
// Add signature IDs to contract (signatures would be created separately)
print("\n3. Adding signature IDs to contract...");
print(" (In practice, signatures would be created separately and their IDs referenced here)");
contract = contract
.add_signature(1001) // Alice's signature ID
.add_signature(1002); // Bob's signature ID
print(" ✓ Signature IDs added");
print(" Total signatures: " + contract.signature_count());
// Check if contract is fully signed
print("\n4. Checking signature status...");
let required_signatures = 2;
if contract.is_fully_signed(required_signatures) {
print(" ✓ Contract is fully signed!");
print(" Activating contract...");
contract = contract.activate();
print(" ✓ Contract status: " + contract.status());
} else {
print(" ⚠ Contract needs more signatures");
print(" Current: " + contract.signature_count() + " / Required: " + required_signatures);
}
// Store the contract
print("\n5. Storing contract in context...");
ctx.save(contract);
print(" ✓ Contract stored");
// Simulate contract completion
print("\n6. Completing contract...");
contract = contract.complete();
print(" ✓ Contract status: " + contract.status());
// Example: Contract cancellation (alternative flow)
print("\n7. Example: Creating a contract that gets cancelled...");
let cancelled_contract = new_contract(2)
.title("Cancelled Agreement")
.content("This contract was cancelled before completion")
.creator_id(101);
print(" ✓ Contract created");
cancelled_contract = cancelled_contract.cancel();
print(" ✓ Contract cancelled");
print(" Status: " + cancelled_contract.status());
// Example: Removing a signature
print("\n8. Example: Removing a signature...");
let test_contract = new_contract(3)
.title("Test Contract")
.add_signature(2001)
.add_signature(2002)
.add_signature(2003);
print(" ✓ Contract with 3 signatures created");
print(" Signatures before removal: " + test_contract.signature_count());
test_contract = test_contract.remove_signature(2002);
print(" ✓ Signature 2002 removed");
print(" Signatures after removal: " + test_contract.signature_count());
print("\n=== Legal Contract Example Complete ===");
print("\nContract Workflow:");
print("1. Create contract with title, content, and metadata");
print("2. Parties create signatures for the contract");
print("3. Add signatures to contract using signature IDs");
print("4. Check if contract is fully signed");
print("5. Activate contract when all signatures are collected");
print("6. Complete or cancel contract based on outcome");
print("\nContract Statuses:");
print("- Draft: Initial state, collecting signatures");
print("- Active: Fully signed and in effect");
print("- Completed: Successfully fulfilled");
print("- Cancelled: Terminated before completion");

84
examples/engine/main.rs Normal file
View File

@@ -0,0 +1,84 @@
/// OSIRIS Engine Example
///
/// Demonstrates how to create an OSIRIS engine with the package
/// and execute multiple Rhai scripts
use osiris::register_osiris_full;
use rhai::Engine;
use std::fs;
use std::path::Path;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== OSIRIS Engine Example ===\n");
// Get all .rhai files in the current directory
let example_dir = Path::new(file!()).parent().unwrap();
let mut rhai_files: Vec<_> = fs::read_dir(example_dir)?
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry.path().extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext == "rhai")
.unwrap_or(false)
})
.collect();
// Sort files alphabetically for consistent execution order
rhai_files.sort_by_key(|entry| entry.file_name());
if rhai_files.is_empty() {
println!("No .rhai files found in examples/engine/");
return Ok(());
}
println!("Found {} Rhai script(s):\n", rhai_files.len());
// Execute each Rhai script with a fresh engine
for entry in rhai_files {
let path = entry.path();
let filename = path.file_name().unwrap().to_string_lossy();
println!("─────────────────────────────────────");
println!("Running: {}", filename);
println!("─────────────────────────────────────");
// Create a new raw engine and register all OSIRIS components
let mut engine = Engine::new_raw();
register_osiris_full(&mut engine);
// Set up context tags with SIGNATORIES for get_context
let mut tag_map = rhai::Map::new();
let signatories: rhai::Array = vec![
rhai::Dynamic::from("alice".to_string()),
rhai::Dynamic::from("bob".to_string()),
rhai::Dynamic::from("charlie".to_string()),
];
tag_map.insert("SIGNATORIES".into(), rhai::Dynamic::from(signatories));
tag_map.insert("DB_PATH".into(), "/tmp/osiris_example".to_string().into());
tag_map.insert("CONTEXT_ID".into(), "example_context".to_string().into());
engine.set_default_tag(rhai::Dynamic::from(tag_map));
// Read and execute the script
let script_content = fs::read_to_string(&path)?;
match engine.eval::<rhai::Dynamic>(&script_content) {
Ok(result) => {
println!("\n✓ Success!");
if !result.is_unit() {
println!("Result: {}", result);
}
}
Err(e) => {
println!("\n✗ Error: {}", e);
}
}
println!();
}
println!("─────────────────────────────────────");
println!("All scripts executed!");
Ok(())
}

View File

@@ -0,0 +1,365 @@
// ============================================================================
// FREEZONE REGISTRATION FLOW
// ============================================================================
// Complete registration flow for new freezone members including:
// 0. Freezone initialization and configuration
// 1. Public key registration
// 2. User profile creation & email verification
// 3. Terms & Conditions signing
// 4. Crypto wallet creation (TFT + ETH)
// 5. Payment processing
// 6. KYC verification with info collection
print("=== FREEZONE REGISTRATION FLOW ===\n");
// ============================================================================
// KEYPAIRS AND IDENTITIES
// ============================================================================
// Freezone Organization (Keypair 1)
let freezone_pubkey = "04d0aea7f0a48bcab4389753ddc2e61623dd89d800652b11d0a383eb3ea74561d730bdd06e0ca8f4cd4013907d95782a0a584313e1d91ae5ad09b663de36bfac44";
// User: Timur (Keypair 2)
let timur_pubkey = "04090636d0a15854c4c0b73f65b6de5f6a27a7b22d6fbf5f6d97c45476a0384fe50781444c33f5af577e017599e4b432373fbcdcd844d8783c5e52240a14b63dc3";
print("Identities:");
print(" Freezone: " + freezone_pubkey);
print(" Timur: " + timur_pubkey);
print();
// ============================================================================
// INITIALIZATION: Freezone Configuration
// ============================================================================
print("Initialization: Freezone Configuration");
print("─────────────────────────────────────────────────────────────");
// Create context with freezone as the signatory
let freezone_ctx = get_context([freezone_pubkey]);
print("✓ Freezone context created");
print(" Context ID: " + freezone_ctx.context_id());
print(" Signatory: Freezone (" + freezone_pubkey + ")");
// Configure Email Client
print("\nConfiguring Email Client...");
let freezone_email_client = new_email_client()
.smtp_host("smtp.freezone.example")
.smtp_port(587)
.from_email("noreply@freezone.example")
.from_name("Freezone Platform");
print("✓ Email client configured");
print(" SMTP Host: smtp.freezone.example");
print(" From: noreply@freezone.example");
// Configure Payment Provider (Pesapal)
print("\nConfiguring Payment Provider...");
let freezone_payment_client = new_payment_client_pesapal_sandbox(
1,
"qkio1BGGYAXTu2JOfm7XSXNruoZsrqEW",
"osGQ364R49cXKeOYSpaOnT++rHs="
);
print("✓ Payment provider configured (Pesapal Sandbox)");
print(" Provider: Pesapal");
print(" Environment: Sandbox");
// Configure KYC Provider
print("\nConfiguring KYC Provider...");
print("✓ KYC provider configured");
print(" Provider: Freezone KYC");
print(" Callback URL: https://freezone.example/kyc/callback");
// Create Freezone's own Ethereum wallet
print("\nCreating Freezone Ethereum Wallet...");
let freezone_eth_wallet = new_ethereum_wallet()
.owner_id(999) // Freezone organization ID
.network("mainnet");
print("✓ Freezone Ethereum wallet created");
print(" Address: " + freezone_eth_wallet.get_address());
print(" Network: " + freezone_eth_wallet.get_network());
print(" Owner: Freezone Organization (ID: 999)");
// Save Freezone's Ethereum account
let freezone_eth_account = new_account()
.owner_id(999)
.address(freezone_eth_wallet.get_address())
.currency("ETH")
.balance(0.0);
freezone_ctx.save(freezone_eth_account);
print("✓ Freezone Ethereum account saved");
print("\n✓ Freezone initialization complete\n");
// ============================================================================
// STEP 0: Registration Flow Setup
// ============================================================================
print("Step 0: Registration Flow Setup");
print("─────────────────────────────────────────────────────────────");
// Define registration flow steps
print("\nRegistration Flow Steps:");
print(" 1. Public key registration");
print(" 2. User profile creation & email verification");
print(" 3. Terms & Conditions signing");
print(" 4. Crypto wallet creation (TFT + ETH)");
print(" 5. Payment processing");
print(" 6. KYC verification with info collection");
print();
// ============================================================================
// STEP 1: Public Key Registration
// ============================================================================
print("Step 1: Public Key Registration");
print("─────────────────────────────────────────────────────────────");
// User (Timur) provides their public key
print("User public key received: " + timur_pubkey);
print("✓ Public key validated and stored\n");
// ============================================================================
// STEP 2: User Profile Creation & Email Verification
// ============================================================================
print("Step 2: User Profile Creation & Email Verification");
print("─────────────────────────────────────────────────────────────");
// Collect basic user information
let user_name = "Timur Gordon";
let user_email = "timur@freezone.example";
print("Collecting user information:");
print(" Name: " + user_name);
print(" Email: " + user_email);
// Create user profile
let user_profile = new_user()
.username(user_name)
.pubkey(timur_pubkey)
.add_email(user_email);
print("✓ User profile created");
freezone_ctx.save(user_profile);
print("✓ Profile saved");
// Email Verification
print("\nInitiating email verification...");
let verification = new_verification("user_1", user_email);
print("✓ Email verification created");
print(" Verification code: " + verification.get_code());
print(" Nonce: " + verification.get_nonce());
// Prepare verification email
let verification_link = "https://freezone.example/verify?nonce=" + verification.get_nonce();
print(" Verification link: " + verification_link);
print(" (Email sent to: " + user_email + ")");
// Simulate user clicking verification link and verifying
print("\n✓ User clicks verification link and verifies email");
verification.verify_nonce(verification.get_nonce());
print("✓ Email verified: " + verification.get_status());
freezone_ctx.save(verification);
print("✓ Verification saved\n");
// ============================================================================
// STEP 3: Terms & Conditions Signing
// ============================================================================
print("Step 3: Terms & Conditions Signing");
print("─────────────────────────────────────────────────────────────");
// Create Terms & Conditions contract
let terms_contract = new_contract(1)
.title("Freezone Membership Terms & Conditions")
.content("By signing this agreement, you agree to abide by all freezone rules and regulations...")
.creator_id(999); // Freezone admin
print("✓ Terms & Conditions contract created");
print(" Title: " + terms_contract.title());
// User signs the contract (add their signature ID)
let user_signature_id = 1001;
terms_contract = terms_contract.add_signature(user_signature_id);
print("✓ User signature added (ID: " + user_signature_id + ")");
// Activate contract once signed
if terms_contract.is_fully_signed(1) {
terms_contract = terms_contract.activate();
print("✓ Contract activated: " + terms_contract.status());
}
freezone_ctx.save(terms_contract);
print("✓ Signed contract saved\n");
// ============================================================================
// STEP 4: Crypto Wallet Creation
// ============================================================================
print("Step 4: Crypto Wallet Creation");
print("─────────────────────────────────────────────────────────────");
// Create TFT crypto account for user
let tft_account = new_account()
.owner_id(1)
.currency("TFT") // ThreeFold Token
.balance(0.0);
print("✓ TFT account created");
print(" Owner ID: 1");
print(" Currency: TFT");
print(" Initial balance: 0");
freezone_ctx.save(tft_account);
// Create Ethereum wallet for user
print("\nCreating Ethereum wallet...");
let eth_wallet = new_ethereum_wallet()
.owner_id(1)
.network("mainnet");
print("✓ Ethereum wallet created");
print(" Address: " + eth_wallet.get_address());
print(" Network: mainnet");
print(" Balance: 0 ETH");
// Save as account
let eth_account = new_account()
.owner_id(1)
.address(eth_wallet.get_address())
.currency("ETH")
.balance(0.0);
freezone_ctx.save(eth_account);
print("✓ Ethereum account saved\n");
// ============================================================================
// STEP 5: Payment Processing
// ============================================================================
print("Step 5: Payment Processing");
print("─────────────────────────────────────────────────────────────");
print("Using configured Pesapal payment client...");
// Create payment request for registration fee
print("\nCreating payment session...");
let payment_request = new_payment_request()
.amount(100.0)
.currency("USD")
.description("Freezone Registration Fee")
.callback_url("https://freezone.example/payment/callback")
.merchant_reference("REG_USER_1_" + timestamp());
print("✓ Payment request created");
print(" Amount: $100 USD");
print(" Description: Freezone Registration Fee");
// Initiate payment with Pesapal (this would return a payment URL)
print("\nInitiating payment session with Pesapal...");
print(" Payment URL: https://pay.pesapal.com/iframe/PesapalIframe3/Index/?OrderTrackingId=abc123");
print(" (User would be redirected to Pesapal payment page)");
// Simulate user completing payment
print("\n✓ User clicks payment link and completes payment");
print(" Payment Status: COMPLETED");
print(" Transaction ID: TXN_" + timestamp());
// Create payment transaction record
let payment_tx = new_transaction()
.source(0) // External payment
.destination(1) // Freezone account
.amount(100.0)
.assetid(1);
print("✓ Payment transaction recorded");
freezone_ctx.save(payment_tx);
print("✓ Transaction saved\n");
// ============================================================================
// STEP 6: KYC Verification
// ============================================================================
print("Step 6: KYC Verification");
print("─────────────────────────────────────────────────────────────");
// Create KYC session
let kyc_session = new_kyc_session("user_1", "freezone_kyc")
.callback_url("https://freezone.example/kyc/callback")
.success_url("https://freezone.example/kyc/success")
.error_url("https://freezone.example/kyc/error");
print("✓ KYC session created");
print(" Client ID: " + kyc_session.get_client_id());
print(" Provider: " + kyc_session.get_provider());
// Generate KYC verification URL
print("\nKYC Verification URL generated:");
print(" https://kyc.provider.com/verify?session=kyc_session_" + timestamp());
print(" (User would be redirected to KYC provider)");
// Simulate user clicking KYC link and completing verification
print("\n✓ User clicks KYC link and completes verification");
print(" - Uploads identity document (Passport)");
print(" - Takes selfie for liveness check");
print(" - Provides address proof");
// Simulate KYC callback with verification results
print("\n✓ KYC Provider callback received:");
print(" - Identity verification: PASSED");
print(" - Liveness check: PASSED");
print(" - Address verification: PASSED");
print(" - Sanctions screening: CLEAR");
print(" - PEP check: NOT FOUND");
print(" - Overall Status: VERIFIED");
// Create KYC info with verified data from callback
print("\nStoring verified KYC information...");
let kyc_info_verified = new_kyc_info()
.first_name("Timur")
.last_name("Gordon")
.email(user_email)
.phone("+1-555-0123")
.country("US")
.date_of_birth("1990-05-15")
.document_type("passport")
.document_number("P123456789")
.verified(true);
freezone_ctx.save(kyc_info_verified);
freezone_ctx.save(kyc_session);
print("✓ KYC verification data saved");
print("✓ KYC verification completed\n");
// ============================================================================
// SUMMARY
// ============================================================================
print("═══════════════════════════════════════════════════════════════");
print("REGISTRATION COMPLETE");
print("═══════════════════════════════════════════════════════════════");
print("\nUser Summary:");
print(" Name: " + user_name);
print(" Email: " + user_email);
print(" Public Key: " + timur_pubkey);
print(" TFT Account: Created");
print(" ETH Account: Created");
print(" KYC Status: Verified");
print(" Payment Status: Completed ($100 USD)");
print(" Contract: Signed and Active");
print("\nRegistration Flow:");
print(" ✓ Freezone initialization (Email, Payment, KYC providers configured)");
print(" ✓ Freezone Ethereum wallet created");
print(" ✓ Public key registration");
print(" ✓ User profile creation & email verification");
print(" ✓ Terms & Conditions signed");
print(" ✓ Crypto wallets created (TFT + ETH)");
print(" ✓ Payment processed ($100 USD)");
print(" ✓ KYC verification completed with verified info");
print("\n" + user_name + " is now a verified Freezone member!");
print("═══════════════════════════════════════════════════════════════\n");

48
examples/freezone/main.rs Normal file
View File

@@ -0,0 +1,48 @@
/// OSIRIS Freezone Registration Flow Example
///
/// Demonstrates a complete freezone registration flow with all steps
use osiris::register_osiris_full;
use rhai::Engine;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== OSIRIS Freezone Registration Flow ===\n");
// Create a new raw engine and register all OSIRIS components
let mut engine = Engine::new_raw();
register_osiris_full(&mut engine);
// Set up context tags with SIGNATORIES for get_context
let mut tag_map = rhai::Map::new();
let signatories: rhai::Array = vec![
rhai::Dynamic::from("freezone_admin".to_string()),
rhai::Dynamic::from("kyc_officer".to_string()),
rhai::Dynamic::from("payment_processor".to_string()),
];
tag_map.insert("SIGNATORIES".into(), rhai::Dynamic::from(signatories));
tag_map.insert("DB_PATH".into(), "/tmp/osiris_freezone".to_string().into());
tag_map.insert("CONTEXT_ID".into(), "freezone_context".to_string().into());
engine.set_default_tag(rhai::Dynamic::from(tag_map));
// Read and execute the freezone script
let script_path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/freezone/freezone.rhai");
let script_content = fs::read_to_string(script_path)?;
match engine.eval::<rhai::Dynamic>(&script_content) {
Ok(result) => {
println!("\n✓ Freezone registration flow completed successfully!");
if !result.is_unit() {
println!("Result: {}", result);
}
}
Err(e) => {
eprintln!("\n✗ Error: {}", e);
eprintln!("Error details: {:?}", e);
return Err(e.into());
}
}
Ok(())
}

View File

@@ -9,6 +9,12 @@
/// - Generic CRUD operations for any data /// - Generic CRUD operations for any data
use crate::objects::Note; use crate::objects::Note;
use crate::objects::heroledger::{
user::User,
group::Group,
money::Account,
dnsrecord::DNSZone,
};
use crate::store::{GenericStore, HeroDbClient}; use crate::store::{GenericStore, HeroDbClient};
use rhai::{CustomType, EvalAltResult, TypeBuilder}; use rhai::{CustomType, EvalAltResult, TypeBuilder};
use std::sync::Arc; use std::sync::Arc;
@@ -117,7 +123,8 @@ impl OsirisContext {
tokio::task::block_in_place(|| { tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async move { tokio::runtime::Handle::current().block_on(async move {
let mut note = Note::new(collection_clone); let mut note = Note::new(collection_clone);
note.base_data.id = id_clone.clone(); // Parse string ID to u32, default to 0 if parsing fails
note.base_data.id = id_clone.parse::<u32>().unwrap_or(0);
note.content = Some(json_content); note.content = Some(json_content);
store.put(&note).await store.put(&note).await
@@ -227,30 +234,19 @@ impl OsirisContext {
} }
impl OsirisContext { impl OsirisContext {
/// Save a Note object (typed) /// Generic save method for any Storable object
pub fn save_note(&self, note: Note) -> Result<String, Box<EvalAltResult>> { pub fn save_object<T>(&self, object: T) -> Result<String, Box<EvalAltResult>>
where
T: crate::store::Storable + Send + 'static,
{
let store = self.store.clone(); let store = self.store.clone();
let id = note.base_data.id.clone(); let id = object.base_data().id;
tokio::task::block_in_place(|| { tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async move { tokio::runtime::Handle::current().block_on(async move {
store.put(&note).await store.put(&object).await
.map_err(|e| format!("Failed to save note: {}", e))?; .map_err(|e| format!("Failed to save object: {}", e))?;
Ok(id) Ok(id.to_string())
})
}).map_err(|e: String| e.into())
}
/// Save an Event object (typed)
pub fn save_event(&self, event: crate::objects::Event) -> Result<String, Box<EvalAltResult>> {
let store = self.store.clone();
let id = event.base_data.id.clone();
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async move {
store.put(&event).await
.map_err(|e| format!("Failed to save event: {}", e))?;
Ok(id)
}) })
}).map_err(|e: String| e.into()) }).map_err(|e: String| e.into())
} }
@@ -264,9 +260,13 @@ impl CustomType for OsirisContext {
.with_fn("context_id", |ctx: &mut OsirisContext| ctx.context_id()) .with_fn("context_id", |ctx: &mut OsirisContext| ctx.context_id())
// Generic CRUD (with collection name) // Generic CRUD (with collection name)
.with_fn("save", |ctx: &mut OsirisContext, collection: String, id: String, data: rhai::Dynamic| ctx.save(collection, id, data)) .with_fn("save", |ctx: &mut OsirisContext, collection: String, id: String, data: rhai::Dynamic| ctx.save(collection, id, data))
// Typed save methods (no collection name needed) // Typed save methods (no collection name needed - Rhai will pick the right one based on type)
.with_fn("save", |ctx: &mut OsirisContext, note: Note| ctx.save_note(note)) .with_fn("save", |ctx: &mut OsirisContext, note: Note| ctx.save_object(note))
.with_fn("save", |ctx: &mut OsirisContext, event: crate::objects::Event| ctx.save_event(event)) .with_fn("save", |ctx: &mut OsirisContext, event: crate::objects::Event| ctx.save_object(event))
.with_fn("save", |ctx: &mut OsirisContext, user: User| ctx.save_object(user))
.with_fn("save", |ctx: &mut OsirisContext, group: Group| ctx.save_object(group))
.with_fn("save", |ctx: &mut OsirisContext, account: Account| ctx.save_object(account))
.with_fn("save", |ctx: &mut OsirisContext, zone: DNSZone| ctx.save_object(zone))
.with_fn("get", |ctx: &mut OsirisContext, collection: String, id: String| ctx.get(collection, id)) .with_fn("get", |ctx: &mut OsirisContext, collection: String, id: String| ctx.get(collection, id))
.with_fn("delete", |ctx: &mut OsirisContext, collection: String, id: String| ctx.delete(collection, id)) .with_fn("delete", |ctx: &mut OsirisContext, collection: String, id: String| ctx.delete(collection, id))
.with_fn("list", |ctx: &mut OsirisContext, collection: String| ctx.list(collection)) .with_fn("list", |ctx: &mut OsirisContext, collection: String| ctx.list(collection))

View File

@@ -5,6 +5,12 @@
use crate::context::OsirisContext; use crate::context::OsirisContext;
use crate::objects::note::rhai::register_note_functions; use crate::objects::note::rhai::register_note_functions;
use crate::objects::event::rhai::register_event_functions; use crate::objects::event::rhai::register_event_functions;
use crate::objects::heroledger::rhai::register_heroledger_modules;
use crate::objects::kyc::rhai::register_kyc_modules;
use crate::objects::flow::rhai::register_flow_modules;
use crate::objects::communication::rhai::register_communication_modules;
use crate::objects::money::rhai::register_money_modules;
use crate::objects::legal::rhai::register_legal_modules;
use rhai::{Engine, def_package, FuncRegistration}; use rhai::{Engine, def_package, FuncRegistration};
use rhai::packages::{Package, StandardPackage}; use rhai::packages::{Package, StandardPackage};
@@ -46,7 +52,7 @@ pub fn register_context_api(engine: &mut rhai::Engine) {
let has_signatory = participant_keys.iter().any(|p| signatories.contains(p)); let has_signatory = participant_keys.iter().any(|p| signatories.contains(p));
if !has_signatory { if !has_signatory {
return Err(Box::new(rhai::EvalAltResult::ErrorRuntime( return Err(Box::new(rhai::EvalAltResult::ErrorRuntime(
format!("Access denied: none of the participants are signatories").into(), format!("Access denied: none of the participants are signatories. Signatories: {}", signatories.join(", ")).into(),
context.position() context.position()
))); )));
} }
@@ -63,15 +69,76 @@ pub fn register_context_api(engine: &mut rhai::Engine) {
def_package! { def_package! {
/// OSIRIS package with all OSIRIS types and functions /// OSIRIS package with all OSIRIS types and functions
pub OsirisPackage(module) : StandardPackage { pub OsirisPackage(module) : StandardPackage {
// Register OsirisContext type // Register OsirisContext type with all its methods
module.set_custom_type::<OsirisContext>("OsirisContext"); module.set_custom_type::<OsirisContext>("OsirisContext");
// Register OsirisContext methods
FuncRegistration::new("participants")
.set_into_module(module, |ctx: &mut OsirisContext| ctx.participants());
FuncRegistration::new("context_id")
.set_into_module(module, |ctx: &mut OsirisContext| ctx.context_id());
// Typed save methods - all named "save" for function overloading using generic save_object
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, note: crate::objects::Note| ctx.save_object(note));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, event: crate::objects::Event| ctx.save_object(event));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, user: crate::objects::heroledger::user::User| ctx.save_object(user));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, group: crate::objects::heroledger::group::Group| ctx.save_object(group));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, account: crate::objects::heroledger::money::Account| ctx.save_object(account));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, zone: crate::objects::heroledger::dnsrecord::DNSZone| ctx.save_object(zone));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, kyc_info: crate::objects::KycInfo| ctx.save_object(kyc_info));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, kyc_session: crate::objects::KycSession| ctx.save_object(kyc_session));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, flow_template: crate::objects::FlowTemplate| ctx.save_object(flow_template));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, flow_instance: crate::objects::FlowInstance| ctx.save_object(flow_instance));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, verification: crate::objects::Verification| ctx.save_object(verification));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, account: crate::objects::Account| ctx.save_object(account));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, asset: crate::objects::Asset| ctx.save_object(asset));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, transaction: crate::objects::Transaction| ctx.save_object(transaction));
FuncRegistration::new("save")
.set_into_module(module, |ctx: &mut OsirisContext, contract: crate::objects::Contract| ctx.save_object(contract));
FuncRegistration::new("list")
.set_into_module(module, |ctx: &mut OsirisContext, collection: String| ctx.list(collection));
FuncRegistration::new("get")
.set_into_module(module, |ctx: &mut OsirisContext, collection: String, id: String| ctx.get(collection, id));
FuncRegistration::new("delete")
.set_into_module(module, |ctx: &mut OsirisContext, collection: String, id: String| ctx.delete(collection, id));
// Register Note functions // Register Note functions
register_note_functions(module); register_note_functions(module);
// Register Event functions // Register Event functions
register_event_functions(module); register_event_functions(module);
// Register HeroLedger modules (User, Group, Account, DNSZone)
register_heroledger_modules(module);
// Register KYC modules (KycClient, KycSession)
register_kyc_modules(module);
// Register Flow modules (FlowTemplate, FlowInstance)
register_flow_modules(module);
// Register Communication modules (Verification, EmailClient)
register_communication_modules(module);
// Register Money modules (Account, Asset, Transaction, PaymentClient)
register_money_modules(module);
// Register Legal modules (Contract)
register_legal_modules(module);
// Register get_context function with signatory-based access control // Register get_context function with signatory-based access control
FuncRegistration::new("get_context") FuncRegistration::new("get_context")
.set_into_module(module, |context: rhai::NativeCallContext, participants: rhai::Array| -> Result<OsirisContext, Box<rhai::EvalAltResult>> { .set_into_module(module, |context: rhai::NativeCallContext, participants: rhai::Array| -> Result<OsirisContext, Box<rhai::EvalAltResult>> {
@@ -117,11 +184,17 @@ def_package! {
} }
} }
/// Register all OSIRIS components into an engine
/// This is a convenience function that registers the complete OsirisPackage
pub fn register_osiris_full(engine: &mut Engine) {
let package = OsirisPackage::new();
package.register_into_engine(engine);
}
/// Create a single OSIRIS engine (for backward compatibility) /// Create a single OSIRIS engine (for backward compatibility)
pub fn create_osiris_engine() -> Result<Engine, Box<dyn std::error::Error>> { pub fn create_osiris_engine() -> Result<Engine, Box<dyn std::error::Error>> {
let mut engine = Engine::new_raw(); let mut engine = Engine::new_raw();
let package = OsirisPackage::new(); register_osiris_full(&mut engine);
package.register_into_engine(&mut engine);
Ok(engine) Ok(engine)
} }

View File

@@ -21,6 +21,7 @@ pub use objects::{Event, Note};
pub use context::{OsirisContext, OsirisInstance, OsirisContextBuilder}; pub use context::{OsirisContext, OsirisInstance, OsirisContextBuilder};
pub use engine::{ pub use engine::{
create_osiris_engine, create_osiris_engine,
register_osiris_full,
OsirisPackage, OsirisPackage,
}; };

View File

@@ -0,0 +1,151 @@
/// Expense Object for Accounting
use crate::store::BaseData;
use serde::{Deserialize, Serialize};
/// Expense category
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ExpenseCategory {
Registration,
Subscription,
Service,
Product,
Other,
}
impl Default for ExpenseCategory {
fn default() -> Self {
ExpenseCategory::Other
}
}
/// Expense status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ExpenseStatus {
Pending,
Approved,
Paid,
Rejected,
}
impl Default for ExpenseStatus {
fn default() -> Self {
ExpenseStatus::Pending
}
}
/// Expense record
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, crate::DeriveObject)]
pub struct Expense {
/// Base data for object storage
pub base_data: BaseData,
/// User/entity ID who incurred the expense
pub user_id: u32,
/// Amount
pub amount: f64,
/// Currency
pub currency: String,
/// Description
pub description: String,
/// Category
pub category: ExpenseCategory,
/// Status
pub status: ExpenseStatus,
/// Date incurred (unix timestamp)
pub expense_date: u64,
/// Related invoice ID (if any)
pub invoice_id: Option<u32>,
}
impl Expense {
/// Create a new expense
pub fn new(id: u32) -> Self {
let base_data = BaseData::with_id(id, String::new());
let now = time::OffsetDateTime::now_utc().unix_timestamp() as u64;
Self {
base_data,
user_id: 0,
amount: 0.0,
currency: String::from("USD"),
description: String::new(),
category: ExpenseCategory::default(),
status: ExpenseStatus::default(),
expense_date: now,
invoice_id: None,
}
}
/// Set user ID (fluent)
pub fn user_id(mut self, id: u32) -> Self {
self.user_id = id;
self
}
/// Set amount (fluent)
pub fn amount(mut self, amount: f64) -> Self {
self.amount = amount;
self
}
/// Set currency (fluent)
pub fn currency(mut self, currency: impl ToString) -> Self {
self.currency = currency.to_string();
self
}
/// Set description (fluent)
pub fn description(mut self, description: impl ToString) -> Self {
self.description = description.to_string();
self
}
/// Set category (fluent)
pub fn category(mut self, category: ExpenseCategory) -> Self {
self.category = category;
self
}
/// Set category from string (fluent)
pub fn category_str(mut self, category: &str) -> Self {
self.category = match category.to_lowercase().as_str() {
"registration" => ExpenseCategory::Registration,
"subscription" => ExpenseCategory::Subscription,
"service" => ExpenseCategory::Service,
"product" => ExpenseCategory::Product,
_ => ExpenseCategory::Other,
};
self
}
/// Set invoice ID (fluent)
pub fn invoice_id(mut self, id: u32) -> Self {
self.invoice_id = Some(id);
self
}
/// Approve expense
pub fn approve(mut self) -> Self {
self.status = ExpenseStatus::Approved;
self
}
/// Mark as paid
pub fn mark_paid(mut self) -> Self {
self.status = ExpenseStatus::Paid;
self
}
/// Reject expense
pub fn reject(mut self) -> Self {
self.status = ExpenseStatus::Rejected;
self
}
}

View File

@@ -0,0 +1,130 @@
/// Invoice Object for Accounting
use crate::store::BaseData;
use serde::{Deserialize, Serialize};
/// Invoice status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum InvoiceStatus {
Draft,
Sent,
Paid,
Overdue,
Cancelled,
}
impl Default for InvoiceStatus {
fn default() -> Self {
InvoiceStatus::Draft
}
}
/// Invoice for billing
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, crate::DeriveObject)]
pub struct Invoice {
/// Base data for object storage
pub base_data: BaseData,
/// Invoice number
pub invoice_number: String,
/// Customer/payer ID
pub customer_id: u32,
/// Amount
pub amount: f64,
/// Currency
pub currency: String,
/// Description
pub description: String,
/// Status
pub status: InvoiceStatus,
/// Due date (unix timestamp)
pub due_date: Option<u64>,
/// Payment date (unix timestamp)
pub paid_date: Option<u64>,
}
impl Invoice {
/// Create a new invoice
pub fn new(id: u32) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
invoice_number: String::new(),
customer_id: 0,
amount: 0.0,
currency: String::from("USD"),
description: String::new(),
status: InvoiceStatus::default(),
due_date: None,
paid_date: None,
}
}
/// Set invoice number (fluent)
pub fn invoice_number(mut self, number: impl ToString) -> Self {
self.invoice_number = number.to_string();
self
}
/// Set customer ID (fluent)
pub fn customer_id(mut self, id: u32) -> Self {
self.customer_id = id;
self
}
/// Set amount (fluent)
pub fn amount(mut self, amount: f64) -> Self {
self.amount = amount;
self
}
/// Set currency (fluent)
pub fn currency(mut self, currency: impl ToString) -> Self {
self.currency = currency.to_string();
self
}
/// Set description (fluent)
pub fn description(mut self, description: impl ToString) -> Self {
self.description = description.to_string();
self
}
/// Set due date (fluent)
pub fn due_date(mut self, date: u64) -> Self {
self.due_date = Some(date);
self
}
/// Mark as sent
pub fn send(mut self) -> Self {
self.status = InvoiceStatus::Sent;
self
}
/// Mark as paid
pub fn mark_paid(mut self) -> Self {
self.status = InvoiceStatus::Paid;
self.paid_date = Some(time::OffsetDateTime::now_utc().unix_timestamp() as u64);
self
}
/// Mark as overdue
pub fn mark_overdue(mut self) -> Self {
self.status = InvoiceStatus::Overdue;
self
}
/// Cancel invoice
pub fn cancel(mut self) -> Self {
self.status = InvoiceStatus::Cancelled;
self
}
}

View File

@@ -0,0 +1,11 @@
/// Accounting Module
///
/// Provides Invoice and Expense objects for financial tracking
pub mod invoice;
pub mod expense;
pub mod rhai;
pub use invoice::{Invoice, InvoiceStatus};
pub use expense::{Expense, ExpenseCategory, ExpenseStatus};
pub use rhai::register_accounting_modules;

View File

View File

@@ -0,0 +1,316 @@
/// Email Client
///
/// Real SMTP email client for sending emails including verification emails.
use serde::{Deserialize, Serialize};
use super::verification::Verification;
use lettre::{
Message, SmtpTransport, Transport,
message::{header::ContentType, MultiPart, SinglePart},
transport::smtp::authentication::Credentials,
};
/// Email client with SMTP configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmailClient {
/// SMTP server hostname
pub smtp_host: String,
/// SMTP port
pub smtp_port: u16,
/// Username for SMTP auth
pub username: String,
/// Password for SMTP auth
pub password: String,
/// From address
pub from_address: String,
/// From name
pub from_name: String,
/// Use TLS
pub use_tls: bool,
}
impl Default for EmailClient {
fn default() -> Self {
Self {
smtp_host: "localhost".to_string(),
smtp_port: 587,
username: String::new(),
password: String::new(),
from_address: "noreply@example.com".to_string(),
from_name: "No Reply".to_string(),
use_tls: true,
}
}
}
impl EmailClient {
/// Create a new email client
pub fn new() -> Self {
Self::default()
}
/// Builder: Set SMTP host
pub fn smtp_host(mut self, host: String) -> Self {
self.smtp_host = host;
self
}
/// Builder: Set SMTP port
pub fn smtp_port(mut self, port: u16) -> Self {
self.smtp_port = port;
self
}
/// Builder: Set username
pub fn username(mut self, username: String) -> Self {
self.username = username;
self
}
/// Builder: Set password
pub fn password(mut self, password: String) -> Self {
self.password = password;
self
}
/// Builder: Set from address
pub fn from_address(mut self, address: String) -> Self {
self.from_address = address;
self
}
/// Builder: Set from name
pub fn from_name(mut self, name: String) -> Self {
self.from_name = name;
self
}
/// Builder: Set use TLS
pub fn use_tls(mut self, use_tls: bool) -> Self {
self.use_tls = use_tls;
self
}
/// Build SMTP transport
fn build_transport(&self) -> Result<SmtpTransport, String> {
let creds = Credentials::new(
self.username.clone(),
self.password.clone(),
);
let transport = if self.use_tls {
SmtpTransport::starttls_relay(&self.smtp_host)
.map_err(|e| format!("Failed to create SMTP transport: {}", e))?
.credentials(creds)
.port(self.smtp_port)
.build()
} else {
SmtpTransport::builder_dangerous(&self.smtp_host)
.credentials(creds)
.port(self.smtp_port)
.build()
};
Ok(transport)
}
/// Send a plain text email
pub fn send_email(
&self,
to: &str,
subject: &str,
body: &str,
) -> Result<(), String> {
let from_mailbox = format!("{} <{}>", self.from_name, self.from_address)
.parse()
.map_err(|e| format!("Invalid from address: {}", e))?;
let to_mailbox = to.parse()
.map_err(|e| format!("Invalid to address: {}", e))?;
let email = Message::builder()
.from(from_mailbox)
.to(to_mailbox)
.subject(subject)
.body(body.to_string())
.map_err(|e| format!("Failed to build email: {}", e))?;
let transport = self.build_transport()?;
transport.send(&email)
.map_err(|e| format!("Failed to send email: {}", e))?;
Ok(())
}
/// Send an HTML email
pub fn send_html_email(
&self,
to: &str,
subject: &str,
html_body: &str,
text_body: Option<&str>,
) -> Result<(), String> {
let from_mailbox = format!("{} <{}>", self.from_name, self.from_address)
.parse()
.map_err(|e| format!("Invalid from address: {}", e))?;
let to_mailbox = to.parse()
.map_err(|e| format!("Invalid to address: {}", e))?;
// Build multipart email with text and HTML alternatives
let text_part = if let Some(text) = text_body {
SinglePart::builder()
.header(ContentType::TEXT_PLAIN)
.body(text.to_string())
} else {
SinglePart::builder()
.header(ContentType::TEXT_PLAIN)
.body(String::new())
};
let html_part = SinglePart::builder()
.header(ContentType::TEXT_HTML)
.body(html_body.to_string());
let multipart = MultiPart::alternative()
.singlepart(text_part)
.singlepart(html_part);
let email = Message::builder()
.from(from_mailbox)
.to(to_mailbox)
.subject(subject)
.multipart(multipart)
.map_err(|e| format!("Failed to build email: {}", e))?;
let transport = self.build_transport()?;
transport.send(&email)
.map_err(|e| format!("Failed to send email: {}", e))?;
Ok(())
}
/// Send a verification email with code
pub fn send_verification_code_email(
&self,
verification: &Verification,
) -> Result<(), String> {
let subject = "Verify your email address";
let body = format!(
"Hello,\n\n\
Please verify your email address by entering this code:\n\n\
{}\n\n\
This code will expire in 24 hours.\n\n\
If you didn't request this, please ignore this email.",
verification.verification_code
);
self.send_email(&verification.contact, subject, &body)
}
/// Send a verification email with URL link
pub fn send_verification_link_email(
&self,
verification: &Verification,
) -> Result<(), String> {
let verification_url = verification.get_verification_url()
.ok_or_else(|| "No callback URL configured".to_string())?;
let subject = "Verify your email address";
let html_body = format!(
r#"<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; }}
.container {{ max-width: 600px; margin: 0 auto; padding: 20px; }}
.button {{
display: inline-block;
padding: 12px 24px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
margin: 20px 0;
}}
.code {{
font-size: 24px;
font-weight: bold;
letter-spacing: 4px;
padding: 10px;
background-color: #f5f5f5;
display: inline-block;
margin: 10px 0;
}}
</style>
</head>
<body>
<div class="container">
<h2>Verify your email address</h2>
<p>Hello,</p>
<p>Please verify your email address by clicking the button below:</p>
<a href="{}" class="button">Verify Email</a>
<p>Or enter this verification code:</p>
<div class="code">{}</div>
<p>This link and code will expire in 24 hours.</p>
<p>If you didn't request this, please ignore this email.</p>
</div>
</body>
</html>"#,
verification_url, verification.verification_code
);
let text_body = format!(
"Hello,\n\n\
Please verify your email address by visiting this link:\n\
{}\n\n\
Or enter this verification code: {}\n\n\
This link and code will expire in 24 hours.\n\n\
If you didn't request this, please ignore this email.",
verification_url, verification.verification_code
);
self.send_html_email(
&verification.contact,
subject,
&html_body,
Some(&text_body),
)
}
}
// For Rhai integration, we need a simpler synchronous wrapper
impl EmailClient {
/// Synchronous wrapper for send_verification_code_email
pub fn send_verification_code_sync(&self, verification: &Verification) -> Result<(), String> {
// In a real implementation, you'd use tokio::runtime::Runtime::new().block_on()
// For now, just simulate
println!("=== VERIFICATION CODE EMAIL ===");
println!("To: {}", verification.contact);
println!("Code: {}", verification.verification_code);
println!("===============================");
Ok(())
}
/// Synchronous wrapper for send_verification_link_email
pub fn send_verification_link_sync(&self, verification: &Verification) -> Result<(), String> {
let verification_url = verification.get_verification_url()
.ok_or_else(|| "No callback URL configured".to_string())?;
println!("=== VERIFICATION LINK EMAIL ===");
println!("To: {}", verification.contact);
println!("Code: {}", verification.verification_code);
println!("Link: {}", verification_url);
println!("===============================");
Ok(())
}
}

View File

@@ -0,0 +1,10 @@
/// Communication Module
///
/// Transport-agnostic verification and email client.
pub mod verification;
pub mod email;
pub mod rhai;
pub use verification::{Verification, VerificationStatus, VerificationTransport};
pub use email::EmailClient;

View File

@@ -0,0 +1,180 @@
/// Rhai bindings for Communication (Verification and Email)
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::verification::{Verification, VerificationStatus, VerificationTransport};
use super::email::EmailClient;
// ============================================================================
// Verification Module
// ============================================================================
type RhaiVerification = Verification;
#[export_module]
mod rhai_verification_module {
use super::RhaiVerification;
use super::super::verification::{Verification, VerificationTransport};
#[rhai_fn(name = "new_verification", return_raw)]
pub fn new_verification(
entity_id: String,
contact: String,
) -> Result<RhaiVerification, Box<EvalAltResult>> {
// Default to email transport
Ok(Verification::new(0, entity_id, contact, VerificationTransport::Email))
}
#[rhai_fn(name = "callback_url", return_raw)]
pub fn set_callback_url(
verification: &mut RhaiVerification,
url: String,
) -> Result<RhaiVerification, Box<EvalAltResult>> {
let owned = std::mem::take(verification);
*verification = owned.callback_url(url);
Ok(verification.clone())
}
#[rhai_fn(name = "mark_sent", return_raw)]
pub fn mark_sent(
verification: &mut RhaiVerification,
) -> Result<(), Box<EvalAltResult>> {
verification.mark_sent();
Ok(())
}
#[rhai_fn(name = "verify_code", return_raw)]
pub fn verify_code(
verification: &mut RhaiVerification,
code: String,
) -> Result<(), Box<EvalAltResult>> {
verification.verify_code(&code)
.map_err(|e| e.into())
}
#[rhai_fn(name = "verify_nonce", return_raw)]
pub fn verify_nonce(
verification: &mut RhaiVerification,
nonce: String,
) -> Result<(), Box<EvalAltResult>> {
verification.verify_nonce(&nonce)
.map_err(|e| e.into())
}
#[rhai_fn(name = "resend", return_raw)]
pub fn resend(
verification: &mut RhaiVerification,
) -> Result<(), Box<EvalAltResult>> {
verification.resend();
Ok(())
}
// Getters
#[rhai_fn(name = "get_entity_id")]
pub fn get_entity_id(verification: &mut RhaiVerification) -> String {
verification.entity_id.clone()
}
#[rhai_fn(name = "get_contact")]
pub fn get_contact(verification: &mut RhaiVerification) -> String {
verification.contact.clone()
}
#[rhai_fn(name = "get_code")]
pub fn get_code(verification: &mut RhaiVerification) -> String {
verification.verification_code.clone()
}
#[rhai_fn(name = "get_nonce")]
pub fn get_nonce(verification: &mut RhaiVerification) -> String {
verification.verification_nonce.clone()
}
#[rhai_fn(name = "get_verification_url")]
pub fn get_verification_url(verification: &mut RhaiVerification) -> String {
verification.get_verification_url().unwrap_or_default()
}
#[rhai_fn(name = "get_status")]
pub fn get_status(verification: &mut RhaiVerification) -> String {
format!("{:?}", verification.status)
}
#[rhai_fn(name = "get_attempts")]
pub fn get_attempts(verification: &mut RhaiVerification) -> i64 {
verification.attempts as i64
}
}
// ============================================================================
// Email Client Module
// ============================================================================
type RhaiEmailClient = EmailClient;
#[export_module]
mod rhai_email_module {
use super::RhaiEmailClient;
use super::super::email::EmailClient;
use super::super::verification::Verification;
use ::rhai::EvalAltResult;
#[rhai_fn(name = "new_email_client", return_raw)]
pub fn new_email_client() -> Result<RhaiEmailClient, Box<EvalAltResult>> {
Ok(EmailClient::new())
}
#[rhai_fn(name = "send_verification_code", return_raw)]
pub fn send_verification_code(
client: &mut RhaiEmailClient,
verification: Verification,
) -> Result<(), Box<EvalAltResult>> {
client.send_verification_code_sync(&verification)
.map_err(|e| e.into())
}
#[rhai_fn(name = "send_verification_link", return_raw)]
pub fn send_verification_link(
client: &mut RhaiEmailClient,
verification: Verification,
) -> Result<(), Box<EvalAltResult>> {
client.send_verification_link_sync(&verification)
.map_err(|e| e.into())
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register Communication modules into a Rhai Module
pub fn register_communication_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<Verification>("Verification");
parent_module.set_custom_type::<EmailClient>("EmailClient");
// Merge verification functions
let verification_module = exported_module!(rhai_verification_module);
parent_module.merge(&verification_module);
// Merge email client functions
let email_module = exported_module!(rhai_email_module);
parent_module.merge(&email_module);
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for Verification {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Verification");
}
}
impl CustomType for EmailClient {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("EmailClient");
}
}

View File

@@ -0,0 +1,239 @@
/// Transport-Agnostic Verification
///
/// Manages verification sessions with codes and nonces for email, SMS, etc.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
/// Verification transport type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum VerificationTransport {
Email,
Sms,
WhatsApp,
Telegram,
Other(String),
}
impl Default for VerificationTransport {
fn default() -> Self {
VerificationTransport::Email
}
}
/// Verification status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum VerificationStatus {
#[default]
Pending,
Sent,
Verified,
Expired,
Failed,
}
/// Verification Session
///
/// Transport-agnostic verification that can be used for email, SMS, etc.
/// Supports both code-based verification and URL-based (nonce) verification.
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct Verification {
#[serde(flatten)]
pub base_data: BaseData,
/// User/entity ID this verification is for
pub entity_id: String,
/// Contact address (email, phone, etc.)
pub contact: String,
/// Transport type
pub transport: VerificationTransport,
/// Verification code (6 digits for user entry)
pub verification_code: String,
/// Verification nonce (for URL-based verification)
pub verification_nonce: String,
/// Current status
pub status: VerificationStatus,
/// When verification was sent
pub sent_at: Option<u64>,
/// When verification was completed
pub verified_at: Option<u64>,
/// When verification expires
pub expires_at: Option<u64>,
/// Number of attempts
pub attempts: u32,
/// Maximum attempts allowed
pub max_attempts: u32,
/// Callback URL (for server to construct verification link)
pub callback_url: Option<String>,
/// Additional metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
impl Verification {
/// Create a new verification
pub fn new(id: u32, entity_id: String, contact: String, transport: VerificationTransport) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
// Generate verification code (6 digits)
let code = Self::generate_code();
// Generate verification nonce (32 char hex)
let nonce = Self::generate_nonce();
// Set expiry to 24 hours from now
let expires_at = Self::now() + (24 * 60 * 60);
Self {
base_data,
entity_id,
contact,
transport,
verification_code: code,
verification_nonce: nonce,
status: VerificationStatus::Pending,
sent_at: None,
verified_at: None,
expires_at: Some(expires_at),
attempts: 0,
max_attempts: 3,
callback_url: None,
metadata: std::collections::HashMap::new(),
}
}
/// Generate a 6-digit verification code
fn generate_code() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("{:06}", (timestamp % 1_000_000) as u32)
}
/// Generate a verification nonce (32 char hex string)
fn generate_nonce() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("{:032x}", timestamp)
}
/// Set callback URL
pub fn callback_url(mut self, url: String) -> Self {
self.callback_url = Some(url);
self
}
/// Get verification URL (callback_url + nonce)
pub fn get_verification_url(&self) -> Option<String> {
self.callback_url.as_ref().map(|base_url| {
if base_url.contains('?') {
format!("{}&nonce={}", base_url, self.verification_nonce)
} else {
format!("{}?nonce={}", base_url, self.verification_nonce)
}
})
}
/// Mark as sent
pub fn mark_sent(&mut self) {
self.status = VerificationStatus::Sent;
self.sent_at = Some(Self::now());
self.base_data.update_modified();
}
/// Verify with code
pub fn verify_code(&mut self, code: &str) -> Result<(), String> {
// Check if expired
if let Some(expires_at) = self.expires_at {
if Self::now() > expires_at {
self.status = VerificationStatus::Expired;
self.base_data.update_modified();
return Err("Verification code expired".to_string());
}
}
// Check attempts
self.attempts += 1;
if self.attempts > self.max_attempts {
self.status = VerificationStatus::Failed;
self.base_data.update_modified();
return Err("Maximum attempts exceeded".to_string());
}
// Check code
if code != self.verification_code {
self.base_data.update_modified();
return Err("Invalid verification code".to_string());
}
// Success
self.status = VerificationStatus::Verified;
self.verified_at = Some(Self::now());
self.base_data.update_modified();
Ok(())
}
/// Verify with nonce (for URL-based verification)
pub fn verify_nonce(&mut self, nonce: &str) -> Result<(), String> {
// Check if expired
if let Some(expires_at) = self.expires_at {
if Self::now() > expires_at {
self.status = VerificationStatus::Expired;
self.base_data.update_modified();
return Err("Verification link expired".to_string());
}
}
// Check nonce
if nonce != self.verification_nonce {
self.base_data.update_modified();
return Err("Invalid verification link".to_string());
}
// Success
self.status = VerificationStatus::Verified;
self.verified_at = Some(Self::now());
self.base_data.update_modified();
Ok(())
}
/// Resend verification (generate new code and nonce)
pub fn resend(&mut self) {
self.verification_code = Self::generate_code();
self.verification_nonce = Self::generate_nonce();
self.status = VerificationStatus::Pending;
self.attempts = 0;
// Extend expiry
self.expires_at = Some(Self::now() + (24 * 60 * 60));
self.base_data.update_modified();
}
/// Helper to get current timestamp
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
}

View File

@@ -0,0 +1,155 @@
/// Email Verification
///
/// Manages email verification sessions and status.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
/// Email verification status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum VerificationStatus {
#[default]
Pending,
Sent,
Verified,
Expired,
Failed,
}
/// Email Verification Session
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct EmailVerification {
#[serde(flatten)]
pub base_data: BaseData,
/// User/entity ID this verification is for
pub entity_id: String,
/// Email address to verify
pub email: String,
/// Verification code/token
pub verification_code: String,
/// Current status
pub status: VerificationStatus,
/// When verification was sent
pub sent_at: Option<u64>,
/// When verification was completed
pub verified_at: Option<u64>,
/// When verification expires
pub expires_at: Option<u64>,
/// Number of attempts
pub attempts: u32,
/// Maximum attempts allowed
pub max_attempts: u32,
/// Additional metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
impl EmailVerification {
/// Create a new email verification
pub fn new(id: u32, entity_id: String, email: String) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
// Generate verification code (6 digits)
let code = Self::generate_code();
// Set expiry to 24 hours from now
let expires_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() + (24 * 60 * 60);
Self {
base_data,
entity_id,
email,
verification_code: code,
status: VerificationStatus::Pending,
sent_at: None,
verified_at: None,
expires_at: Some(expires_at),
attempts: 0,
max_attempts: 3,
metadata: std::collections::HashMap::new(),
}
}
/// Generate a 6-digit verification code
fn generate_code() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("{:06}", (timestamp % 1_000_000) as u32)
}
/// Mark as sent
pub fn mark_sent(&mut self) {
self.status = VerificationStatus::Sent;
self.sent_at = Some(Self::now());
self.base_data.update_modified();
}
/// Verify with code
pub fn verify(&mut self, code: &str) -> Result<(), String> {
// Check if expired
if let Some(expires_at) = self.expires_at {
if Self::now() > expires_at {
self.status = VerificationStatus::Expired;
self.base_data.update_modified();
return Err("Verification code expired".to_string());
}
}
// Check attempts
self.attempts += 1;
if self.attempts > self.max_attempts {
self.status = VerificationStatus::Failed;
self.base_data.update_modified();
return Err("Maximum attempts exceeded".to_string());
}
// Check code
if code != self.verification_code {
self.base_data.update_modified();
return Err("Invalid verification code".to_string());
}
// Success
self.status = VerificationStatus::Verified;
self.verified_at = Some(Self::now());
self.base_data.update_modified();
Ok(())
}
/// Resend verification (generate new code)
pub fn resend(&mut self) {
self.verification_code = Self::generate_code();
self.status = VerificationStatus::Pending;
self.attempts = 0;
// Extend expiry
self.expires_at = Some(Self::now() + (24 * 60 * 60));
self.base_data.update_modified();
}
/// Helper to get current timestamp
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
}

View File

@@ -56,7 +56,7 @@ impl Event {
pub fn new(ns: String, title: impl ToString) -> Self { pub fn new(ns: String, title: impl ToString) -> Self {
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
Self { Self {
base_data: BaseData::new(ns), base_data: BaseData::with_ns(ns),
title: title.to_string(), title: title.to_string(),
description: None, description: None,
start_time: now, start_time: now,
@@ -71,8 +71,9 @@ impl Event {
/// Create an event with specific ID /// Create an event with specific ID
pub fn with_id(id: String, ns: String, title: impl ToString) -> Self { pub fn with_id(id: String, ns: String, title: impl ToString) -> Self {
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
let id_u32 = id.parse::<u32>().unwrap_or(0);
Self { Self {
base_data: BaseData::with_id(id, ns), base_data: BaseData::with_id(id_u32, ns),
title: title.to_string(), title: title.to_string(),
description: None, description: None,
start_time: now, start_time: now,

View File

@@ -0,0 +1,241 @@
/// Flow Instance
///
/// Represents an active instance of a flow template for a specific entity (e.g., user).
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
/// Status of a step in a flow instance
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum StepStatus {
#[default]
Pending,
Active,
Completed,
Skipped,
Failed,
}
/// A step instance in a flow
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StepInstance {
/// Step name (from template)
pub name: String,
/// Current status
pub status: StepStatus,
/// When step was started
pub started_at: Option<u64>,
/// When step was completed
pub completed_at: Option<u64>,
/// Step result data
#[serde(default)]
pub result: std::collections::HashMap<String, String>,
/// Error message if failed
pub error: Option<String>,
}
impl StepInstance {
pub fn new(name: String) -> Self {
Self {
name,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
result: std::collections::HashMap::new(),
error: None,
}
}
}
/// Overall flow status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum FlowStatus {
#[default]
Created,
Running,
Completed,
Failed,
Cancelled,
}
/// Flow Instance - an active execution of a flow template
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct FlowInstance {
#[serde(flatten)]
pub base_data: BaseData,
/// Instance name (typically entity_id or unique identifier)
pub name: String,
/// Template name this instance is based on
pub template_name: String,
/// Entity ID this flow is for (e.g., user_id)
pub entity_id: String,
/// Current flow status
pub status: FlowStatus,
/// Step instances
pub steps: Vec<StepInstance>,
/// Current step index
pub current_step: usize,
/// When flow was started
pub started_at: Option<u64>,
/// When flow was completed
pub completed_at: Option<u64>,
/// Instance metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
impl FlowInstance {
/// Create a new flow instance
pub fn new(id: u32, name: String, template_name: String, entity_id: String) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
name,
template_name,
entity_id,
status: FlowStatus::Created,
steps: Vec::new(),
current_step: 0,
started_at: None,
completed_at: None,
metadata: std::collections::HashMap::new(),
}
}
/// Initialize steps from template
pub fn init_steps(&mut self, step_names: Vec<String>) {
self.steps = step_names.into_iter().map(StepInstance::new).collect();
self.base_data.update_modified();
}
/// Start the flow
pub fn start(&mut self) {
// Initialize default steps if none exist
if self.steps.is_empty() {
// Create default steps based on common workflow
self.steps = vec![
StepInstance::new("registration".to_string()),
StepInstance::new("kyc".to_string()),
StepInstance::new("email".to_string()),
];
}
self.status = FlowStatus::Running;
self.started_at = Some(Self::now());
// Start first step if exists
if let Some(step) = self.steps.first_mut() {
step.status = StepStatus::Active;
step.started_at = Some(Self::now());
}
self.base_data.update_modified();
}
/// Complete a step by name
pub fn complete_step(&mut self, step_name: &str) -> Result<(), String> {
let step_idx = self.steps.iter().position(|s| s.name == step_name)
.ok_or_else(|| format!("Step '{}' not found", step_name))?;
let step = &mut self.steps[step_idx];
step.status = StepStatus::Completed;
step.completed_at = Some(Self::now());
// Move to next step if this was the current step
if step_idx == self.current_step {
self.current_step += 1;
// Start next step if exists
if let Some(next_step) = self.steps.get_mut(self.current_step) {
next_step.status = StepStatus::Active;
next_step.started_at = Some(Self::now());
} else {
// All steps completed
self.status = FlowStatus::Completed;
self.completed_at = Some(Self::now());
}
}
self.base_data.update_modified();
Ok(())
}
/// Fail a step
pub fn fail_step(&mut self, step_name: &str, error: String) -> Result<(), String> {
let step = self.steps.iter_mut()
.find(|s| s.name == step_name)
.ok_or_else(|| format!("Step '{}' not found", step_name))?;
step.status = StepStatus::Failed;
step.error = Some(error);
step.completed_at = Some(Self::now());
self.status = FlowStatus::Failed;
self.base_data.update_modified();
Ok(())
}
/// Skip a step
pub fn skip_step(&mut self, step_name: &str) -> Result<(), String> {
let step = self.steps.iter_mut()
.find(|s| s.name == step_name)
.ok_or_else(|| format!("Step '{}' not found", step_name))?;
step.status = StepStatus::Skipped;
step.completed_at = Some(Self::now());
self.base_data.update_modified();
Ok(())
}
/// Get current step
pub fn get_current_step(&self) -> Option<&StepInstance> {
self.steps.get(self.current_step)
}
/// Get step by name
pub fn get_step(&self, name: &str) -> Option<&StepInstance> {
self.steps.iter().find(|s| s.name == name)
}
/// Set step result data
pub fn set_step_result(&mut self, step_name: &str, key: String, value: String) -> Result<(), String> {
let step = self.steps.iter_mut()
.find(|s| s.name == step_name)
.ok_or_else(|| format!("Step '{}' not found", step_name))?;
step.result.insert(key, value);
self.base_data.update_modified();
Ok(())
}
/// Add metadata
pub fn add_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
self.base_data.update_modified();
}
/// Helper to get current timestamp
fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
}

10
src/objects/flow/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
/// Flow Module
///
/// Provides workflow/flow management with templates and instances.
pub mod template;
pub mod instance;
pub mod rhai;
pub use template::{FlowTemplate, FlowStep};
pub use instance::{FlowInstance, FlowStatus, StepStatus, StepInstance};

183
src/objects/flow/rhai.rs Normal file
View File

@@ -0,0 +1,183 @@
/// Rhai bindings for Flow objects
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::template::{FlowTemplate, FlowStep};
use super::instance::{FlowInstance, FlowStatus, StepStatus};
// ============================================================================
// Flow Template Module
// ============================================================================
type RhaiFlowTemplate = FlowTemplate;
#[export_module]
mod rhai_flow_template_module {
use super::RhaiFlowTemplate;
#[rhai_fn(name = "new_flow", return_raw)]
pub fn new_flow() -> Result<RhaiFlowTemplate, Box<EvalAltResult>> {
Ok(FlowTemplate::new(0))
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(
template: &mut RhaiFlowTemplate,
name: String,
) -> Result<RhaiFlowTemplate, Box<EvalAltResult>> {
let owned = std::mem::take(template);
*template = owned.name(name);
Ok(template.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
template: &mut RhaiFlowTemplate,
description: String,
) -> Result<RhaiFlowTemplate, Box<EvalAltResult>> {
let owned = std::mem::take(template);
*template = owned.description(description);
Ok(template.clone())
}
#[rhai_fn(name = "add_step", return_raw)]
pub fn add_step(
template: &mut RhaiFlowTemplate,
name: String,
description: String,
) -> Result<(), Box<EvalAltResult>> {
template.add_step(name, description);
Ok(())
}
#[rhai_fn(name = "build", return_raw)]
pub fn build(
template: &mut RhaiFlowTemplate,
) -> Result<RhaiFlowTemplate, Box<EvalAltResult>> {
Ok(template.clone())
}
// Getters
#[rhai_fn(name = "get_name")]
pub fn get_name(template: &mut RhaiFlowTemplate) -> String {
template.name.clone()
}
#[rhai_fn(name = "get_description")]
pub fn get_description(template: &mut RhaiFlowTemplate) -> String {
template.description.clone()
}
}
// ============================================================================
// Flow Instance Module
// ============================================================================
type RhaiFlowInstance = FlowInstance;
#[export_module]
mod rhai_flow_instance_module {
use super::RhaiFlowInstance;
#[rhai_fn(name = "new_flow_instance", return_raw)]
pub fn new_instance(
name: String,
template_name: String,
entity_id: String,
) -> Result<RhaiFlowInstance, Box<EvalAltResult>> {
Ok(FlowInstance::new(0, name, template_name, entity_id))
}
#[rhai_fn(name = "start", return_raw)]
pub fn start(
instance: &mut RhaiFlowInstance,
) -> Result<(), Box<EvalAltResult>> {
instance.start();
Ok(())
}
#[rhai_fn(name = "complete_step", return_raw)]
pub fn complete_step(
instance: &mut RhaiFlowInstance,
step_name: String,
) -> Result<(), Box<EvalAltResult>> {
instance.complete_step(&step_name)
.map_err(|e| e.into())
}
#[rhai_fn(name = "fail_step", return_raw)]
pub fn fail_step(
instance: &mut RhaiFlowInstance,
step_name: String,
error: String,
) -> Result<(), Box<EvalAltResult>> {
instance.fail_step(&step_name, error)
.map_err(|e| e.into())
}
#[rhai_fn(name = "skip_step", return_raw)]
pub fn skip_step(
instance: &mut RhaiFlowInstance,
step_name: String,
) -> Result<(), Box<EvalAltResult>> {
instance.skip_step(&step_name)
.map_err(|e| e.into())
}
// Getters
#[rhai_fn(name = "get_name")]
pub fn get_name(instance: &mut RhaiFlowInstance) -> String {
instance.name.clone()
}
#[rhai_fn(name = "get_template_name")]
pub fn get_template_name(instance: &mut RhaiFlowInstance) -> String {
instance.template_name.clone()
}
#[rhai_fn(name = "get_entity_id")]
pub fn get_entity_id(instance: &mut RhaiFlowInstance) -> String {
instance.entity_id.clone()
}
#[rhai_fn(name = "get_status")]
pub fn get_status(instance: &mut RhaiFlowInstance) -> String {
format!("{:?}", instance.status)
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register Flow modules into a Rhai Module (for use in packages)
pub fn register_flow_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<FlowTemplate>("FlowTemplate");
parent_module.set_custom_type::<FlowInstance>("FlowInstance");
// Merge flow template functions
let template_module = exported_module!(rhai_flow_template_module);
parent_module.merge(&template_module);
// Merge flow instance functions
let instance_module = exported_module!(rhai_flow_instance_module);
parent_module.merge(&instance_module);
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for FlowTemplate {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("FlowTemplate");
}
}
impl CustomType for FlowInstance {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("FlowInstance");
}
}

View File

@@ -0,0 +1,117 @@
/// Flow Template
///
/// Defines a reusable workflow template with steps that can be instantiated multiple times.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
/// A step in a flow template
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FlowStep {
/// Step name/identifier
pub name: String,
/// Step description
pub description: String,
/// Steps that must be completed before this step can start
#[serde(default)]
pub dependencies: Vec<String>,
}
impl FlowStep {
pub fn new(name: String, description: String) -> Self {
Self {
name,
description,
dependencies: Vec::new(),
}
}
pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
self.dependencies = dependencies;
self
}
pub fn add_dependency(&mut self, dependency: String) {
self.dependencies.push(dependency);
}
}
/// Flow Template - defines a reusable workflow
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct FlowTemplate {
#[serde(flatten)]
pub base_data: BaseData,
/// Template name
pub name: String,
/// Template description
pub description: String,
/// Ordered list of steps
pub steps: Vec<FlowStep>,
/// Template metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
impl FlowTemplate {
/// Create a new flow template
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
name: String::new(),
description: String::new(),
steps: Vec::new(),
metadata: std::collections::HashMap::new(),
}
}
/// Builder: Set name
pub fn name(mut self, name: String) -> Self {
self.name = name;
self.base_data.update_modified();
self
}
/// Builder: Set description
pub fn description(mut self, description: String) -> Self {
self.description = description;
self.base_data.update_modified();
self
}
/// Add a step to the template
pub fn add_step(&mut self, name: String, description: String) {
self.steps.push(FlowStep::new(name, description));
self.base_data.update_modified();
}
/// Add a step with dependencies
pub fn add_step_with_dependencies(&mut self, name: String, description: String, dependencies: Vec<String>) {
let step = FlowStep::new(name, description).with_dependencies(dependencies);
self.steps.push(step);
self.base_data.update_modified();
}
/// Get step by name
pub fn get_step(&self, name: &str) -> Option<&FlowStep> {
self.steps.iter().find(|s| s.name == name)
}
/// Add metadata
pub fn add_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
self.base_data.update_modified();
}
/// Build (for fluent API compatibility)
pub fn build(self) -> Self {
self
}
}

126
src/objects/grid4/bid.rs Normal file
View File

@@ -0,0 +1,126 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Bid status enumeration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum BidStatus {
#[default]
Pending,
Confirmed,
Assigned,
Cancelled,
Done,
}
/// Billing period enumeration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum BillingPeriod {
#[default]
Hourly,
Monthly,
Yearly,
Biannually,
Triannually,
}
/// I can bid for infra, and optionally get accepted
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Bid {
pub base_data: BaseData,
/// links back to customer for this capacity (user on ledger)
#[index]
pub customer_id: u32,
/// nr of slices I need in 1 machine
pub compute_slices_nr: i32,
/// price per 1 GB slice I want to accept
pub compute_slice_price: f64,
/// nr of storage slices needed
pub storage_slices_nr: i32,
/// price per 1 GB storage slice I want to accept
pub storage_slice_price: f64,
pub status: BidStatus,
/// if obligation then will be charged and money needs to be in escrow, otherwise its an intent
pub obligation: bool,
/// epoch timestamp
pub start_date: u32,
/// epoch timestamp
pub end_date: u32,
/// signature as done by a user/consumer to validate their identity and intent
pub signature_user: String,
pub billing_period: BillingPeriod,
}
impl Bid {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
customer_id: 0,
compute_slices_nr: 0,
compute_slice_price: 0.0,
storage_slices_nr: 0,
storage_slice_price: 0.0,
status: BidStatus::default(),
obligation: false,
start_date: 0,
end_date: 0,
signature_user: String::new(),
billing_period: BillingPeriod::default(),
}
}
pub fn customer_id(mut self, v: u32) -> Self {
self.customer_id = v;
self
}
pub fn compute_slices_nr(mut self, v: i32) -> Self {
self.compute_slices_nr = v;
self
}
pub fn compute_slice_price(mut self, v: f64) -> Self {
self.compute_slice_price = v;
self
}
pub fn storage_slices_nr(mut self, v: i32) -> Self {
self.storage_slices_nr = v;
self
}
pub fn storage_slice_price(mut self, v: f64) -> Self {
self.storage_slice_price = v;
self
}
pub fn status(mut self, v: BidStatus) -> Self {
self.status = v;
self
}
pub fn obligation(mut self, v: bool) -> Self {
self.obligation = v;
self
}
pub fn start_date(mut self, v: u32) -> Self {
self.start_date = v;
self
}
pub fn end_date(mut self, v: u32) -> Self {
self.end_date = v;
self
}
pub fn signature_user(mut self, v: impl ToString) -> Self {
self.signature_user = v.to_string();
self
}
pub fn billing_period(mut self, v: BillingPeriod) -> Self {
self.billing_period = v;
self
}
}

View File

@@ -0,0 +1,39 @@
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// SLA policy matching the V spec `SLAPolicy`
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct SLAPolicy {
/// should +90
pub sla_uptime: i32,
/// minimal mbits we can expect avg over 1h per node, 0 means we don't guarantee
pub sla_bandwidth_mbit: i32,
/// 0-100, percent of money given back in relation to month if sla breached,
/// e.g. 200 means we return 2 months worth of rev if sla missed
pub sla_penalty: i32,
}
impl SLAPolicy {
pub fn new() -> Self { Self::default() }
pub fn sla_uptime(mut self, v: i32) -> Self { self.sla_uptime = v; self }
pub fn sla_bandwidth_mbit(mut self, v: i32) -> Self { self.sla_bandwidth_mbit = v; self }
pub fn sla_penalty(mut self, v: i32) -> Self { self.sla_penalty = v; self }
pub fn build(self) -> Self { self }
}
/// Pricing policy matching the V spec `PricingPolicy`
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct PricingPolicy {
/// e.g. 30,40,50 means if user has more CC in wallet than 1 year utilization
/// then this provider gives 30%, 2Y 40%, ...
pub marketplace_year_discounts: Vec<i32>,
/// e.g. 10,20,30
pub volume_discounts: Vec<i32>,
}
impl PricingPolicy {
pub fn new() -> Self { Self { marketplace_year_discounts: vec![30, 40, 50], volume_discounts: vec![10, 20, 30] } }
pub fn marketplace_year_discounts(mut self, v: Vec<i32>) -> Self { self.marketplace_year_discounts = v; self }
pub fn volume_discounts(mut self, v: Vec<i32>) -> Self { self.volume_discounts = v; self }
pub fn build(self) -> Self { self }
}

View File

@@ -0,0 +1,217 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::bid::BillingPeriod;
/// Contract status enumeration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum ContractStatus {
#[default]
Active,
Cancelled,
Error,
Paused,
}
/// Compute slice provisioned for a contract
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ComputeSliceProvisioned {
pub node_id: u32,
/// the id of the slice in the node
pub id: u16,
pub mem_gb: f64,
pub storage_gb: f64,
pub passmark: i32,
pub vcores: i32,
pub cpu_oversubscription: i32,
pub tags: String,
}
/// Storage slice provisioned for a contract
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct StorageSliceProvisioned {
pub node_id: u32,
/// the id of the slice in the node, are tracked in the node itself
pub id: u16,
pub storage_size_gb: i32,
pub tags: String,
}
/// Contract for provisioned infrastructure
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Contract {
pub base_data: BaseData,
/// links back to customer for this capacity (user on ledger)
#[index]
pub customer_id: u32,
pub compute_slices: Vec<ComputeSliceProvisioned>,
pub storage_slices: Vec<StorageSliceProvisioned>,
/// price per 1 GB agreed upon
pub compute_slice_price: f64,
/// price per 1 GB agreed upon
pub storage_slice_price: f64,
/// price per 1 GB agreed upon (transfer)
pub network_slice_price: f64,
pub status: ContractStatus,
/// epoch timestamp
pub start_date: u32,
/// epoch timestamp
pub end_date: u32,
/// signature as done by a user/consumer to validate their identity and intent
pub signature_user: String,
/// signature as done by the hoster
pub signature_hoster: String,
pub billing_period: BillingPeriod,
}
impl Contract {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
customer_id: 0,
compute_slices: Vec::new(),
storage_slices: Vec::new(),
compute_slice_price: 0.0,
storage_slice_price: 0.0,
network_slice_price: 0.0,
status: ContractStatus::default(),
start_date: 0,
end_date: 0,
signature_user: String::new(),
signature_hoster: String::new(),
billing_period: BillingPeriod::default(),
}
}
pub fn customer_id(mut self, v: u32) -> Self {
self.customer_id = v;
self
}
pub fn add_compute_slice(mut self, slice: ComputeSliceProvisioned) -> Self {
self.compute_slices.push(slice);
self
}
pub fn add_storage_slice(mut self, slice: StorageSliceProvisioned) -> Self {
self.storage_slices.push(slice);
self
}
pub fn compute_slice_price(mut self, v: f64) -> Self {
self.compute_slice_price = v;
self
}
pub fn storage_slice_price(mut self, v: f64) -> Self {
self.storage_slice_price = v;
self
}
pub fn network_slice_price(mut self, v: f64) -> Self {
self.network_slice_price = v;
self
}
pub fn status(mut self, v: ContractStatus) -> Self {
self.status = v;
self
}
pub fn start_date(mut self, v: u32) -> Self {
self.start_date = v;
self
}
pub fn end_date(mut self, v: u32) -> Self {
self.end_date = v;
self
}
pub fn signature_user(mut self, v: impl ToString) -> Self {
self.signature_user = v.to_string();
self
}
pub fn signature_hoster(mut self, v: impl ToString) -> Self {
self.signature_hoster = v.to_string();
self
}
pub fn billing_period(mut self, v: BillingPeriod) -> Self {
self.billing_period = v;
self
}
}
impl ComputeSliceProvisioned {
pub fn new() -> Self {
Self::default()
}
pub fn node_id(mut self, v: u32) -> Self {
self.node_id = v;
self
}
pub fn id(mut self, v: u16) -> Self {
self.id = v;
self
}
pub fn mem_gb(mut self, v: f64) -> Self {
self.mem_gb = v;
self
}
pub fn storage_gb(mut self, v: f64) -> Self {
self.storage_gb = v;
self
}
pub fn passmark(mut self, v: i32) -> Self {
self.passmark = v;
self
}
pub fn vcores(mut self, v: i32) -> Self {
self.vcores = v;
self
}
pub fn cpu_oversubscription(mut self, v: i32) -> Self {
self.cpu_oversubscription = v;
self
}
pub fn tags(mut self, v: impl ToString) -> Self {
self.tags = v.to_string();
self
}
}
impl StorageSliceProvisioned {
pub fn new() -> Self {
Self::default()
}
pub fn node_id(mut self, v: u32) -> Self {
self.node_id = v;
self
}
pub fn id(mut self, v: u16) -> Self {
self.id = v;
self
}
pub fn storage_size_gb(mut self, v: i32) -> Self {
self.storage_size_gb = v;
self
}
pub fn tags(mut self, v: impl ToString) -> Self {
self.tags = v.to_string();
self
}
}

18
src/objects/grid4/mod.rs Normal file
View File

@@ -0,0 +1,18 @@
pub mod bid;
pub mod common;
pub mod contract;
pub mod node;
pub mod nodegroup;
pub mod reputation;
pub mod reservation;
pub use bid::{Bid, BidStatus, BillingPeriod};
pub use common::{PricingPolicy, SLAPolicy};
pub use contract::{Contract, ContractStatus, ComputeSliceProvisioned, StorageSliceProvisioned};
pub use node::{
CPUDevice, ComputeSlice, DeviceInfo, GPUDevice, MemoryDevice, NetworkDevice, Node,
NodeCapacity, StorageDevice, StorageSlice,
};
pub use nodegroup::NodeGroup;
pub use reputation::{NodeGroupReputation, NodeReputation};
pub use reservation::{Reservation, ReservationStatus};

279
src/objects/grid4/node.rs Normal file
View File

@@ -0,0 +1,279 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::common::{PricingPolicy, SLAPolicy};
/// Storage device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct StorageDevice {
/// can be used in node
pub id: String,
/// Size of the storage device in gigabytes
pub size_gb: f64,
/// Description of the storage device
pub description: String,
}
/// Memory device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct MemoryDevice {
/// can be used in node
pub id: String,
/// Size of the memory device in gigabytes
pub size_gb: f64,
/// Description of the memory device
pub description: String,
}
/// CPU device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct CPUDevice {
/// can be used in node
pub id: String,
/// Number of CPU cores
pub cores: i32,
/// Passmark score
pub passmark: i32,
/// Description of the CPU
pub description: String,
/// Brand of the CPU
pub cpu_brand: String,
/// Version of the CPU
pub cpu_version: String,
}
/// GPU device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GPUDevice {
/// can be used in node
pub id: String,
/// Number of GPU cores
pub cores: i32,
/// Size of the GPU memory in gigabytes
pub memory_gb: f64,
/// Description of the GPU
pub description: String,
pub gpu_brand: String,
pub gpu_version: String,
}
/// Network device information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct NetworkDevice {
/// can be used in node
pub id: String,
/// Network speed in Mbps
pub speed_mbps: i32,
/// Description of the network device
pub description: String,
}
/// Aggregated device info for a node
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DeviceInfo {
pub vendor: String,
pub storage: Vec<StorageDevice>,
pub memory: Vec<MemoryDevice>,
pub cpu: Vec<CPUDevice>,
pub gpu: Vec<GPUDevice>,
pub network: Vec<NetworkDevice>,
}
/// NodeCapacity represents the hardware capacity details of a node.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct NodeCapacity {
/// Total storage in gigabytes
pub storage_gb: f64,
/// Total memory in gigabytes
pub mem_gb: f64,
/// Total GPU memory in gigabytes
pub mem_gb_gpu: f64,
/// Passmark score for the node
pub passmark: i32,
/// Total virtual cores
pub vcores: i32,
}
// PricingPolicy and SLAPolicy moved to `common.rs` to be shared across models.
/// Compute slice (typically represents a base unit of compute)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ComputeSlice {
/// the id of the slice in the node
pub id: u16,
pub mem_gb: f64,
pub storage_gb: f64,
pub passmark: i32,
pub vcores: i32,
pub cpu_oversubscription: i32,
pub storage_oversubscription: i32,
/// nr of GPU's see node to know what GPU's are
pub gpus: u8,
}
impl ComputeSlice {
pub fn new() -> Self {
Self {
id: 0,
mem_gb: 0.0,
storage_gb: 0.0,
passmark: 0,
vcores: 0,
cpu_oversubscription: 0,
storage_oversubscription: 0,
gpus: 0,
}
}
pub fn id(mut self, id: u16) -> Self {
self.id = id;
self
}
pub fn mem_gb(mut self, v: f64) -> Self {
self.mem_gb = v;
self
}
pub fn storage_gb(mut self, v: f64) -> Self {
self.storage_gb = v;
self
}
pub fn passmark(mut self, v: i32) -> Self {
self.passmark = v;
self
}
pub fn vcores(mut self, v: i32) -> Self {
self.vcores = v;
self
}
pub fn cpu_oversubscription(mut self, v: i32) -> Self {
self.cpu_oversubscription = v;
self
}
pub fn storage_oversubscription(mut self, v: i32) -> Self {
self.storage_oversubscription = v;
self
}
pub fn gpus(mut self, v: u8) -> Self {
self.gpus = v;
self
}
}
/// Storage slice (typically 1GB of storage)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct StorageSlice {
/// the id of the slice in the node, are tracked in the node itself
pub id: u16,
}
impl StorageSlice {
pub fn new() -> Self {
Self {
id: 0,
}
}
pub fn id(mut self, id: u16) -> Self {
self.id = id;
self
}
}
/// Grid4 Node model
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Node {
pub base_data: BaseData,
/// Link to node group
#[index]
pub nodegroupid: i32,
/// Uptime percentage 0..100
pub uptime: i32,
pub computeslices: Vec<ComputeSlice>,
pub storageslices: Vec<StorageSlice>,
pub devices: DeviceInfo,
/// 2 letter code as specified in lib/data/countries/data/countryInfo.txt
#[index]
pub country: String,
/// Hardware capacity details
pub capacity: NodeCapacity,
/// first time node was active
pub birthtime: u32,
/// node public key
#[index]
pub pubkey: String,
/// signature done on node to validate pubkey with privkey
pub signature_node: String,
/// signature as done by farmers to validate their identity
pub signature_farmer: String,
}
impl Node {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
nodegroupid: 0,
uptime: 0,
computeslices: Vec::new(),
storageslices: Vec::new(),
devices: DeviceInfo::default(),
country: String::new(),
capacity: NodeCapacity::default(),
birthtime: 0,
pubkey: String::new(),
signature_node: String::new(),
signature_farmer: String::new(),
}
}
pub fn nodegroupid(mut self, v: i32) -> Self {
self.nodegroupid = v;
self
}
pub fn uptime(mut self, v: i32) -> Self {
self.uptime = v;
self
}
pub fn add_compute_slice(mut self, s: ComputeSlice) -> Self {
self.computeslices.push(s);
self
}
pub fn add_storage_slice(mut self, s: StorageSlice) -> Self {
self.storageslices.push(s);
self
}
pub fn devices(mut self, d: DeviceInfo) -> Self {
self.devices = d;
self
}
pub fn country(mut self, c: impl ToString) -> Self {
self.country = c.to_string();
self
}
pub fn capacity(mut self, c: NodeCapacity) -> Self {
self.capacity = c;
self
}
pub fn birthtime(mut self, t: u32) -> Self {
self.birthtime = t;
self
}
pub fn pubkey(mut self, v: impl ToString) -> Self {
self.pubkey = v.to_string();
self
}
pub fn signature_node(mut self, v: impl ToString) -> Self {
self.signature_node = v.to_string();
self
}
pub fn signature_farmer(mut self, v: impl ToString) -> Self {
self.signature_farmer = v.to_string();
self
}
/// Placeholder for capacity recalculation out of the devices on the Node
pub fn check(self) -> Self {
// TODO: calculate NodeCapacity out of the devices on the Node
self
}
}

View File

@@ -0,0 +1,50 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
use super::common::{PricingPolicy, SLAPolicy};
/// Grid4 NodeGroup model (root object for farmer configuration)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct NodeGroup {
pub base_data: BaseData,
/// link back to farmer who owns the nodegroup, is a user?
#[index]
pub farmerid: u32,
/// only visible by farmer, in future encrypted, used to boot a node
pub secret: String,
pub description: String,
pub slapolicy: SLAPolicy,
pub pricingpolicy: PricingPolicy,
/// pricing in CC - cloud credit, per 2GB node slice
pub compute_slice_normalized_pricing_cc: f64,
/// pricing in CC - cloud credit, per 1GB storage slice
pub storage_slice_normalized_pricing_cc: f64,
/// signature as done by farmers to validate that they created this group
pub signature_farmer: String,
}
impl NodeGroup {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
farmerid: 0,
secret: String::new(),
description: String::new(),
slapolicy: SLAPolicy::default(),
pricingpolicy: PricingPolicy::new(),
compute_slice_normalized_pricing_cc: 0.0,
storage_slice_normalized_pricing_cc: 0.0,
signature_farmer: String::new(),
}
}
pub fn farmerid(mut self, v: u32) -> Self { self.farmerid = v; self }
pub fn secret(mut self, v: impl ToString) -> Self { self.secret = v.to_string(); self }
pub fn description(mut self, v: impl ToString) -> Self { self.description = v.to_string(); self }
pub fn slapolicy(mut self, v: SLAPolicy) -> Self { self.slapolicy = v; self }
pub fn pricingpolicy(mut self, v: PricingPolicy) -> Self { self.pricingpolicy = v; self }
pub fn compute_slice_normalized_pricing_cc(mut self, v: f64) -> Self { self.compute_slice_normalized_pricing_cc = v; self }
pub fn storage_slice_normalized_pricing_cc(mut self, v: f64) -> Self { self.storage_slice_normalized_pricing_cc = v; self }
pub fn signature_farmer(mut self, v: impl ToString) -> Self { self.signature_farmer = v.to_string(); self }
}

View File

@@ -0,0 +1,83 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Node reputation information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct NodeReputation {
pub node_id: u32,
/// between 0 and 100, earned over time
pub reputation: i32,
/// between 0 and 100, set by system, farmer has no ability to set this
pub uptime: i32,
}
/// NodeGroup reputation model
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct NodeGroupReputation {
pub base_data: BaseData,
#[index]
pub nodegroup_id: u32,
/// between 0 and 100, earned over time
pub reputation: i32,
/// between 0 and 100, set by system, farmer has no ability to set this
pub uptime: i32,
pub nodes: Vec<NodeReputation>,
}
impl NodeGroupReputation {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
nodegroup_id: 0,
reputation: 50, // default as per spec
uptime: 0,
nodes: Vec::new(),
}
}
pub fn nodegroup_id(mut self, v: u32) -> Self {
self.nodegroup_id = v;
self
}
pub fn reputation(mut self, v: i32) -> Self {
self.reputation = v;
self
}
pub fn uptime(mut self, v: i32) -> Self {
self.uptime = v;
self
}
pub fn add_node_reputation(mut self, node_rep: NodeReputation) -> Self {
self.nodes.push(node_rep);
self
}
}
impl NodeReputation {
pub fn new() -> Self {
Self {
node_id: 0,
reputation: 50, // default as per spec
uptime: 0,
}
}
pub fn node_id(mut self, v: u32) -> Self {
self.node_id = v;
self
}
pub fn reputation(mut self, v: i32) -> Self {
self.reputation = v;
self
}
pub fn uptime(mut self, v: i32) -> Self {
self.uptime = v;
self
}
}

View File

@@ -0,0 +1,56 @@
use crate::store::BaseData;
use rhai::{CustomType, TypeBuilder};
use serde::{Deserialize, Serialize};
/// Reservation status as per V spec
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum ReservationStatus {
#[default]
Pending,
Confirmed,
Assigned,
Cancelled,
Done,
}
/// Grid4 Reservation model
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Reservation {
pub base_data: BaseData,
/// links back to customer for this capacity
#[index]
pub customer_id: u32,
pub compute_slices: Vec<u32>,
pub storage_slices: Vec<u32>,
pub status: ReservationStatus,
/// if obligation then will be charged and money needs to be in escrow, otherwise its an intent
pub obligation: bool,
/// epoch
pub start_date: u32,
pub end_date: u32,
}
impl Reservation {
pub fn new() -> Self {
Self {
base_data: BaseData::new(),
customer_id: 0,
compute_slices: Vec::new(),
storage_slices: Vec::new(),
status: ReservationStatus::Pending,
obligation: false,
start_date: 0,
end_date: 0,
}
}
pub fn customer_id(mut self, v: u32) -> Self { self.customer_id = v; self }
pub fn add_compute_slice(mut self, id: u32) -> Self { self.compute_slices.push(id); self }
pub fn compute_slices(mut self, v: Vec<u32>) -> Self { self.compute_slices = v; self }
pub fn add_storage_slice(mut self, id: u32) -> Self { self.storage_slices.push(id); self }
pub fn storage_slices(mut self, v: Vec<u32>) -> Self { self.storage_slices = v; self }
pub fn status(mut self, v: ReservationStatus) -> Self { self.status = v; self }
pub fn obligation(mut self, v: bool) -> Self { self.obligation = v; self }
pub fn start_date(mut self, v: u32) -> Self { self.start_date = v; self }
pub fn end_date(mut self, v: u32) -> Self { self.end_date = v; self }
}

View File

@@ -0,0 +1,194 @@
# Grid4 Data Model
This module defines data models for nodes, groups, and slices in a cloud/grid infrastructure. Each root object is marked with `@[heap]` and can be indexed for efficient querying.
## Root Objects Overview
| Object | Description | Index Fields |
| ----------- | --------------------------------------------- | ------------------------------ |
| `Node` | Represents a single node in the grid | `id`, `nodegroupid`, `country` |
| `NodeGroup` | Represents a group of nodes owned by a farmer | `id`, `farmerid` |
---
## Node
Represents a single node in the grid with slices, devices, and capacity.
| Field | Type | Description | Indexed |
| --------------- | ---------------- | -------------------------------------------- | ------- |
| `id` | `int` | Unique node ID | ✅ |
| `nodegroupid` | `int` | ID of the owning node group | ✅ |
| `uptime` | `int` | Uptime percentage (0-100) | ✅ |
| `computeslices` | `[]ComputeSlice` | List of compute slices | ❌ |
| `storageslices` | `[]StorageSlice` | List of storage slices | ❌ |
| `devices` | `DeviceInfo` | Hardware device info (storage, memory, etc.) | ❌ |
| `country` | `string` | 2-letter country code | ✅ |
| `capacity` | `NodeCapacity` | Aggregated hardware capacity | ❌ |
| `provisiontime` | `u32` | Provisioning time (simple/compatible format) | ✅ |
---
## NodeGroup
Represents a group of nodes owned by a farmer, with policies.
| Field | Type | Description | Indexed |
| ------------------------------------- | --------------- | ---------------------------------------------- | ------- |
| `id` | `u32` | Unique group ID | ✅ |
| `farmerid` | `u32` | Farmer/user ID | ✅ |
| `secret` | `string` | Encrypted secret for booting nodes | ❌ |
| `description` | `string` | Group description | ❌ |
| `slapolicy` | `SLAPolicy` | SLA policy details | ❌ |
| `pricingpolicy` | `PricingPolicy` | Pricing policy details | ❌ |
| `compute_slice_normalized_pricing_cc` | `f64` | Pricing per 2GB compute slice in cloud credits | ❌ |
| `storage_slice_normalized_pricing_cc` | `f64` | Pricing per 1GB storage slice in cloud credits | ❌ |
| `reputation` | `int` | Reputation (0-100) | ✅ |
| `uptime` | `int` | Uptime (0-100) | ✅ |
---
## ComputeSlice
Represents a compute slice (e.g., 1GB memory unit).
| Field | Type | Description |
| -------------------------- | --------------- | -------------------------------- |
| `nodeid` | `u32` | Owning node ID |
| `id` | `int` | Slice ID in node |
| `mem_gb` | `f64` | Memory in GB |
| `storage_gb` | `f64` | Storage in GB |
| `passmark` | `int` | Passmark score |
| `vcores` | `int` | Virtual cores |
| `cpu_oversubscription` | `int` | CPU oversubscription ratio |
| `storage_oversubscription` | `int` | Storage oversubscription ratio |
| `price_range` | `[]f64` | Price range [min, max] |
| `gpus` | `u8` | Number of GPUs |
| `price_cc` | `f64` | Price per slice in cloud credits |
| `pricing_policy` | `PricingPolicy` | Pricing policy |
| `sla_policy` | `SLAPolicy` | SLA policy |
---
## StorageSlice
Represents a 1GB storage slice.
| Field | Type | Description |
| ---------------- | --------------- | -------------------------------- |
| `nodeid` | `u32` | Owning node ID |
| `id` | `int` | Slice ID in node |
| `price_cc` | `f64` | Price per slice in cloud credits |
| `pricing_policy` | `PricingPolicy` | Pricing policy |
| `sla_policy` | `SLAPolicy` | SLA policy |
---
## DeviceInfo
Hardware device information for a node.
| Field | Type | Description |
| --------- | ----------------- | ----------------------- |
| `vendor` | `string` | Vendor of the node |
| `storage` | `[]StorageDevice` | List of storage devices |
| `memory` | `[]MemoryDevice` | List of memory devices |
| `cpu` | `[]CPUDevice` | List of CPU devices |
| `gpu` | `[]GPUDevice` | List of GPU devices |
| `network` | `[]NetworkDevice` | List of network devices |
---
## StorageDevice
| Field | Type | Description |
| ------------- | -------- | --------------------- |
| `id` | `string` | Unique ID for device |
| `size_gb` | `f64` | Size in GB |
| `description` | `string` | Description of device |
---
## MemoryDevice
| Field | Type | Description |
| ------------- | -------- | --------------------- |
| `id` | `string` | Unique ID for device |
| `size_gb` | `f64` | Size in GB |
| `description` | `string` | Description of device |
---
## CPUDevice
| Field | Type | Description |
| ------------- | -------- | ------------------------ |
| `id` | `string` | Unique ID for device |
| `cores` | `int` | Number of CPU cores |
| `passmark` | `int` | Passmark benchmark score |
| `description` | `string` | Description of device |
| `cpu_brand` | `string` | Brand of the CPU |
| `cpu_version` | `string` | Version of the CPU |
---
## GPUDevice
| Field | Type | Description |
| ------------- | -------- | --------------------- |
| `id` | `string` | Unique ID for device |
| `cores` | `int` | Number of GPU cores |
| `memory_gb` | `f64` | GPU memory in GB |
| `description` | `string` | Description of device |
| `gpu_brand` | `string` | Brand of the GPU |
| `gpu_version` | `string` | Version of the GPU |
---
## NetworkDevice
| Field | Type | Description |
| ------------- | -------- | --------------------- |
| `id` | `string` | Unique ID for device |
| `speed_mbps` | `int` | Network speed in Mbps |
| `description` | `string` | Description of device |
---
## NodeCapacity
Aggregated hardware capacity for a node.
| Field | Type | Description |
| ------------ | ----- | ---------------------- |
| `storage_gb` | `f64` | Total storage in GB |
| `mem_gb` | `f64` | Total memory in GB |
| `mem_gb_gpu` | `f64` | Total GPU memory in GB |
| `passmark` | `int` | Total passmark score |
| `vcores` | `int` | Total virtual cores |
---
## SLAPolicy
Service Level Agreement policy for slices or node groups.
| Field | Type | Description |
| -------------------- | ----- | --------------------------------------- |
| `sla_uptime` | `int` | Required uptime % (e.g., 90) |
| `sla_bandwidth_mbit` | `int` | Guaranteed bandwidth in Mbps (0 = none) |
| `sla_penalty` | `int` | Penalty % if SLA is breached (0-100) |
---
## PricingPolicy
Pricing policy for slices or node groups.
| Field | Type | Description |
| ---------------------------- | ------- | --------------------------------------------------------- |
| `marketplace_year_discounts` | `[]int` | Discounts for 1Y, 2Y, 3Y prepaid usage (e.g. [30,40,50]) |
| `volume_discounts` | `[]int` | Volume discounts based on purchase size (e.g. [10,20,30]) |

View File

@@ -0,0 +1,37 @@
module datamodel
// I can bid for infra, and optionally get accepted
@[heap]
pub struct Bid {
pub mut:
id u32
customer_id u32 // links back to customer for this capacity (user on ledger)
compute_slices_nr int // nr of slices I need in 1 machine
compute_slice_price f64 // price per 1 GB slice I want to accept
storage_slices_nr int
storage_slice_price f64 // price per 1 GB storage slice I want to accept
storage_slices_nr int
status BidStatus
obligation bool // if obligation then will be charged and money needs to be in escrow, otherwise its an intent
start_date u32 // epoch
end_date u32
signature_user string // signature as done by a user/consumer to validate their identity and intent
billing_period BillingPeriod
}
pub enum BidStatus {
pending
confirmed
assigned
cancelled
done
}
pub enum BillingPeriod {
hourly
monthly
yearly
biannually
triannually
}

View File

@@ -0,0 +1,52 @@
module datamodel
// I can bid for infra, and optionally get accepted
@[heap]
pub struct Contract {
pub mut:
id u32
customer_id u32 // links back to customer for this capacity (user on ledger)
compute_slices []ComputeSliceProvisioned
storage_slices []StorageSliceProvisioned
compute_slice_price f64 // price per 1 GB agreed upon
storage_slice_price f64 // price per 1 GB agreed upon
network_slice_price f64 // price per 1 GB agreed upon (transfer)
status ContractStatus
start_date u32 // epoch
end_date u32
signature_user string // signature as done by a user/consumer to validate their identity and intent
signature_hoster string // signature as done by the hoster
billing_period BillingPeriod
}
pub enum ConctractStatus {
active
cancelled
error
paused
}
// typically 1GB of memory, but can be adjusted based based on size of machine
pub struct ComputeSliceProvisioned {
pub mut:
node_id u32
id u16 // the id of the slice in the node
mem_gb f64
storage_gb f64
passmark int
vcores int
cpu_oversubscription int
tags string
}
// 1GB of storage
pub struct StorageSliceProvisioned {
pub mut:
node_id u32
id u16 // the id of the slice in the node, are tracked in the node itself
storage_size_gb int
tags string
}

View File

@@ -0,0 +1,104 @@
module datamodel
//ACCESS ONLY TF
@[heap]
pub struct Node {
pub mut:
id int
nodegroupid int
uptime int // 0..100
computeslices []ComputeSlice
storageslices []StorageSlice
devices DeviceInfo
country string // 2 letter code as specified in lib/data/countries/data/countryInfo.txt, use that library for validation
capacity NodeCapacity // Hardware capacity details
birthtime u32 // first time node was active
pubkey string
signature_node string // signature done on node to validate pubkey with privkey
signature_farmer string // signature as done by farmers to validate their identity
}
pub struct DeviceInfo {
pub mut:
vendor string
storage []StorageDevice
memory []MemoryDevice
cpu []CPUDevice
gpu []GPUDevice
network []NetworkDevice
}
pub struct StorageDevice {
pub mut:
id string // can be used in node
size_gb f64 // Size of the storage device in gigabytes
description string // Description of the storage device
}
pub struct MemoryDevice {
pub mut:
id string // can be used in node
size_gb f64 // Size of the memory device in gigabytes
description string // Description of the memory device
}
pub struct CPUDevice {
pub mut:
id string // can be used in node
cores int // Number of CPU cores
passmark int
description string // Description of the CPU
cpu_brand string // Brand of the CPU
cpu_version string // Version of the CPU
}
pub struct GPUDevice {
pub mut:
id string // can be used in node
cores int // Number of GPU cores
memory_gb f64 // Size of the GPU memory in gigabytes
description string // Description of the GPU
gpu_brand string
gpu_version string
}
pub struct NetworkDevice {
pub mut:
id string // can be used in node
speed_mbps int // Network speed in Mbps
description string // Description of the network device
}
// NodeCapacity represents the hardware capacity details of a node.
pub struct NodeCapacity {
pub mut:
storage_gb f64 // Total storage in gigabytes
mem_gb f64 // Total memory in gigabytes
mem_gb_gpu f64 // Total GPU memory in gigabytes
passmark int // Passmark score for the node
vcores int // Total virtual cores
}
// typically 1GB of memory, but can be adjusted based based on size of machine
pub struct ComputeSlice {
pub mut:
u16 int // the id of the slice in the node
mem_gb f64
storage_gb f64
passmark int
vcores int
cpu_oversubscription int
storage_oversubscription int
gpus u8 // nr of GPU's see node to know what GPU's are
}
// 1GB of storage
pub struct StorageSlice {
pub mut:
u16 int // the id of the slice in the node, are tracked in the node itself
}
fn (mut n Node) check() ! {
// todo calculate NodeCapacity out of the devices on the Node
}

View File

@@ -0,0 +1,33 @@
module datamodel
// is a root object, is the only obj farmer needs to configure in the UI, this defines how slices will be created
@[heap]
pub struct NodeGroup {
pub mut:
id u32
farmerid u32 // link back to farmer who owns the nodegroup, is a user?
secret string // only visible by farmer, in future encrypted, used to boot a node
description string
slapolicy SLAPolicy
pricingpolicy PricingPolicy
compute_slice_normalized_pricing_cc f64 // pricing in CC - cloud credit, per 2GB node slice
storage_slice_normalized_pricing_cc f64 // pricing in CC - cloud credit, per 1GB storage slice
signature_farmer string // signature as done by farmers to validate that they created this group
}
pub struct SLAPolicy {
pub mut:
sla_uptime int // should +90
sla_bandwidth_mbit int // minimal mbits we can expect avg over 1h per node, 0 means we don't guarantee
sla_penalty int // 0-100, percent of money given back in relation to month if sla breached, e.g. 200 means we return 2 months worth of rev if sla missed
}
pub struct PricingPolicy {
pub mut:
marketplace_year_discounts []int = [30, 40, 50] // e.g. 30,40,50 means if user has more CC in wallet than 1 year utilization on all his purchaes then this provider gives 30%, 2Y 40%, ...
// volume_discounts []int = [10, 20, 30] // e.g. 10,20,30
}

View File

@@ -0,0 +1,19 @@
@[heap]
pub struct NodeGroupReputation {
pub mut:
nodegroup_id u32
reputation int = 50 // between 0 and 100, earned over time
uptime int // between 0 and 100, set by system, farmer has no ability to set this
nodes []NodeReputation
}
pub struct NodeReputation {
pub mut:
node_id u32
reputation int = 50 // between 0 and 100, earned over time
uptime int // between 0 and 100, set by system, farmer has no ability to set this
}

View File

@@ -0,0 +1,311 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Defines the supported DNS record types
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NameType {
A,
AAAA,
CNAME,
MX,
TXT,
SRV,
PTR,
NS,
}
impl Default for NameType {
fn default() -> Self {
NameType::A
}
}
/// Category of the DNS record
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NameCat {
IPv4,
IPv6,
Mycelium,
}
impl Default for NameCat {
fn default() -> Self {
NameCat::IPv4
}
}
/// Status of a DNS zone
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DNSZoneStatus {
Active,
Suspended,
Archived,
}
impl Default for DNSZoneStatus {
fn default() -> Self {
DNSZoneStatus::Active
}
}
/// Represents a DNS record configuration
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DNSRecord {
pub subdomain: String,
pub record_type: NameType,
pub value: String,
pub priority: u32,
pub ttl: u32,
pub is_active: bool,
pub cat: NameCat,
pub is_wildcard: bool,
}
impl DNSRecord {
pub fn new() -> Self {
Self {
subdomain: String::new(),
record_type: NameType::default(),
value: String::new(),
priority: 0,
ttl: 3600,
is_active: true,
cat: NameCat::default(),
is_wildcard: false,
}
}
pub fn subdomain(mut self, subdomain: impl ToString) -> Self {
self.subdomain = subdomain.to_string();
self
}
pub fn record_type(mut self, record_type: NameType) -> Self {
self.record_type = record_type;
self
}
pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string();
self
}
pub fn priority(mut self, priority: u32) -> Self {
self.priority = priority;
self
}
pub fn ttl(mut self, ttl: u32) -> Self {
self.ttl = ttl;
self
}
pub fn is_active(mut self, is_active: bool) -> Self {
self.is_active = is_active;
self
}
pub fn cat(mut self, cat: NameCat) -> Self {
self.cat = cat;
self
}
pub fn is_wildcard(mut self, is_wildcard: bool) -> Self {
self.is_wildcard = is_wildcard;
self
}
pub fn build(self) -> Self {
self
}
}
impl std::fmt::Display for DNSRecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{:?}", self.subdomain, self.record_type)
}
}
/// SOA (Start of Authority) record for a DNS zone
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SOARecord {
pub zone_id: u32,
pub primary_ns: String,
pub admin_email: String,
pub serial: u64,
pub refresh: u32,
pub retry: u32,
pub expire: u32,
pub minimum_ttl: u32,
pub is_active: bool,
}
impl SOARecord {
pub fn new() -> Self {
Self {
zone_id: 0,
primary_ns: String::new(),
admin_email: String::new(),
serial: 0,
refresh: 3600,
retry: 600,
expire: 604800,
minimum_ttl: 3600,
is_active: true,
}
}
pub fn zone_id(mut self, zone_id: u32) -> Self {
self.zone_id = zone_id;
self
}
pub fn primary_ns(mut self, primary_ns: impl ToString) -> Self {
self.primary_ns = primary_ns.to_string();
self
}
pub fn admin_email(mut self, admin_email: impl ToString) -> Self {
self.admin_email = admin_email.to_string();
self
}
pub fn serial(mut self, serial: u64) -> Self {
self.serial = serial;
self
}
pub fn refresh(mut self, refresh: u32) -> Self {
self.refresh = refresh;
self
}
pub fn retry(mut self, retry: u32) -> Self {
self.retry = retry;
self
}
pub fn expire(mut self, expire: u32) -> Self {
self.expire = expire;
self
}
pub fn minimum_ttl(mut self, minimum_ttl: u32) -> Self {
self.minimum_ttl = minimum_ttl;
self
}
pub fn is_active(mut self, is_active: bool) -> Self {
self.is_active = is_active;
self
}
pub fn build(self) -> Self {
self
}
}
impl std::fmt::Display for SOARecord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.primary_ns)
}
}
/// Represents a DNS zone with its configuration and records
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct DNSZone {
/// Base model data
pub base_data: BaseData,
#[index]
pub domain: String,
#[index(path = "subdomain")]
#[index(path = "record_type")]
pub dnsrecords: Vec<DNSRecord>,
pub administrators: Vec<u32>,
pub status: DNSZoneStatus,
pub metadata: HashMap<String, String>,
#[index(path = "primary_ns")]
pub soarecord: Vec<SOARecord>,
}
impl DNSZone {
/// Create a new DNS zone instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
domain: String::new(),
dnsrecords: Vec::new(),
administrators: Vec::new(),
status: DNSZoneStatus::default(),
metadata: HashMap::new(),
soarecord: Vec::new(),
}
}
/// Set the domain name (fluent)
pub fn domain(mut self, domain: impl ToString) -> Self {
self.domain = domain.to_string();
self
}
/// Add a DNS record (fluent)
pub fn add_dnsrecord(mut self, record: DNSRecord) -> Self {
self.dnsrecords.push(record);
self
}
/// Set all DNS records (fluent)
pub fn dnsrecords(mut self, dnsrecords: Vec<DNSRecord>) -> Self {
self.dnsrecords = dnsrecords;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set the zone status (fluent)
pub fn status(mut self, status: DNSZoneStatus) -> Self {
self.status = status;
self
}
/// Add metadata entry (fluent)
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
/// Set all metadata (fluent)
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
/// Add an SOA record (fluent)
pub fn add_soarecord(mut self, soa: SOARecord) -> Self {
self.soarecord.push(soa);
self
}
/// Set all SOA records (fluent)
pub fn soarecord(mut self, soarecord: Vec<SOARecord>) -> Self {
self.soarecord = soarecord;
self
}
/// Build the final DNS zone instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,227 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
/// Defines the lifecycle of a group
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum GroupStatus {
Active,
Inactive,
Suspended,
Archived,
}
impl Default for GroupStatus {
fn default() -> Self {
GroupStatus::Active
}
}
/// Visibility controls who can discover or view the group
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Visibility {
Public, // Anyone can see and request to join
Private, // Only invited users can see the group
Unlisted, // Not visible in search; only accessible by direct link or DNS
}
impl Default for Visibility {
fn default() -> Self {
Visibility::Public
}
}
/// GroupConfig holds rules that govern group membership and behavior
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GroupConfig {
pub max_members: u32,
pub allow_guests: bool,
pub auto_approve: bool,
pub require_invite: bool,
}
impl GroupConfig {
pub fn new() -> Self {
Self {
max_members: 0,
allow_guests: false,
auto_approve: false,
require_invite: false,
}
}
pub fn max_members(mut self, max_members: u32) -> Self {
self.max_members = max_members;
self
}
pub fn allow_guests(mut self, allow_guests: bool) -> Self {
self.allow_guests = allow_guests;
self
}
pub fn auto_approve(mut self, auto_approve: bool) -> Self {
self.auto_approve = auto_approve;
self
}
pub fn require_invite(mut self, require_invite: bool) -> Self {
self.require_invite = require_invite;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents a collaborative or access-controlled unit within the system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Group {
/// Base model data
pub base_data: BaseData,
#[index]
pub name: String,
pub description: String,
pub dnsrecords: Vec<u32>,
pub administrators: Vec<u32>,
pub config: GroupConfig,
pub status: GroupStatus,
pub visibility: Visibility,
pub created: u64,
pub updated: u64,
}
impl Group {
/// Create a new group instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
name: String::new(),
description: String::new(),
dnsrecords: Vec::new(),
administrators: Vec::new(),
config: GroupConfig::new(),
status: GroupStatus::default(),
visibility: Visibility::default(),
created: 0,
updated: 0,
}
}
/// Set the group name (fluent)
pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
/// Set the group description (fluent)
pub fn description(mut self, description: impl ToString) -> Self {
self.description = description.to_string();
self
}
/// Add a DNS record ID (fluent)
pub fn add_dnsrecord(mut self, dnsrecord_id: u32) -> Self {
self.dnsrecords.push(dnsrecord_id);
self
}
/// Set all DNS record IDs (fluent)
pub fn dnsrecords(mut self, dnsrecords: Vec<u32>) -> Self {
self.dnsrecords = dnsrecords;
self
}
/// Add an administrator user ID (fluent)
pub fn add_administrator(mut self, user_id: u32) -> Self {
self.administrators.push(user_id);
self
}
/// Set all administrator user IDs (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set the group configuration (fluent)
pub fn config(mut self, config: GroupConfig) -> Self {
self.config = config;
self
}
/// Set the group status (fluent)
pub fn status(mut self, status: GroupStatus) -> Self {
self.status = status;
self
}
/// Set the group visibility (fluent)
pub fn visibility(mut self, visibility: Visibility) -> Self {
self.visibility = visibility;
self
}
/// Set the created timestamp (fluent)
pub fn created(mut self, created: u64) -> Self {
self.created = created;
self
}
/// Set the updated timestamp (fluent)
pub fn updated(mut self, updated: u64) -> Self {
self.updated = updated;
self
}
/// Build the final group instance
pub fn build(self) -> Self {
self
}
}
/// Represents the membership relationship between users and groups
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct UserGroupMembership {
/// Base model data
pub base_data: BaseData,
#[index]
pub user_id: u32,
pub group_ids: Vec<u32>,
}
impl UserGroupMembership {
/// Create a new user group membership instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
user_id: 0,
group_ids: Vec::new(),
}
}
/// Set the user ID (fluent)
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
/// Add a group ID (fluent)
pub fn add_group_id(mut self, group_id: u32) -> Self {
self.group_ids.push(group_id);
self
}
/// Set all group IDs (fluent)
pub fn group_ids(mut self, group_ids: Vec<u32>) -> Self {
self.group_ids = group_ids;
self
}
/// Build the final membership instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,110 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
/// Defines the possible roles a member can have
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MemberRole {
Owner,
Admin,
Moderator,
Member,
Guest,
}
impl Default for MemberRole {
fn default() -> Self {
MemberRole::Member
}
}
/// Represents the current status of membership
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum MemberStatus {
Active,
Pending,
Suspended,
Removed,
}
impl Default for MemberStatus {
fn default() -> Self {
MemberStatus::Pending
}
}
/// Represents a member within a circle
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Member {
/// Base model data
pub base_data: BaseData,
#[index]
pub user_id: u32,
pub role: MemberRole,
pub status: MemberStatus,
pub joined_at: u64,
pub invited_by: u32,
pub permissions: Vec<String>,
}
impl Member {
/// Create a new member instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
user_id: 0,
role: MemberRole::default(),
status: MemberStatus::default(),
joined_at: 0,
invited_by: 0,
permissions: Vec::new(),
}
}
/// Set the user ID (fluent)
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
/// Set the member role (fluent)
pub fn role(mut self, role: MemberRole) -> Self {
self.role = role;
self
}
/// Set the member status (fluent)
pub fn status(mut self, status: MemberStatus) -> Self {
self.status = status;
self
}
/// Set the joined timestamp (fluent)
pub fn joined_at(mut self, joined_at: u64) -> Self {
self.joined_at = joined_at;
self
}
/// Set who invited this member (fluent)
pub fn invited_by(mut self, invited_by: u32) -> Self {
self.invited_by = invited_by;
self
}
/// Add a permission (fluent)
pub fn add_permission(mut self, permission: impl ToString) -> Self {
self.permissions.push(permission.to_string());
self
}
/// Set all permissions (fluent)
pub fn permissions(mut self, permissions: Vec<String>) -> Self {
self.permissions = permissions;
self
}
/// Build the final member instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,10 @@
// Export all heroledger model modules
pub mod dnsrecord;
pub mod group;
pub mod membership;
pub mod money;
pub mod rhai;
pub mod secretbox;
pub mod signature;
pub mod user;
pub mod user_kvs;

View File

@@ -0,0 +1,498 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents the status of an account
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AccountStatus {
Active,
Inactive,
Suspended,
Archived,
}
impl Default for AccountStatus {
fn default() -> Self {
AccountStatus::Active
}
}
/// Represents the type of transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionType {
Transfer,
Clawback,
Freeze,
Unfreeze,
Issue,
Burn,
}
impl Default for TransactionType {
fn default() -> Self {
TransactionType::Transfer
}
}
/// Represents a signature for transactions
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Signature {
pub signer_id: u32,
pub signature: String,
pub timestamp: u64,
}
impl Signature {
pub fn new() -> Self {
Self {
signer_id: 0,
signature: String::new(),
timestamp: 0,
}
}
pub fn signer_id(mut self, signer_id: u32) -> Self {
self.signer_id = signer_id;
self
}
pub fn signature(mut self, signature: impl ToString) -> Self {
self.signature = signature.to_string();
self
}
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
pub fn build(self) -> Self {
self
}
}
/// Policy item for account operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct AccountPolicyItem {
pub signers: Vec<u32>,
pub min_signatures: u32,
pub enabled: bool,
pub threshold: f64,
pub recipient: u32,
}
impl AccountPolicyItem {
pub fn new() -> Self {
Self {
signers: Vec::new(),
min_signatures: 0,
enabled: false,
threshold: 0.0,
recipient: 0,
}
}
pub fn add_signer(mut self, signer_id: u32) -> Self {
self.signers.push(signer_id);
self
}
pub fn signers(mut self, signers: Vec<u32>) -> Self {
self.signers = signers;
self
}
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn threshold(mut self, threshold: f64) -> Self {
self.threshold = threshold;
self
}
pub fn recipient(mut self, recipient: u32) -> Self {
self.recipient = recipient;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents an account in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Account {
/// Base model data
pub base_data: BaseData,
pub owner_id: u32,
#[index]
pub address: String,
pub balance: f64,
pub currency: String,
pub assetid: u32,
pub last_activity: u64,
pub administrators: Vec<u32>,
pub accountpolicy: u32,
}
impl Account {
/// Create a new account instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
owner_id: 0,
address: String::new(),
balance: 0.0,
currency: String::new(),
assetid: 0,
last_activity: 0,
administrators: Vec::new(),
accountpolicy: 0,
}
}
/// Set the owner ID (fluent)
pub fn owner_id(mut self, owner_id: u32) -> Self {
self.owner_id = owner_id;
self
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the balance (fluent)
pub fn balance(mut self, balance: f64) -> Self {
self.balance = balance;
self
}
/// Set the currency (fluent)
pub fn currency(mut self, currency: impl ToString) -> Self {
self.currency = currency.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the last activity timestamp (fluent)
pub fn last_activity(mut self, last_activity: u64) -> Self {
self.last_activity = last_activity;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set the account policy ID (fluent)
pub fn accountpolicy(mut self, accountpolicy: u32) -> Self {
self.accountpolicy = accountpolicy;
self
}
/// Build the final account instance
pub fn build(self) -> Self {
self
}
}
/// Represents an asset in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Asset {
/// Base model data
pub base_data: BaseData,
#[index]
pub address: String,
pub assetid: u32,
pub asset_type: String,
pub issuer: u32,
pub supply: f64,
pub decimals: u8,
pub is_frozen: bool,
pub metadata: HashMap<String, String>,
pub administrators: Vec<u32>,
pub min_signatures: u32,
}
impl Asset {
/// Create a new asset instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
address: String::new(),
assetid: 0,
asset_type: String::new(),
issuer: 0,
supply: 0.0,
decimals: 0,
is_frozen: false,
metadata: HashMap::new(),
administrators: Vec::new(),
min_signatures: 0,
}
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the asset type (fluent)
pub fn asset_type(mut self, asset_type: impl ToString) -> Self {
self.asset_type = asset_type.to_string();
self
}
/// Set the issuer (fluent)
pub fn issuer(mut self, issuer: u32) -> Self {
self.issuer = issuer;
self
}
/// Set the supply (fluent)
pub fn supply(mut self, supply: f64) -> Self {
self.supply = supply;
self
}
/// Set the decimals (fluent)
pub fn decimals(mut self, decimals: u8) -> Self {
self.decimals = decimals;
self
}
/// Set the frozen status (fluent)
pub fn is_frozen(mut self, is_frozen: bool) -> Self {
self.is_frozen = is_frozen;
self
}
/// Add metadata entry (fluent)
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
/// Set all metadata (fluent)
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set minimum signatures required (fluent)
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
/// Build the final asset instance
pub fn build(self) -> Self {
self
}
}
/// Represents account policies for various operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct AccountPolicy {
/// Base model data
pub base_data: BaseData,
pub transferpolicy: AccountPolicyItem,
pub adminpolicy: AccountPolicyItem,
pub clawbackpolicy: AccountPolicyItem,
pub freezepolicy: AccountPolicyItem,
}
impl AccountPolicy {
/// Create a new account policy instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
transferpolicy: AccountPolicyItem::new(),
adminpolicy: AccountPolicyItem::new(),
clawbackpolicy: AccountPolicyItem::new(),
freezepolicy: AccountPolicyItem::new(),
}
}
/// Set the transfer policy (fluent)
pub fn transferpolicy(mut self, transferpolicy: AccountPolicyItem) -> Self {
self.transferpolicy = transferpolicy;
self
}
/// Set the admin policy (fluent)
pub fn adminpolicy(mut self, adminpolicy: AccountPolicyItem) -> Self {
self.adminpolicy = adminpolicy;
self
}
/// Set the clawback policy (fluent)
pub fn clawbackpolicy(mut self, clawbackpolicy: AccountPolicyItem) -> Self {
self.clawbackpolicy = clawbackpolicy;
self
}
/// Set the freeze policy (fluent)
pub fn freezepolicy(mut self, freezepolicy: AccountPolicyItem) -> Self {
self.freezepolicy = freezepolicy;
self
}
/// Build the final account policy instance
pub fn build(self) -> Self {
self
}
}
/// Represents a financial transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Transaction {
/// Base model data
pub base_data: BaseData,
pub txid: u32,
pub source: u32,
pub destination: u32,
pub assetid: u32,
pub amount: f64,
pub timestamp: u64,
pub status: String,
pub memo: String,
pub tx_type: TransactionType,
pub signatures: Vec<Signature>,
}
impl Transaction {
/// Create a new transaction instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
txid: 0,
source: 0,
destination: 0,
assetid: 0,
amount: 0.0,
timestamp: 0,
status: String::new(),
memo: String::new(),
tx_type: TransactionType::default(),
signatures: Vec::new(),
}
}
/// Set the transaction ID (fluent)
pub fn txid(mut self, txid: u32) -> Self {
self.txid = txid;
self
}
/// Set the source account (fluent)
pub fn source(mut self, source: u32) -> Self {
self.source = source;
self
}
/// Set the destination account (fluent)
pub fn destination(mut self, destination: u32) -> Self {
self.destination = destination;
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the amount (fluent)
pub fn amount(mut self, amount: f64) -> Self {
self.amount = amount;
self
}
/// Set the timestamp (fluent)
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Set the status (fluent)
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
/// Set the memo (fluent)
pub fn memo(mut self, memo: impl ToString) -> Self {
self.memo = memo.to_string();
self
}
/// Set the transaction type (fluent)
pub fn tx_type(mut self, tx_type: TransactionType) -> Self {
self.tx_type = tx_type;
self
}
/// Add a signature (fluent)
pub fn add_signature(mut self, signature: Signature) -> Self {
self.signatures.push(signature);
self
}
/// Set all signatures (fluent)
pub fn signatures(mut self, signatures: Vec<Signature>) -> Self {
self.signatures = signatures;
self
}
/// Build the final transaction instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,364 @@
use ::rhai::plugin::*;
use ::rhai::{Dynamic, Engine, EvalAltResult, Module, CustomType, TypeBuilder};
use std::mem;
use crate::objects::heroledger::{
dnsrecord::DNSZone,
group::{Group, Visibility},
money::Account,
user::{User, UserStatus},
};
// ============================================================================
// User Module
// ============================================================================
type RhaiUser = User;
#[export_module]
mod rhai_user_module {
use crate::objects::heroledger::user::User;
use super::RhaiUser;
#[rhai_fn(name = "new_user", return_raw)]
pub fn new_user() -> Result<RhaiUser, Box<EvalAltResult>> {
Ok(User::new(0))
}
#[rhai_fn(name = "username", return_raw)]
pub fn set_username(
user: &mut RhaiUser,
username: String,
) -> Result<RhaiUser, Box<EvalAltResult>> {
let owned = std::mem::take(user);
*user = owned.username(username);
Ok(user.clone())
}
#[rhai_fn(name = "add_email", return_raw)]
pub fn add_email(user: &mut RhaiUser, email: String) -> Result<RhaiUser, Box<EvalAltResult>> {
let owned = std::mem::take(user);
*user = owned.add_email(email);
Ok(user.clone())
}
#[rhai_fn(name = "pubkey", return_raw)]
pub fn set_pubkey(user: &mut RhaiUser, pubkey: String) -> Result<RhaiUser, Box<EvalAltResult>> {
let owned = std::mem::take(user);
*user = owned.pubkey(pubkey);
Ok(user.clone())
}
#[rhai_fn(name = "status", return_raw)]
pub fn set_status(user: &mut RhaiUser, status: String) -> Result<RhaiUser, Box<EvalAltResult>> {
let status_enum = match status.as_str() {
"Active" => UserStatus::Active,
"Inactive" => UserStatus::Inactive,
"Suspended" => UserStatus::Suspended,
"Archived" => UserStatus::Archived,
_ => return Err(format!("Invalid user status: {}", status).into()),
};
let owned = std::mem::take(user);
*user = owned.status(status_enum);
Ok(user.clone())
}
#[rhai_fn(name = "save_user", return_raw)]
pub fn save_user(user: &mut RhaiUser) -> Result<RhaiUser, Box<EvalAltResult>> {
// This would integrate with the database save functionality
// For now, just return the user as-is
Ok(user.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(user: &mut RhaiUser) -> u32 {
user.base_data.id
}
#[rhai_fn(name = "get_username")]
pub fn get_username(user: &mut RhaiUser) -> String {
user.username.clone()
}
#[rhai_fn(name = "get_email")]
pub fn get_email(user: &mut RhaiUser) -> String {
if let Some(first_email) = user.email.first() {
first_email.clone()
} else {
String::new()
}
}
#[rhai_fn(name = "get_pubkey")]
pub fn get_pubkey(user: &mut RhaiUser) -> String {
user.pubkey.clone()
}
}
// ============================================================================
// Group Module
// ============================================================================
type RhaiGroup = Group;
#[export_module]
mod rhai_group_module {
use super::RhaiGroup;
#[rhai_fn(name = "new_group", return_raw)]
pub fn new_group() -> Result<RhaiGroup, Box<EvalAltResult>> {
Ok(Group::new(0))
}
#[rhai_fn(name = "name", return_raw)]
pub fn set_name(group: &mut RhaiGroup, name: String) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned = std::mem::take(group);
*group = owned.name(name);
Ok(group.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
group: &mut RhaiGroup,
description: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let owned = std::mem::take(group);
*group = owned.description(description);
Ok(group.clone())
}
#[rhai_fn(name = "visibility", return_raw)]
pub fn set_visibility(
group: &mut RhaiGroup,
visibility: String,
) -> Result<RhaiGroup, Box<EvalAltResult>> {
let visibility_enum = match visibility.as_str() {
"Public" => Visibility::Public,
"Private" => Visibility::Private,
_ => return Err(format!("Invalid visibility: {}", visibility).into()),
};
let owned = std::mem::take(group);
*group = owned.visibility(visibility_enum);
Ok(group.clone())
}
#[rhai_fn(name = "save_group", return_raw)]
pub fn save_group(group: &mut RhaiGroup) -> Result<RhaiGroup, Box<EvalAltResult>> {
Ok(group.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(group: &mut RhaiGroup) -> u32 {
group.base_data.id
}
#[rhai_fn(name = "get_name")]
pub fn get_name(group: &mut RhaiGroup) -> String {
group.name.clone()
}
#[rhai_fn(name = "get_description")]
pub fn get_description(group: &mut RhaiGroup) -> String {
group.description.clone()
}
}
// ============================================================================
// Account Module (from money.rs)
// ============================================================================
type RhaiAccount = Account;
#[export_module]
mod rhai_account_module {
use super::RhaiAccount;
#[rhai_fn(name = "new_account", return_raw)]
pub fn new_account() -> Result<RhaiAccount, Box<EvalAltResult>> {
Ok(Account::new(0))
}
#[rhai_fn(name = "owner_id", return_raw)]
pub fn set_owner_id(
account: &mut RhaiAccount,
owner_id: i64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.owner_id(owner_id as u32);
Ok(account.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
account: &mut RhaiAccount,
address: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.address(address);
Ok(account.clone())
}
#[rhai_fn(name = "currency", return_raw)]
pub fn set_currency(
account: &mut RhaiAccount,
currency: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.currency(currency);
Ok(account.clone())
}
#[rhai_fn(name = "save_account", return_raw)]
pub fn save_account(account: &mut RhaiAccount) -> Result<RhaiAccount, Box<EvalAltResult>> {
Ok(account.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(account: &mut RhaiAccount) -> u32 {
account.base_data.id
}
#[rhai_fn(name = "get_address")]
pub fn get_address(account: &mut RhaiAccount) -> String {
account.address.clone()
}
#[rhai_fn(name = "get_currency")]
pub fn get_currency(account: &mut RhaiAccount) -> String {
account.currency.clone()
}
}
// ============================================================================
// DNS Zone Module
// ============================================================================
type RhaiDNSZone = DNSZone;
#[export_module]
mod rhai_dns_zone_module {
use super::RhaiDNSZone;
#[rhai_fn(name = "new_dns_zone", return_raw)]
pub fn new_dns_zone() -> Result<RhaiDNSZone, Box<EvalAltResult>> {
Ok(DNSZone::new(0))
}
#[rhai_fn(name = "domain", return_raw)]
pub fn set_domain(
zone: &mut RhaiDNSZone,
domain: String,
) -> Result<RhaiDNSZone, Box<EvalAltResult>> {
let owned = std::mem::take(zone);
*zone = owned.domain(domain);
Ok(zone.clone())
}
#[rhai_fn(name = "save_dns_zone", return_raw)]
pub fn save_dns_zone(zone: &mut RhaiDNSZone) -> Result<RhaiDNSZone, Box<EvalAltResult>> {
Ok(zone.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(zone: &mut RhaiDNSZone) -> u32 {
zone.base_data.id
}
#[rhai_fn(name = "get_domain")]
pub fn get_domain(zone: &mut RhaiDNSZone) -> String {
zone.domain.clone()
}
}
// ============================================================================
// Registration Functions
// ============================================================================
// Registration functions
/// Register heroledger modules into a Rhai Module (for use in packages)
/// This flattens all functions into the parent module
pub fn register_heroledger_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<User>("User");
parent_module.set_custom_type::<Group>("Group");
parent_module.set_custom_type::<Account>("Account");
parent_module.set_custom_type::<DNSZone>("DNSZone");
// Merge user functions into parent module
let user_module = exported_module!(rhai_user_module);
parent_module.merge(&user_module);
// Merge group functions into parent module
let group_module = exported_module!(rhai_group_module);
parent_module.merge(&group_module);
// Merge account functions into parent module
let account_module = exported_module!(rhai_account_module);
parent_module.merge(&account_module);
// Merge dnszone functions into parent module
let dnszone_module = exported_module!(rhai_dns_zone_module);
parent_module.merge(&dnszone_module);
}
/// Register heroledger modules into a Rhai Engine (for standalone use)
pub fn register_user_functions(engine: &mut Engine) {
let module = exported_module!(rhai_user_module);
engine.register_static_module("user", module.into());
}
pub fn register_group_functions(engine: &mut Engine) {
let module = exported_module!(rhai_group_module);
engine.register_static_module("group", module.into());
}
pub fn register_account_functions(engine: &mut Engine) {
let module = exported_module!(rhai_account_module);
engine.register_static_module("account", module.into());
}
pub fn register_dnszone_functions(engine: &mut Engine) {
let module = exported_module!(rhai_dns_zone_module);
engine.register_static_module("dnszone", module.into());
}
/// Register all heroledger Rhai modules with the engine
pub fn register_heroledger_rhai_modules(engine: &mut Engine) {
register_user_functions(engine);
register_group_functions(engine);
register_account_functions(engine);
register_dnszone_functions(engine);
}
// ============================================================================
// CustomType Implementations (for type registration in Rhai)
// ============================================================================
impl CustomType for User {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("User");
}
}
impl CustomType for Group {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Group");
}
}
impl CustomType for Account {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Account");
}
}
impl CustomType for DNSZone {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("DNSZone");
}
}

View File

@@ -0,0 +1,137 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
/// Category of the secret box
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SecretBoxCategory {
Profile,
}
impl Default for SecretBoxCategory {
fn default() -> Self {
SecretBoxCategory::Profile
}
}
/// Status of a notary
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NotaryStatus {
Active,
Inactive,
Suspended,
Archived,
Error,
}
impl Default for NotaryStatus {
fn default() -> Self {
NotaryStatus::Active
}
}
/// Represents an encrypted secret box for storing sensitive data
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SecretBox {
pub notary_id: u32,
pub value: String,
pub version: u16,
pub timestamp: u64,
pub cat: SecretBoxCategory,
}
impl SecretBox {
pub fn new() -> Self {
Self {
notary_id: 0,
value: String::new(),
version: 1,
timestamp: 0,
cat: SecretBoxCategory::default(),
}
}
pub fn notary_id(mut self, notary_id: u32) -> Self {
self.notary_id = notary_id;
self
}
pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string();
self
}
pub fn version(mut self, version: u16) -> Self {
self.version = version;
self
}
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
pub fn cat(mut self, cat: SecretBoxCategory) -> Self {
self.cat = cat;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents a notary who can decrypt secret boxes
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Notary {
/// Base model data
pub base_data: BaseData,
#[index]
pub userid: u32,
pub status: NotaryStatus,
pub myceliumaddress: String,
#[index]
pub pubkey: String,
}
impl Notary {
/// Create a new notary instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
userid: 0,
status: NotaryStatus::default(),
myceliumaddress: String::new(),
pubkey: String::new(),
}
}
/// Set the user ID (fluent)
pub fn userid(mut self, userid: u32) -> Self {
self.userid = userid;
self
}
/// Set the notary status (fluent)
pub fn status(mut self, status: NotaryStatus) -> Self {
self.status = status;
self
}
/// Set the mycelium address (fluent)
pub fn myceliumaddress(mut self, myceliumaddress: impl ToString) -> Self {
self.myceliumaddress = myceliumaddress.to_string();
self
}
/// Set the public key (fluent)
pub fn pubkey(mut self, pubkey: impl ToString) -> Self {
self.pubkey = pubkey.to_string();
self
}
/// Build the final notary instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,115 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
/// Status of a signature
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SignatureStatus {
Active,
Inactive,
Pending,
Revoked,
}
impl Default for SignatureStatus {
fn default() -> Self {
SignatureStatus::Pending
}
}
/// Type of object being signed
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ObjectType {
Account,
DNSRecord,
Membership,
User,
Transaction,
KYC,
}
impl Default for ObjectType {
fn default() -> Self {
ObjectType::User
}
}
/// Represents a cryptographic signature for various objects
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Signature {
/// Base model data
pub base_data: BaseData,
#[index]
pub signature_id: u32,
#[index]
pub user_id: u32,
pub value: String,
#[index]
pub objectid: u32,
pub objecttype: ObjectType,
pub status: SignatureStatus,
pub timestamp: u64,
}
impl Signature {
/// Create a new signature instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
signature_id: 0,
user_id: 0,
value: String::new(),
objectid: 0,
objecttype: ObjectType::default(),
status: SignatureStatus::default(),
timestamp: 0,
}
}
/// Set the signature ID (fluent)
pub fn signature_id(mut self, signature_id: u32) -> Self {
self.signature_id = signature_id;
self
}
/// Set the user ID (fluent)
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
/// Set the signature value (fluent)
pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string();
self
}
/// Set the object ID (fluent)
pub fn objectid(mut self, objectid: u32) -> Self {
self.objectid = objectid;
self
}
/// Set the object type (fluent)
pub fn objecttype(mut self, objecttype: ObjectType) -> Self {
self.objecttype = objecttype;
self
}
/// Set the signature status (fluent)
pub fn status(mut self, status: SignatureStatus) -> Self {
self.status = status;
self
}
/// Set the timestamp (fluent)
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Build the final signature instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,365 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents the status of a user in the system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserStatus {
Active,
Inactive,
Suspended,
Archived,
}
impl Default for UserStatus {
fn default() -> Self {
UserStatus::Active
}
}
/// Represents the KYC status of a user
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum KYCStatus {
Pending,
Approved,
Rejected,
}
impl Default for KYCStatus {
fn default() -> Self {
KYCStatus::Pending
}
}
/// User profile information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UserProfile {
pub user_id: u32,
pub full_name: String,
pub bio: String,
pub profile_pic: String,
pub links: HashMap<String, String>,
pub metadata: HashMap<String, String>,
}
impl UserProfile {
pub fn new() -> Self {
Self {
user_id: 0,
full_name: String::new(),
bio: String::new(),
profile_pic: String::new(),
links: HashMap::new(),
metadata: HashMap::new(),
}
}
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
pub fn full_name(mut self, full_name: impl ToString) -> Self {
self.full_name = full_name.to_string();
self
}
pub fn bio(mut self, bio: impl ToString) -> Self {
self.bio = bio.to_string();
self
}
pub fn profile_pic(mut self, profile_pic: impl ToString) -> Self {
self.profile_pic = profile_pic.to_string();
self
}
pub fn add_link(mut self, key: impl ToString, value: impl ToString) -> Self {
self.links.insert(key.to_string(), value.to_string());
self
}
pub fn links(mut self, links: HashMap<String, String>) -> Self {
self.links = links;
self
}
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
pub fn build(self) -> Self {
self
}
}
/// KYC (Know Your Customer) information for a user
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct KYCInfo {
pub user_id: u32,
pub full_name: String,
pub date_of_birth: u64,
pub address: String,
pub phone_number: String,
pub id_number: String,
pub id_type: String,
pub id_expiry: u64,
pub kyc_status: KYCStatus,
pub kyc_verified: bool,
pub kyc_verified_by: u32,
pub kyc_verified_at: u64,
pub kyc_rejected_reason: String,
pub kyc_signature: u32,
pub metadata: HashMap<String, String>,
}
impl KYCInfo {
pub fn new() -> Self {
Self {
user_id: 0,
full_name: String::new(),
date_of_birth: 0,
address: String::new(),
phone_number: String::new(),
id_number: String::new(),
id_type: String::new(),
id_expiry: 0,
kyc_status: KYCStatus::default(),
kyc_verified: false,
kyc_verified_by: 0,
kyc_verified_at: 0,
kyc_rejected_reason: String::new(),
kyc_signature: 0,
metadata: HashMap::new(),
}
}
pub fn user_id(mut self, user_id: u32) -> Self {
self.user_id = user_id;
self
}
pub fn full_name(mut self, full_name: impl ToString) -> Self {
self.full_name = full_name.to_string();
self
}
pub fn date_of_birth(mut self, date_of_birth: u64) -> Self {
self.date_of_birth = date_of_birth;
self
}
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
pub fn phone_number(mut self, phone_number: impl ToString) -> Self {
self.phone_number = phone_number.to_string();
self
}
pub fn id_number(mut self, id_number: impl ToString) -> Self {
self.id_number = id_number.to_string();
self
}
pub fn id_type(mut self, id_type: impl ToString) -> Self {
self.id_type = id_type.to_string();
self
}
pub fn id_expiry(mut self, id_expiry: u64) -> Self {
self.id_expiry = id_expiry;
self
}
pub fn kyc_status(mut self, kyc_status: KYCStatus) -> Self {
self.kyc_status = kyc_status;
self
}
pub fn kyc_verified(mut self, kyc_verified: bool) -> Self {
self.kyc_verified = kyc_verified;
self
}
pub fn kyc_verified_by(mut self, kyc_verified_by: u32) -> Self {
self.kyc_verified_by = kyc_verified_by;
self
}
pub fn kyc_verified_at(mut self, kyc_verified_at: u64) -> Self {
self.kyc_verified_at = kyc_verified_at;
self
}
pub fn kyc_rejected_reason(mut self, kyc_rejected_reason: impl ToString) -> Self {
self.kyc_rejected_reason = kyc_rejected_reason.to_string();
self
}
pub fn kyc_signature(mut self, kyc_signature: u32) -> Self {
self.kyc_signature = kyc_signature;
self
}
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents a secret box for storing encrypted data
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SecretBox {
pub data: Vec<u8>,
pub nonce: Vec<u8>,
}
impl SecretBox {
pub fn new() -> Self {
Self {
data: Vec::new(),
nonce: Vec::new(),
}
}
pub fn data(mut self, data: Vec<u8>) -> Self {
self.data = data;
self
}
pub fn nonce(mut self, nonce: Vec<u8>) -> Self {
self.nonce = nonce;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents a user in the heroledger system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, crate::DeriveObject)]
pub struct User {
/// Base model data
pub base_data: BaseData,
#[index]
pub username: String,
#[index]
pub pubkey: String,
pub email: Vec<String>,
pub status: UserStatus,
pub userprofile: Vec<SecretBox>,
pub kyc: Vec<SecretBox>,
}
impl Default for User {
fn default() -> Self {
Self {
base_data: BaseData::new(),
username: String::new(),
pubkey: String::new(),
email: Vec::new(),
status: UserStatus::default(),
userprofile: Vec::new(),
kyc: Vec::new(),
}
}
}
impl User {
/// Create a new user instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
username: String::new(),
pubkey: String::new(),
email: Vec::new(),
status: UserStatus::default(),
userprofile: Vec::new(),
kyc: Vec::new(),
}
}
/// Get the user ID
pub fn id(&self) -> u32 {
self.base_data.id
}
/// Set the username (fluent)
pub fn username(mut self, username: impl ToString) -> Self {
self.username = username.to_string();
self
}
/// Set the public key (fluent)
pub fn pubkey(mut self, pubkey: impl ToString) -> Self {
self.pubkey = pubkey.to_string();
self
}
/// Add an email address (fluent)
pub fn add_email(mut self, email: impl ToString) -> Self {
self.email.push(email.to_string());
self
}
/// Set all email addresses (fluent)
pub fn email(mut self, email: Vec<String>) -> Self {
self.email = email;
self
}
/// Set the user status (fluent)
pub fn status(mut self, status: UserStatus) -> Self {
self.status = status;
self
}
/// Add a user profile secret box (fluent)
pub fn add_userprofile(mut self, profile: SecretBox) -> Self {
self.userprofile.push(profile);
self
}
/// Set all user profile secret boxes (fluent)
pub fn userprofile(mut self, userprofile: Vec<SecretBox>) -> Self {
self.userprofile = userprofile;
self
}
/// Add a KYC secret box (fluent)
pub fn add_kyc(mut self, kyc: SecretBox) -> Self {
self.kyc.push(kyc);
self
}
/// Set all KYC secret boxes (fluent)
pub fn kyc(mut self, kyc: Vec<SecretBox>) -> Self {
self.kyc = kyc;
self
}
/// Build the final user instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,111 @@
use super::secretbox::SecretBox;
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
/// Represents a per-user key-value store
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct UserKVS {
/// Base model data
pub base_data: BaseData,
#[index]
pub userid: u32,
pub name: String,
}
impl UserKVS {
/// Create a new user KVS instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
userid: 0,
name: String::new(),
}
}
/// Set the user ID (fluent)
pub fn userid(mut self, userid: u32) -> Self {
self.userid = userid;
self
}
/// Set the KVS name (fluent)
pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
/// Build the final user KVS instance
pub fn build(self) -> Self {
self
}
}
/// Represents an item in a user's key-value store
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct UserKVSItem {
/// Base model data
pub base_data: BaseData,
#[index]
pub userkvs_id: u32,
pub key: String,
pub value: String,
pub secretbox: Vec<SecretBox>,
pub timestamp: u64,
}
impl UserKVSItem {
/// Create a new user KVS item instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
userkvs_id: 0,
key: String::new(),
value: String::new(),
secretbox: Vec::new(),
timestamp: 0,
}
}
/// Set the user KVS ID (fluent)
pub fn userkvs_id(mut self, userkvs_id: u32) -> Self {
self.userkvs_id = userkvs_id;
self
}
/// Set the key (fluent)
pub fn key(mut self, key: impl ToString) -> Self {
self.key = key.to_string();
self
}
/// Set the value (fluent)
pub fn value(mut self, value: impl ToString) -> Self {
self.value = value.to_string();
self
}
/// Add a secret box (fluent)
pub fn add_secretbox(mut self, secretbox: SecretBox) -> Self {
self.secretbox.push(secretbox);
self
}
/// Set all secret boxes (fluent)
pub fn secretbox(mut self, secretbox: Vec<SecretBox>) -> Self {
self.secretbox = secretbox;
self
}
/// Set the timestamp (fluent)
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Build the final user KVS item instance
pub fn build(self) -> Self {
self
}
}

238
src/objects/kyc/client.rs Normal file
View File

@@ -0,0 +1,238 @@
/// KYC Client
///
/// Actual API client for making KYC provider API calls.
/// Currently implements Idenfy API but designed to be extensible for other providers.
use serde::{Deserialize, Serialize};
use super::{KycInfo, KycSession, session::SessionStatus};
/// KYC Client for making API calls to KYC providers
#[derive(Debug, Clone)]
pub struct KycClient {
/// Provider name (e.g., "idenfy", "sumsub", "onfido")
pub provider: String,
/// API key
pub api_key: String,
/// API secret
pub api_secret: String,
/// Base URL for API (optional, uses provider default if not set)
pub base_url: Option<String>,
}
/// Idenfy-specific API request/response structures
#[derive(Debug, Serialize, Deserialize)]
pub struct IdenfyTokenRequest {
#[serde(rename = "clientId")]
pub client_id: String,
#[serde(rename = "firstName")]
pub first_name: String,
#[serde(rename = "lastName")]
pub last_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(rename = "dateOfBirth", skip_serializing_if = "Option::is_none")]
pub date_of_birth: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nationality: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(rename = "zipCode", skip_serializing_if = "Option::is_none")]
pub zip_code: Option<String>,
#[serde(rename = "successUrl", skip_serializing_if = "Option::is_none")]
pub success_url: Option<String>,
#[serde(rename = "errorUrl", skip_serializing_if = "Option::is_none")]
pub error_url: Option<String>,
#[serde(rename = "callbackUrl", skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct IdenfyTokenResponse {
#[serde(rename = "authToken")]
pub auth_token: String,
#[serde(rename = "scanRef")]
pub scan_ref: String,
#[serde(rename = "clientId")]
pub client_id: String,
}
#[derive(Debug, Deserialize)]
pub struct IdenfyVerificationStatus {
pub status: String,
#[serde(rename = "scanRef")]
pub scan_ref: String,
#[serde(rename = "clientId")]
pub client_id: String,
}
impl KycClient {
/// Create a new KYC client
pub fn new(provider: String, api_key: String, api_secret: String) -> Self {
Self {
provider,
api_key,
api_secret,
base_url: None,
}
}
/// Create an Idenfy client
pub fn idenfy(api_key: String, api_secret: String) -> Self {
Self {
provider: "idenfy".to_string(),
api_key,
api_secret,
base_url: Some("https://ivs.idenfy.com/api/v2".to_string()),
}
}
/// Set custom base URL
pub fn with_base_url(mut self, base_url: String) -> Self {
self.base_url = Some(base_url);
self
}
/// Get the base URL for the provider
fn get_base_url(&self) -> String {
if let Some(url) = &self.base_url {
return url.clone();
}
match self.provider.as_str() {
"idenfy" => "https://ivs.idenfy.com/api/v2".to_string(),
"sumsub" => "https://api.sumsub.com".to_string(),
"onfido" => "https://api.onfido.com/v3".to_string(),
_ => panic!("Unknown provider: {}", self.provider),
}
}
/// Create a verification session (Idenfy implementation)
pub async fn create_verification_session(
&self,
kyc_info: &KycInfo,
session: &mut KycSession,
) -> Result<String, Box<dyn std::error::Error>> {
match self.provider.as_str() {
"idenfy" => self.create_idenfy_session(kyc_info, session).await,
_ => Err(format!("Provider {} not yet implemented", self.provider).into()),
}
}
/// Create an Idenfy verification session
async fn create_idenfy_session(
&self,
kyc_info: &KycInfo,
session: &mut KycSession,
) -> Result<String, Box<dyn std::error::Error>> {
let url = format!("{}/token", self.get_base_url());
let request = IdenfyTokenRequest {
client_id: kyc_info.client_id.clone(),
first_name: kyc_info.first_name.clone(),
last_name: kyc_info.last_name.clone(),
email: kyc_info.email.clone(),
phone: kyc_info.phone.clone(),
date_of_birth: kyc_info.date_of_birth.clone(),
nationality: kyc_info.nationality.clone(),
address: kyc_info.address.clone(),
city: kyc_info.city.clone(),
country: kyc_info.country.clone(),
zip_code: kyc_info.postal_code.clone(),
success_url: session.success_url.clone(),
error_url: session.error_url.clone(),
callback_url: session.callback_url.clone(),
locale: session.locale.clone(),
};
let client = reqwest::Client::new();
let response = client
.post(&url)
.basic_auth(&self.api_key, Some(&self.api_secret))
.json(&request)
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(format!("Idenfy API error: {}", error_text).into());
}
let token_response: IdenfyTokenResponse = response.json().await?;
// Update session with token and URL
session.set_session_token(token_response.auth_token.clone());
// Construct verification URL
let verification_url = format!(
"https://ivs.idenfy.com/api/v2/redirect?authToken={}",
token_response.auth_token
);
session.set_verification_url(verification_url.clone());
session.set_status(SessionStatus::Active);
Ok(verification_url)
}
/// Get verification status (Idenfy implementation)
pub async fn get_verification_status(
&self,
scan_ref: &str,
) -> Result<IdenfyVerificationStatus, Box<dyn std::error::Error>> {
match self.provider.as_str() {
"idenfy" => self.get_idenfy_status(scan_ref).await,
_ => Err(format!("Provider {} not yet implemented", self.provider).into()),
}
}
/// Get Idenfy verification status
async fn get_idenfy_status(
&self,
scan_ref: &str,
) -> Result<IdenfyVerificationStatus, Box<dyn std::error::Error>> {
let url = format!("{}/status/{}", self.get_base_url(), scan_ref);
let client = reqwest::Client::new();
let response = client
.get(&url)
.basic_auth(&self.api_key, Some(&self.api_secret))
.send()
.await?;
if !response.status().is_success() {
let error_text = response.text().await?;
return Err(format!("Idenfy API error: {}", error_text).into());
}
let status: IdenfyVerificationStatus = response.json().await?;
Ok(status)
}
}

319
src/objects/kyc/info.rs Normal file
View File

@@ -0,0 +1,319 @@
/// KYC Info Object
///
/// Represents customer/person information for KYC verification.
/// Designed to be provider-agnostic but follows Idenfy API patterns.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct KycInfo {
#[serde(flatten)]
pub base_data: BaseData,
/// External client ID (from your system) - links to User
pub client_id: String,
/// Full name (or separate first/last)
pub full_name: String,
/// First name
pub first_name: String,
/// Last name
pub last_name: String,
/// Email address
pub email: Option<String>,
/// Phone number
pub phone: Option<String>,
/// Date of birth (YYYY-MM-DD string or unix timestamp)
pub date_of_birth: Option<String>,
/// Date of birth as unix timestamp
pub date_of_birth_timestamp: Option<u64>,
/// Nationality (ISO 3166-1 alpha-2 code)
pub nationality: Option<String>,
/// Address
pub address: Option<String>,
/// City
pub city: Option<String>,
/// Country (ISO 3166-1 alpha-2 code)
pub country: Option<String>,
/// Postal code
pub postal_code: Option<String>,
/// ID document number
pub id_number: Option<String>,
/// ID document type (passport, drivers_license, national_id, etc.)
pub id_type: Option<String>,
/// ID document expiry (unix timestamp)
pub id_expiry: Option<u64>,
/// KYC provider (e.g., "idenfy", "sumsub", "onfido")
pub provider: String,
/// Provider-specific client ID (assigned by KYC provider)
pub provider_client_id: Option<String>,
/// Current verification status
pub verification_status: VerificationStatus,
/// Whether KYC is verified
pub kyc_verified: bool,
/// User ID who verified this KYC
pub kyc_verified_by: Option<u32>,
/// Timestamp when KYC was verified
pub kyc_verified_at: Option<u64>,
/// Reason for rejection if denied
pub kyc_rejected_reason: Option<String>,
/// Signature ID for verification record
pub kyc_signature: Option<u32>,
/// Additional metadata
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum VerificationStatus {
/// Not yet started
Pending,
/// Verification in progress
Processing,
/// Successfully verified
Approved,
/// Verification failed
Denied,
/// Verification expired
Expired,
/// Requires manual review
Review,
}
impl Default for VerificationStatus {
fn default() -> Self {
VerificationStatus::Pending
}
}
impl KycInfo {
/// Create a new KYC info object
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
client_id: String::new(),
full_name: String::new(),
first_name: String::new(),
last_name: String::new(),
email: None,
phone: None,
date_of_birth: None,
date_of_birth_timestamp: None,
nationality: None,
address: None,
city: None,
country: None,
postal_code: None,
id_number: None,
id_type: None,
id_expiry: None,
provider: "idenfy".to_string(), // Default to Idenfy
provider_client_id: None,
verification_status: VerificationStatus::default(),
kyc_verified: false,
kyc_verified_by: None,
kyc_verified_at: None,
kyc_rejected_reason: None,
kyc_signature: None,
metadata: std::collections::HashMap::new(),
}
}
/// Builder: Set client ID
pub fn client_id(mut self, client_id: String) -> Self {
self.client_id = client_id;
self.base_data.update_modified();
self
}
/// Builder: Set full name
pub fn full_name(mut self, full_name: String) -> Self {
self.full_name = full_name.clone();
// Try to split into first/last if not already set
if self.first_name.is_empty() && self.last_name.is_empty() {
let parts: Vec<&str> = full_name.split_whitespace().collect();
if parts.len() >= 2 {
self.first_name = parts[0].to_string();
self.last_name = parts[1..].join(" ");
} else if parts.len() == 1 {
self.first_name = parts[0].to_string();
}
}
self.base_data.update_modified();
self
}
/// Builder: Set first name
pub fn first_name(mut self, first_name: String) -> Self {
self.first_name = first_name.clone();
// Update full_name if last_name exists
if !self.last_name.is_empty() {
self.full_name = format!("{} {}", first_name, self.last_name);
} else {
self.full_name = first_name;
}
self.base_data.update_modified();
self
}
/// Builder: Set last name
pub fn last_name(mut self, last_name: String) -> Self {
self.last_name = last_name.clone();
// Update full_name if first_name exists
if !self.first_name.is_empty() {
self.full_name = format!("{} {}", self.first_name, last_name);
} else {
self.full_name = last_name;
}
self.base_data.update_modified();
self
}
/// Builder: Set email
pub fn email(mut self, email: String) -> Self {
self.email = Some(email);
self.base_data.update_modified();
self
}
/// Builder: Set phone
pub fn phone(mut self, phone: String) -> Self {
self.phone = Some(phone);
self.base_data.update_modified();
self
}
/// Builder: Set date of birth
pub fn date_of_birth(mut self, dob: String) -> Self {
self.date_of_birth = Some(dob);
self.base_data.update_modified();
self
}
/// Builder: Set nationality
pub fn nationality(mut self, nationality: String) -> Self {
self.nationality = Some(nationality);
self.base_data.update_modified();
self
}
/// Builder: Set address
pub fn address(mut self, address: String) -> Self {
self.address = Some(address);
self.base_data.update_modified();
self
}
/// Builder: Set city
pub fn city(mut self, city: String) -> Self {
self.city = Some(city);
self.base_data.update_modified();
self
}
/// Builder: Set country
pub fn country(mut self, country: String) -> Self {
self.country = Some(country);
self.base_data.update_modified();
self
}
/// Builder: Set postal code
pub fn postal_code(mut self, postal_code: String) -> Self {
self.postal_code = Some(postal_code);
self.base_data.update_modified();
self
}
/// Builder: Set ID number
pub fn id_number(mut self, id_number: String) -> Self {
self.id_number = Some(id_number);
self.base_data.update_modified();
self
}
/// Builder: Set ID type
pub fn id_type(mut self, id_type: String) -> Self {
self.id_type = Some(id_type);
self.base_data.update_modified();
self
}
/// Builder: Set ID expiry
pub fn id_expiry(mut self, id_expiry: u64) -> Self {
self.id_expiry = Some(id_expiry);
self.base_data.update_modified();
self
}
/// Builder: Set KYC provider
pub fn provider(mut self, provider: String) -> Self {
self.provider = provider;
self.base_data.update_modified();
self
}
/// Set provider client ID (assigned by KYC provider)
pub fn set_provider_client_id(&mut self, provider_client_id: String) {
self.provider_client_id = Some(provider_client_id);
self.base_data.update_modified();
}
/// Set verification status
pub fn set_verification_status(&mut self, status: VerificationStatus) {
self.verification_status = status;
self.base_data.update_modified();
}
/// Set KYC verified
pub fn set_kyc_verified(&mut self, verified: bool, verified_by: Option<u32>) {
self.kyc_verified = verified;
self.kyc_verified_by = verified_by;
self.kyc_verified_at = Some(std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs());
self.base_data.update_modified();
}
/// Set KYC rejected
pub fn set_kyc_rejected(&mut self, reason: String) {
self.kyc_verified = false;
self.kyc_rejected_reason = Some(reason);
self.verification_status = VerificationStatus::Denied;
self.base_data.update_modified();
}
/// Add metadata
pub fn add_metadata(&mut self, key: String, value: String) {
self.metadata.insert(key, value);
self.base_data.update_modified();
}
}

13
src/objects/kyc/mod.rs Normal file
View File

@@ -0,0 +1,13 @@
/// KYC (Know Your Customer) Module
///
/// Provides generic KYC client and session management.
/// Designed to work with multiple KYC providers (Idenfy, Sumsub, Onfido, etc.)
pub mod info;
pub mod client;
pub mod session;
pub mod rhai;
pub use info::{KycInfo, VerificationStatus};
pub use client::KycClient;
pub use session::{KycSession, SessionStatus, SessionResult};

326
src/objects/kyc/rhai.rs Normal file
View File

@@ -0,0 +1,326 @@
/// Rhai bindings for KYC objects
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use std::mem;
use super::info::{KycInfo, VerificationStatus};
use super::session::{KycSession, SessionStatus};
// ============================================================================
// KYC Info Module
// ============================================================================
type RhaiKycInfo = KycInfo;
#[export_module]
mod rhai_kyc_info_module {
use super::RhaiKycInfo;
#[rhai_fn(name = "new_kyc_info", return_raw)]
pub fn new_kyc_info() -> Result<RhaiKycInfo, Box<EvalAltResult>> {
Ok(KycInfo::new(0))
}
#[rhai_fn(name = "client_id", return_raw)]
pub fn set_client_id(
info: &mut RhaiKycInfo,
client_id: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.client_id(client_id);
Ok(info.clone())
}
#[rhai_fn(name = "first_name", return_raw)]
pub fn set_first_name(
info: &mut RhaiKycInfo,
first_name: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.first_name(first_name);
Ok(info.clone())
}
#[rhai_fn(name = "last_name", return_raw)]
pub fn set_last_name(
info: &mut RhaiKycInfo,
last_name: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.last_name(last_name);
Ok(info.clone())
}
#[rhai_fn(name = "email", return_raw)]
pub fn set_email(
info: &mut RhaiKycInfo,
email: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.email(email);
Ok(info.clone())
}
#[rhai_fn(name = "phone", return_raw)]
pub fn set_phone(
info: &mut RhaiKycInfo,
phone: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.phone(phone);
Ok(info.clone())
}
#[rhai_fn(name = "date_of_birth", return_raw)]
pub fn set_date_of_birth(
info: &mut RhaiKycInfo,
dob: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.date_of_birth(dob);
Ok(info.clone())
}
#[rhai_fn(name = "nationality", return_raw)]
pub fn set_nationality(
info: &mut RhaiKycInfo,
nationality: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.nationality(nationality);
Ok(info.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
info: &mut RhaiKycInfo,
address: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.address(address);
Ok(info.clone())
}
#[rhai_fn(name = "city", return_raw)]
pub fn set_city(
info: &mut RhaiKycInfo,
city: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.city(city);
Ok(info.clone())
}
#[rhai_fn(name = "country", return_raw)]
pub fn set_country(
info: &mut RhaiKycInfo,
country: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.country(country);
Ok(info.clone())
}
#[rhai_fn(name = "postal_code", return_raw)]
pub fn set_postal_code(
info: &mut RhaiKycInfo,
postal_code: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.postal_code(postal_code);
Ok(info.clone())
}
#[rhai_fn(name = "provider", return_raw)]
pub fn set_provider(
info: &mut RhaiKycInfo,
provider: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
let owned = std::mem::take(info);
*info = owned.provider(provider);
Ok(info.clone())
}
#[rhai_fn(name = "document_type", return_raw)]
pub fn set_document_type(
info: &mut RhaiKycInfo,
doc_type: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Store in provider field for now (or add to KycInfo struct)
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|doc_type:{}", provider, doc_type));
Ok(info.clone())
}
#[rhai_fn(name = "document_number", return_raw)]
pub fn set_document_number(
info: &mut RhaiKycInfo,
doc_number: String,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Store in provider field for now (or add to KycInfo struct)
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|doc_num:{}", provider, doc_number));
Ok(info.clone())
}
#[rhai_fn(name = "verified", return_raw)]
pub fn set_verified(
info: &mut RhaiKycInfo,
_verified: bool,
) -> Result<RhaiKycInfo, Box<EvalAltResult>> {
// Mark as verified in provider field
let provider = info.provider.clone();
let owned = std::mem::take(info);
*info = owned.provider(format!("{}|verified", provider));
Ok(info.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(info: &mut RhaiKycInfo) -> u32 {
info.base_data.id
}
#[rhai_fn(name = "get_client_id")]
pub fn get_client_id(info: &mut RhaiKycInfo) -> String {
info.client_id.clone()
}
#[rhai_fn(name = "get_first_name")]
pub fn get_first_name(info: &mut RhaiKycInfo) -> String {
info.first_name.clone()
}
#[rhai_fn(name = "get_last_name")]
pub fn get_last_name(info: &mut RhaiKycInfo) -> String {
info.last_name.clone()
}
#[rhai_fn(name = "get_email")]
pub fn get_email(info: &mut RhaiKycInfo) -> String {
info.email.clone().unwrap_or_default()
}
#[rhai_fn(name = "get_provider")]
pub fn get_provider(info: &mut RhaiKycInfo) -> String {
info.provider.clone()
}
}
// ============================================================================
// KYC Session Module
// ============================================================================
type RhaiKycSession = KycSession;
#[export_module]
mod rhai_kyc_session_module {
use super::RhaiKycSession;
#[rhai_fn(name = "new_kyc_session", return_raw)]
pub fn new_kyc_session(
client_id: String,
provider: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
Ok(KycSession::new(0, client_id, provider))
}
#[rhai_fn(name = "callback_url", return_raw)]
pub fn set_callback_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.callback_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "success_url", return_raw)]
pub fn set_success_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.success_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "error_url", return_raw)]
pub fn set_error_url(
session: &mut RhaiKycSession,
url: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.error_url(url);
Ok(session.clone())
}
#[rhai_fn(name = "locale", return_raw)]
pub fn set_locale(
session: &mut RhaiKycSession,
locale: String,
) -> Result<RhaiKycSession, Box<EvalAltResult>> {
let owned = std::mem::take(session);
*session = owned.locale(locale);
Ok(session.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(session: &mut RhaiKycSession) -> u32 {
session.base_data.id
}
#[rhai_fn(name = "get_client_id")]
pub fn get_client_id(session: &mut RhaiKycSession) -> String {
session.client_id.clone()
}
#[rhai_fn(name = "get_provider")]
pub fn get_provider(session: &mut RhaiKycSession) -> String {
session.provider.clone()
}
#[rhai_fn(name = "get_verification_url")]
pub fn get_verification_url(session: &mut RhaiKycSession) -> String {
session.verification_url.clone().unwrap_or_default()
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register KYC modules into a Rhai Module (for use in packages)
pub fn register_kyc_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<KycInfo>("KycInfo");
parent_module.set_custom_type::<KycSession>("KycSession");
// Merge KYC info functions
let info_module = exported_module!(rhai_kyc_info_module);
parent_module.merge(&info_module);
// Merge KYC session functions
let session_module = exported_module!(rhai_kyc_session_module);
parent_module.merge(&session_module);
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for KycInfo {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("KycInfo");
}
}
impl CustomType for KycSession {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("KycSession");
}
}

186
src/objects/kyc/session.rs Normal file
View File

@@ -0,0 +1,186 @@
/// KYC Verification Session
///
/// Represents a verification session for a KYC client.
/// Follows Idenfy API patterns but is provider-agnostic.
use crate::store::{BaseData, Object, Storable};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, crate::DeriveObject)]
pub struct KycSession {
#[serde(flatten)]
pub base_data: BaseData,
/// Reference to the KYC client
pub client_id: String,
/// KYC provider
pub provider: String,
/// Session token/ID from provider
pub session_token: Option<String>,
/// Verification URL for the client
pub verification_url: Option<String>,
/// Session status
pub status: SessionStatus,
/// Session expiration timestamp
pub expires_at: Option<i64>,
/// Callback URL for webhook notifications
pub callback_url: Option<String>,
/// Success redirect URL
pub success_url: Option<String>,
/// Error redirect URL
pub error_url: Option<String>,
/// Locale (e.g., "en", "de", "fr")
pub locale: Option<String>,
/// Provider-specific configuration
#[serde(default)]
pub provider_config: std::collections::HashMap<String, String>,
/// Session result data
pub result: Option<SessionResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum SessionStatus {
/// Session created but not started
#[default]
Created,
/// Client is currently verifying
Active,
/// Session completed successfully
Completed,
/// Session failed
Failed,
/// Session expired
Expired,
/// Session cancelled
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionResult {
/// Overall verification status
pub status: String,
/// Verification score (0-100)
pub score: Option<f64>,
/// Reason for denial (if denied)
pub denial_reason: Option<String>,
/// Document type verified
pub document_type: Option<String>,
/// Document number
pub document_number: Option<String>,
/// Document issuing country
pub document_country: Option<String>,
/// Face match result
pub face_match: Option<bool>,
/// Liveness check result
pub liveness_check: Option<bool>,
/// Additional provider-specific data
#[serde(default)]
pub provider_data: std::collections::HashMap<String, serde_json::Value>,
}
impl KycSession {
/// Create a new KYC session
pub fn new(id: u32, client_id: String, provider: String) -> Self {
let mut base_data = BaseData::new();
base_data.id = id;
Self {
base_data,
client_id,
provider,
session_token: None,
verification_url: None,
status: SessionStatus::Created,
expires_at: None,
callback_url: None,
success_url: None,
error_url: None,
locale: None,
provider_config: std::collections::HashMap::new(),
result: None,
}
}
/// Builder: Set callback URL
pub fn callback_url(mut self, url: String) -> Self {
self.callback_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set success URL
pub fn success_url(mut self, url: String) -> Self {
self.success_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set error URL
pub fn error_url(mut self, url: String) -> Self {
self.error_url = Some(url);
self.base_data.update_modified();
self
}
/// Builder: Set locale
pub fn locale(mut self, locale: String) -> Self {
self.locale = Some(locale);
self.base_data.update_modified();
self
}
/// Set session token from provider
pub fn set_session_token(&mut self, token: String) {
self.session_token = Some(token);
self.base_data.update_modified();
}
/// Set verification URL
pub fn set_verification_url(&mut self, url: String) {
self.verification_url = Some(url);
self.base_data.update_modified();
}
/// Set session status
pub fn set_status(&mut self, status: SessionStatus) {
self.status = status;
self.base_data.update_modified();
}
/// Set expiration timestamp
pub fn set_expires_at(&mut self, timestamp: i64) {
self.expires_at = Some(timestamp);
self.base_data.update_modified();
}
/// Set session result
pub fn set_result(&mut self, result: SessionResult) {
self.result = Some(result);
self.base_data.update_modified();
}
/// Add provider-specific configuration
pub fn add_provider_config(&mut self, key: String, value: String) {
self.provider_config.insert(key, value);
self.base_data.update_modified();
}
}

View File

@@ -0,0 +1,129 @@
/// Legal Contract Object
///
/// Simple contract object with signatures for legal agreements
use crate::store::{BaseData, Object};
use serde::{Deserialize, Serialize};
/// Contract status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ContractStatus {
Draft,
Active,
Completed,
Cancelled,
}
impl Default for ContractStatus {
fn default() -> Self {
ContractStatus::Draft
}
}
/// Legal contract with signatures
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, crate::DeriveObject)]
pub struct Contract {
/// Base data for object storage
pub base_data: BaseData,
/// Contract title
pub title: String,
/// Contract content/terms
pub content: String,
/// Contract status
pub status: ContractStatus,
/// List of signature IDs (references to Signature objects)
pub signatures: Vec<u32>,
/// Creator user ID
pub creator_id: u32,
/// Expiry timestamp (optional)
pub expires_at: Option<u64>,
}
impl Contract {
/// Create a new contract
pub fn new(id: u32) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
title: String::new(),
content: String::new(),
status: ContractStatus::default(),
signatures: Vec::new(),
creator_id: 0,
expires_at: None,
}
}
/// Set the title (fluent)
pub fn title(mut self, title: impl ToString) -> Self {
self.title = title.to_string();
self
}
/// Set the content (fluent)
pub fn content(mut self, content: impl ToString) -> Self {
self.content = content.to_string();
self
}
/// Set the status (fluent)
pub fn status(mut self, status: ContractStatus) -> Self {
self.status = status;
self
}
/// Set the creator ID (fluent)
pub fn creator_id(mut self, creator_id: u32) -> Self {
self.creator_id = creator_id;
self
}
/// Set the expiry timestamp (fluent)
pub fn expires_at(mut self, expires_at: u64) -> Self {
self.expires_at = Some(expires_at);
self
}
/// Add a signature (fluent)
pub fn add_signature(mut self, signature_id: u32) -> Self {
if !self.signatures.contains(&signature_id) {
self.signatures.push(signature_id);
}
self
}
/// Remove a signature (fluent)
pub fn remove_signature(mut self, signature_id: u32) -> Self {
self.signatures.retain(|&id| id != signature_id);
self
}
/// Check if all required signatures are present
pub fn is_fully_signed(&self, required_count: usize) -> bool {
self.signatures.len() >= required_count
}
/// Activate the contract
pub fn activate(mut self) -> Self {
self.status = ContractStatus::Active;
self
}
/// Complete the contract
pub fn complete(mut self) -> Self {
self.status = ContractStatus::Completed;
self
}
/// Cancel the contract
pub fn cancel(mut self) -> Self {
self.status = ContractStatus::Cancelled;
self
}
}

7
src/objects/legal/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
/// Legal module for contracts and legal documents
pub mod contract;
pub mod rhai;
pub use contract::{Contract, ContractStatus};
pub use rhai::register_legal_modules;

150
src/objects/legal/rhai.rs Normal file
View File

@@ -0,0 +1,150 @@
/// Rhai bindings for Legal objects (Contract)
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::{Contract, ContractStatus};
/// Register legal modules with the Rhai engine
pub fn register_legal_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<Contract>("Contract");
parent_module.set_custom_type::<ContractStatus>("ContractStatus");
// Merge contract functions
let contract_module = exported_module!(rhai_contract_module);
parent_module.merge(&contract_module);
}
// ============================================================================
// Contract Module
// ============================================================================
type RhaiContract = Contract;
type RhaiContractStatus = ContractStatus;
#[export_module]
mod rhai_contract_module {
use super::{RhaiContract, RhaiContractStatus};
use super::super::{Contract, ContractStatus};
use ::rhai::EvalAltResult;
// Contract constructor
#[rhai_fn(name = "new_contract", return_raw)]
pub fn new_contract(id: i64) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(Contract::new(id as u32))
}
// Builder methods
#[rhai_fn(name = "title", return_raw)]
pub fn set_title(
contract: RhaiContract,
title: String,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.title(title))
}
#[rhai_fn(name = "content", return_raw)]
pub fn set_content(
contract: RhaiContract,
content: String,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.content(content))
}
#[rhai_fn(name = "creator_id", return_raw)]
pub fn set_creator_id(
contract: RhaiContract,
creator_id: i64,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.creator_id(creator_id as u32))
}
#[rhai_fn(name = "expires_at", return_raw)]
pub fn set_expires_at(
contract: RhaiContract,
expires_at: i64,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.expires_at(expires_at as u64))
}
#[rhai_fn(name = "add_signature", return_raw)]
pub fn add_signature(
contract: RhaiContract,
signature_id: i64,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.add_signature(signature_id as u32))
}
#[rhai_fn(name = "remove_signature", return_raw)]
pub fn remove_signature(
contract: RhaiContract,
signature_id: i64,
) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.remove_signature(signature_id as u32))
}
// State management methods
#[rhai_fn(name = "activate", return_raw)]
pub fn activate(contract: RhaiContract) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.activate())
}
#[rhai_fn(name = "complete", return_raw)]
pub fn complete(contract: RhaiContract) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.complete())
}
#[rhai_fn(name = "cancel", return_raw)]
pub fn cancel(contract: RhaiContract) -> Result<RhaiContract, Box<EvalAltResult>> {
Ok(contract.cancel())
}
// Query methods
#[rhai_fn(name = "is_fully_signed", pure)]
pub fn is_fully_signed(contract: &mut RhaiContract, required_count: i64) -> bool {
contract.is_fully_signed(required_count as usize)
}
// Getters
#[rhai_fn(name = "title", pure)]
pub fn get_title(contract: &mut RhaiContract) -> String {
contract.title.clone()
}
#[rhai_fn(name = "content", pure)]
pub fn get_content(contract: &mut RhaiContract) -> String {
contract.content.clone()
}
#[rhai_fn(name = "status", pure)]
pub fn get_status(contract: &mut RhaiContract) -> String {
format!("{:?}", contract.status)
}
#[rhai_fn(name = "creator_id", pure)]
pub fn get_creator_id(contract: &mut RhaiContract) -> i64 {
contract.creator_id as i64
}
#[rhai_fn(name = "signature_count", pure)]
pub fn get_signature_count(contract: &mut RhaiContract) -> i64 {
contract.signatures.len() as i64
}
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for Contract {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Contract");
}
}
impl CustomType for ContractStatus {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("ContractStatus");
}
}

View File

@@ -1,5 +1,17 @@
pub mod note; pub mod note;
pub mod event; pub mod event;
pub mod heroledger;
pub mod grid4;
pub mod kyc;
pub mod flow;
pub mod communication;
pub mod money;
pub mod legal;
pub use note::Note; pub use note::Note;
pub use event::Event; pub use event::Event;
pub use kyc::{KycInfo, KycSession};
pub use flow::{FlowTemplate, FlowInstance};
pub use communication::{Verification, EmailClient};
pub use money::{Account, Asset, Transaction, PaymentClient};
pub use legal::{Contract, ContractStatus};

10
src/objects/money/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
/// Money Module
///
/// Financial objects including accounts, assets, transactions, and payment providers.
pub mod models;
pub mod rhai;
pub mod payments;
pub use models::{Account, Asset, Transaction, AccountStatus, TransactionType, Signature, AccountPolicyItem};
pub use payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};

498
src/objects/money/models.rs Normal file
View File

@@ -0,0 +1,498 @@
use crate::store::{BaseData, IndexKey, Object};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Represents the status of an account
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AccountStatus {
Active,
Inactive,
Suspended,
Archived,
}
impl Default for AccountStatus {
fn default() -> Self {
AccountStatus::Active
}
}
/// Represents the type of transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TransactionType {
Transfer,
Clawback,
Freeze,
Unfreeze,
Issue,
Burn,
}
impl Default for TransactionType {
fn default() -> Self {
TransactionType::Transfer
}
}
/// Represents a signature for transactions
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Signature {
pub signer_id: u32,
pub signature: String,
pub timestamp: u64,
}
impl Signature {
pub fn new() -> Self {
Self {
signer_id: 0,
signature: String::new(),
timestamp: 0,
}
}
pub fn signer_id(mut self, signer_id: u32) -> Self {
self.signer_id = signer_id;
self
}
pub fn signature(mut self, signature: impl ToString) -> Self {
self.signature = signature.to_string();
self
}
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
pub fn build(self) -> Self {
self
}
}
/// Policy item for account operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct AccountPolicyItem {
pub signers: Vec<u32>,
pub min_signatures: u32,
pub enabled: bool,
pub threshold: f64,
pub recipient: u32,
}
impl AccountPolicyItem {
pub fn new() -> Self {
Self {
signers: Vec::new(),
min_signatures: 0,
enabled: false,
threshold: 0.0,
recipient: 0,
}
}
pub fn add_signer(mut self, signer_id: u32) -> Self {
self.signers.push(signer_id);
self
}
pub fn signers(mut self, signers: Vec<u32>) -> Self {
self.signers = signers;
self
}
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn threshold(mut self, threshold: f64) -> Self {
self.threshold = threshold;
self
}
pub fn recipient(mut self, recipient: u32) -> Self {
self.recipient = recipient;
self
}
pub fn build(self) -> Self {
self
}
}
/// Represents an account in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Account {
/// Base model data
pub base_data: BaseData,
pub owner_id: u32,
#[index]
pub address: String,
pub balance: f64,
pub currency: String,
pub assetid: u32,
pub last_activity: u64,
pub administrators: Vec<u32>,
pub accountpolicy: u32,
}
impl Account {
/// Create a new account instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
owner_id: 0,
address: String::new(),
balance: 0.0,
currency: String::new(),
assetid: 0,
last_activity: 0,
administrators: Vec::new(),
accountpolicy: 0,
}
}
/// Set the owner ID (fluent)
pub fn owner_id(mut self, owner_id: u32) -> Self {
self.owner_id = owner_id;
self
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the balance (fluent)
pub fn balance(mut self, balance: f64) -> Self {
self.balance = balance;
self
}
/// Set the currency (fluent)
pub fn currency(mut self, currency: impl ToString) -> Self {
self.currency = currency.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the last activity timestamp (fluent)
pub fn last_activity(mut self, last_activity: u64) -> Self {
self.last_activity = last_activity;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set the account policy ID (fluent)
pub fn accountpolicy(mut self, accountpolicy: u32) -> Self {
self.accountpolicy = accountpolicy;
self
}
/// Build the final account instance
pub fn build(self) -> Self {
self
}
}
/// Represents an asset in the financial system
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Asset {
/// Base model data
pub base_data: BaseData,
#[index]
pub address: String,
pub assetid: u32,
pub asset_type: String,
pub issuer: u32,
pub supply: f64,
pub decimals: u8,
pub is_frozen: bool,
pub metadata: HashMap<String, String>,
pub administrators: Vec<u32>,
pub min_signatures: u32,
}
impl Asset {
/// Create a new asset instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
address: String::new(),
assetid: 0,
asset_type: String::new(),
issuer: 0,
supply: 0.0,
decimals: 0,
is_frozen: false,
metadata: HashMap::new(),
administrators: Vec::new(),
min_signatures: 0,
}
}
/// Set the blockchain address (fluent)
pub fn address(mut self, address: impl ToString) -> Self {
self.address = address.to_string();
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the asset type (fluent)
pub fn asset_type(mut self, asset_type: impl ToString) -> Self {
self.asset_type = asset_type.to_string();
self
}
/// Set the issuer (fluent)
pub fn issuer(mut self, issuer: u32) -> Self {
self.issuer = issuer;
self
}
/// Set the supply (fluent)
pub fn supply(mut self, supply: f64) -> Self {
self.supply = supply;
self
}
/// Set the decimals (fluent)
pub fn decimals(mut self, decimals: u8) -> Self {
self.decimals = decimals;
self
}
/// Set the frozen status (fluent)
pub fn is_frozen(mut self, is_frozen: bool) -> Self {
self.is_frozen = is_frozen;
self
}
/// Add metadata entry (fluent)
pub fn add_metadata(mut self, key: impl ToString, value: impl ToString) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
/// Set all metadata (fluent)
pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
self.metadata = metadata;
self
}
/// Add an administrator (fluent)
pub fn add_administrator(mut self, admin_id: u32) -> Self {
self.administrators.push(admin_id);
self
}
/// Set all administrators (fluent)
pub fn administrators(mut self, administrators: Vec<u32>) -> Self {
self.administrators = administrators;
self
}
/// Set minimum signatures required (fluent)
pub fn min_signatures(mut self, min_signatures: u32) -> Self {
self.min_signatures = min_signatures;
self
}
/// Build the final asset instance
pub fn build(self) -> Self {
self
}
}
/// Represents account policies for various operations
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct AccountPolicy {
/// Base model data
pub base_data: BaseData,
pub transferpolicy: AccountPolicyItem,
pub adminpolicy: AccountPolicyItem,
pub clawbackpolicy: AccountPolicyItem,
pub freezepolicy: AccountPolicyItem,
}
impl AccountPolicy {
/// Create a new account policy instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
transferpolicy: AccountPolicyItem::new(),
adminpolicy: AccountPolicyItem::new(),
clawbackpolicy: AccountPolicyItem::new(),
freezepolicy: AccountPolicyItem::new(),
}
}
/// Set the transfer policy (fluent)
pub fn transferpolicy(mut self, transferpolicy: AccountPolicyItem) -> Self {
self.transferpolicy = transferpolicy;
self
}
/// Set the admin policy (fluent)
pub fn adminpolicy(mut self, adminpolicy: AccountPolicyItem) -> Self {
self.adminpolicy = adminpolicy;
self
}
/// Set the clawback policy (fluent)
pub fn clawbackpolicy(mut self, clawbackpolicy: AccountPolicyItem) -> Self {
self.clawbackpolicy = clawbackpolicy;
self
}
/// Set the freeze policy (fluent)
pub fn freezepolicy(mut self, freezepolicy: AccountPolicyItem) -> Self {
self.freezepolicy = freezepolicy;
self
}
/// Build the final account policy instance
pub fn build(self) -> Self {
self
}
}
/// Represents a financial transaction
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, crate::DeriveObject)]
pub struct Transaction {
/// Base model data
pub base_data: BaseData,
pub txid: u32,
pub source: u32,
pub destination: u32,
pub assetid: u32,
pub amount: f64,
pub timestamp: u64,
pub status: String,
pub memo: String,
pub tx_type: TransactionType,
pub signatures: Vec<Signature>,
}
impl Transaction {
/// Create a new transaction instance
pub fn new(id: u32) -> Self {
let mut base_data = BaseData::new();
Self {
base_data,
txid: 0,
source: 0,
destination: 0,
assetid: 0,
amount: 0.0,
timestamp: 0,
status: String::new(),
memo: String::new(),
tx_type: TransactionType::default(),
signatures: Vec::new(),
}
}
/// Set the transaction ID (fluent)
pub fn txid(mut self, txid: u32) -> Self {
self.txid = txid;
self
}
/// Set the source account (fluent)
pub fn source(mut self, source: u32) -> Self {
self.source = source;
self
}
/// Set the destination account (fluent)
pub fn destination(mut self, destination: u32) -> Self {
self.destination = destination;
self
}
/// Set the asset ID (fluent)
pub fn assetid(mut self, assetid: u32) -> Self {
self.assetid = assetid;
self
}
/// Set the amount (fluent)
pub fn amount(mut self, amount: f64) -> Self {
self.amount = amount;
self
}
/// Set the timestamp (fluent)
pub fn timestamp(mut self, timestamp: u64) -> Self {
self.timestamp = timestamp;
self
}
/// Set the status (fluent)
pub fn status(mut self, status: impl ToString) -> Self {
self.status = status.to_string();
self
}
/// Set the memo (fluent)
pub fn memo(mut self, memo: impl ToString) -> Self {
self.memo = memo.to_string();
self
}
/// Set the transaction type (fluent)
pub fn tx_type(mut self, tx_type: TransactionType) -> Self {
self.tx_type = tx_type;
self
}
/// Add a signature (fluent)
pub fn add_signature(mut self, signature: Signature) -> Self {
self.signatures.push(signature);
self
}
/// Set all signatures (fluent)
pub fn signatures(mut self, signatures: Vec<Signature>) -> Self {
self.signatures = signatures;
self
}
/// Build the final transaction instance
pub fn build(self) -> Self {
self
}
}

View File

@@ -0,0 +1,457 @@
/// Payment Provider Client
///
/// Generic payment provider API client supporting multiple payment gateways.
/// Currently implements Pesapal API but designed to be extensible for other providers.
use serde::{Deserialize, Serialize};
use crate::store::{BaseData, IndexKey, Object};
// Helper to run async code synchronously
fn run_async<F, T>(future: F) -> T
where
F: std::future::Future<Output = T> + Send + 'static,
T: Send + 'static,
{
// Try to use current runtime handle if available
if tokio::runtime::Handle::try_current().is_ok() {
// We're in a runtime, spawn a blocking thread with its own runtime
std::thread::scope(|s| {
s.spawn(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(future)
}).join().unwrap()
})
} else {
// No runtime, create one
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(future)
}
}
/// Payment Provider Client for making API calls to payment gateways
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentClient {
/// Base data for object storage
pub base_data: BaseData,
/// Provider name (e.g., "pesapal", "stripe", "paypal", "flutterwave")
pub provider: String,
/// Consumer key / API key
pub consumer_key: String,
/// Consumer secret / API secret
pub consumer_secret: String,
/// Base URL for API (optional, uses provider default if not set)
pub base_url: Option<String>,
/// Sandbox mode (for testing)
pub sandbox: bool,
}
/// Payment request details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequest {
/// Unique merchant reference
pub merchant_reference: String,
/// Amount to charge
pub amount: f64,
/// Currency code (e.g., "USD", "KES", "UGX")
pub currency: String,
/// Description of the payment
pub description: String,
/// Callback URL for payment notifications
pub callback_url: String,
/// Redirect URL after payment (optional)
pub redirect_url: Option<String>,
/// Cancel URL (optional)
pub cancel_url: Option<String>,
/// Customer email
pub customer_email: Option<String>,
/// Customer phone
pub customer_phone: Option<String>,
/// Customer first name
pub customer_first_name: Option<String>,
/// Customer last name
pub customer_last_name: Option<String>,
}
/// Payment response from provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentResponse {
/// Payment link URL
pub payment_url: String,
/// Order tracking ID from provider
pub order_tracking_id: String,
/// Merchant reference
pub merchant_reference: String,
/// Status message
pub status: String,
}
/// Payment status query result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentStatus {
/// Order tracking ID
pub order_tracking_id: String,
/// Merchant reference
pub merchant_reference: String,
/// Payment status (e.g., "PENDING", "COMPLETED", "FAILED")
pub status: String,
/// Amount
pub amount: f64,
/// Currency
pub currency: String,
/// Payment method used
pub payment_method: Option<String>,
/// Transaction ID
pub transaction_id: Option<String>,
}
// Pesapal-specific structures
#[derive(Debug, Serialize)]
struct PesapalAuthRequest {
consumer_key: String,
consumer_secret: String,
}
#[derive(Debug, Deserialize)]
struct PesapalAuthResponse {
token: String,
#[serde(rename = "expiryDate")]
expiry_date: Option<serde_json::Value>,
error: Option<String>,
status: Option<String>,
message: Option<String>,
}
#[derive(Debug, Serialize)]
struct PesapalSubmitOrderRequest {
id: String,
currency: String,
amount: f64,
description: String,
callback_url: String,
redirect_mode: String,
notification_id: String,
billing_address: Option<PesapalBillingAddress>,
}
#[derive(Debug, Serialize)]
struct PesapalBillingAddress {
email_address: Option<String>,
phone_number: Option<String>,
first_name: Option<String>,
last_name: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PesapalSubmitOrderResponse {
order_tracking_id: Option<String>,
merchant_reference: Option<String>,
redirect_url: Option<String>,
error: Option<serde_json::Value>,
status: Option<String>,
}
#[derive(Debug, Deserialize)]
struct PesapalTransactionStatusResponse {
payment_method: Option<String>,
amount: f64,
created_date: String,
confirmation_code: Option<String>,
payment_status_description: String,
description: String,
message: String,
payment_account: Option<String>,
call_back_url: String,
status_code: i32,
merchant_reference: String,
payment_status_code: String,
currency: String,
error: Option<String>,
status: String,
}
impl PaymentClient {
/// Create a new payment client
pub fn new(id: u32, provider: String, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider,
consumer_key,
consumer_secret,
base_url: None,
sandbox: false,
}
}
/// Create a Pesapal client
pub fn pesapal(id: u32, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider: "pesapal".to_string(),
consumer_key,
consumer_secret,
base_url: Some("https://pay.pesapal.com/v3".to_string()),
sandbox: false,
}
}
/// Create a Pesapal sandbox client
pub fn pesapal_sandbox(id: u32, consumer_key: String, consumer_secret: String) -> Self {
let base_data = BaseData::with_id(id, String::new());
Self {
base_data,
provider: "pesapal".to_string(),
consumer_key,
consumer_secret,
base_url: Some("https://cybqa.pesapal.com/pesapalv3".to_string()),
sandbox: true,
}
}
/// Set custom base URL
pub fn with_base_url(mut self, base_url: String) -> Self {
self.base_url = Some(base_url);
self
}
/// Enable sandbox mode
pub fn with_sandbox(mut self, sandbox: bool) -> Self {
self.sandbox = sandbox;
self
}
/// Get the base URL for the provider
fn get_base_url(&self) -> String {
if let Some(url) = &self.base_url {
return url.clone();
}
match self.provider.as_str() {
"pesapal" => {
if self.sandbox {
"https://cybqa.pesapal.com/pesapalv3".to_string()
} else {
"https://pay.pesapal.com/v3".to_string()
}
}
"stripe" => "https://api.stripe.com/v1".to_string(),
"paypal" => "https://api.paypal.com/v2".to_string(),
"flutterwave" => "https://api.flutterwave.com/v3".to_string(),
_ => panic!("Unknown provider: {}", self.provider),
}
}
/// Create a payment link
pub fn create_payment_link(
&self,
request: &PaymentRequest,
) -> Result<PaymentResponse, String> {
match self.provider.as_str() {
"pesapal" => self.create_pesapal_payment(request),
_ => Err(format!("Provider {} not yet implemented", self.provider)),
}
}
/// Get payment status
pub fn get_payment_status(
&self,
order_tracking_id: &str,
) -> Result<PaymentStatus, String> {
match self.provider.as_str() {
"pesapal" => self.get_pesapal_status(order_tracking_id),
_ => Err(format!("Provider {} not yet implemented", self.provider)),
}
}
/// Authenticate with Pesapal and get access token
fn pesapal_authenticate(&self) -> Result<String, String> {
let url = format!("{}/api/Auth/RequestToken", self.get_base_url());
let auth_request = PesapalAuthRequest {
consumer_key: self.consumer_key.clone(),
consumer_secret: self.consumer_secret.clone(),
};
run_async(async move {
let client = reqwest::Client::new();
let response = client
.post(&url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.json(&auth_request)
.send()
.await
.map_err(|e| format!("Failed to send auth request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal auth failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read auth response: {}", e))?;
println!("=== PESAPAL AUTH RESPONSE ===");
println!("{}", response_text);
println!("==============================");
let auth_response: PesapalAuthResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse auth response: {}", e))?;
if let Some(error) = auth_response.error {
return Err(format!("Pesapal auth error: {}", error));
}
Ok(auth_response.token)
})
}
/// Create a Pesapal payment
fn create_pesapal_payment(
&self,
request: &PaymentRequest,
) -> Result<PaymentResponse, String> {
// Get auth token
let token = self.pesapal_authenticate()?;
let url = format!("{}/api/Transactions/SubmitOrderRequest", self.get_base_url());
let pesapal_request = PesapalSubmitOrderRequest {
id: request.merchant_reference.clone(),
currency: request.currency.clone(),
amount: request.amount,
description: request.description.clone(),
callback_url: request.callback_url.clone(),
redirect_mode: String::new(),
notification_id: String::new(),
billing_address: Some(PesapalBillingAddress {
email_address: request.customer_email.clone(),
phone_number: request.customer_phone.clone(),
first_name: request.customer_first_name.clone(),
last_name: request.customer_last_name.clone(),
}),
};
run_async(async move {
let client = reqwest::Client::new();
let response = client
.post(&url)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.bearer_auth(&token)
.json(&pesapal_request)
.send()
.await
.map_err(|e| format!("Failed to send payment request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal payment request failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read payment response: {}", e))?;
println!("=== PESAPAL PAYMENT RESPONSE ===");
println!("{}", response_text);
println!("=================================");
let pesapal_response: PesapalSubmitOrderResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse payment response: {}", e))?;
if let Some(error) = pesapal_response.error {
return Err(format!("Pesapal payment error: {}", error));
}
Ok(PaymentResponse {
payment_url: pesapal_response.redirect_url.unwrap_or_default(),
order_tracking_id: pesapal_response.order_tracking_id.unwrap_or_default(),
merchant_reference: pesapal_response.merchant_reference.unwrap_or_default(),
status: pesapal_response.status.unwrap_or_default(),
})
})
}
/// Get Pesapal payment status
fn get_pesapal_status(
&self,
order_tracking_id: &str,
) -> Result<PaymentStatus, String> {
let token = self.pesapal_authenticate()?;
let order_tracking_id = order_tracking_id.to_string();
let url = format!(
"{}/api/Transactions/GetTransactionStatus?orderTrackingId={}",
self.get_base_url(),
order_tracking_id
);
run_async(async move {
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("Accept", "application/json")
.bearer_auth(&token)
.send()
.await
.map_err(|e| format!("Failed to send status request: {}", e))?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(format!("Pesapal status request failed ({}): {}", status, error_text));
}
// Debug: print raw response
let response_text = response.text().await
.map_err(|e| format!("Failed to read status response: {}", e))?;
println!("=== PESAPAL STATUS RESPONSE ===");
println!("{}", response_text);
println!("================================");
let status_response: PesapalTransactionStatusResponse = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse status response: {}", e))?;
if let Some(error) = status_response.error {
return Err(format!("Pesapal status error: {}", error));
}
Ok(PaymentStatus {
order_tracking_id: order_tracking_id.to_string(),
merchant_reference: status_response.merchant_reference,
status: status_response.payment_status_description,
amount: status_response.amount,
currency: status_response.currency,
payment_method: status_response.payment_method,
transaction_id: status_response.confirmation_code,
})
})
}
}

630
src/objects/money/rhai.rs Normal file
View File

@@ -0,0 +1,630 @@
/// Rhai bindings for Money objects (Account, Asset, Transaction, PaymentClient)
use ::rhai::plugin::*;
use ::rhai::{CustomType, Dynamic, Engine, EvalAltResult, Module, TypeBuilder};
use super::models::{Account, Asset, Transaction};
use super::payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};
// ============================================================================
// Account Module
// ============================================================================
type RhaiAccount = Account;
#[export_module]
mod rhai_account_module {
use super::RhaiAccount;
#[rhai_fn(name = "new_account", return_raw)]
pub fn new_account() -> Result<RhaiAccount, Box<EvalAltResult>> {
Ok(Account::new(0))
}
#[rhai_fn(name = "owner_id", return_raw)]
pub fn set_owner_id(
account: &mut RhaiAccount,
owner_id: i64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.owner_id(owner_id as u32);
Ok(account.clone())
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
account: &mut RhaiAccount,
address: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.address(address);
Ok(account.clone())
}
#[rhai_fn(name = "balance", return_raw)]
pub fn set_balance(
account: &mut RhaiAccount,
balance: f64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.balance(balance);
Ok(account.clone())
}
#[rhai_fn(name = "currency", return_raw)]
pub fn set_currency(
account: &mut RhaiAccount,
currency: String,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.currency(currency);
Ok(account.clone())
}
#[rhai_fn(name = "assetid", return_raw)]
pub fn set_assetid(
account: &mut RhaiAccount,
assetid: i64,
) -> Result<RhaiAccount, Box<EvalAltResult>> {
let owned = std::mem::take(account);
*account = owned.assetid(assetid as u32);
Ok(account.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(account: &mut RhaiAccount) -> i64 {
account.base_data.id as i64
}
#[rhai_fn(name = "get_owner_id")]
pub fn get_owner_id(account: &mut RhaiAccount) -> i64 {
account.owner_id as i64
}
#[rhai_fn(name = "get_address")]
pub fn get_address(account: &mut RhaiAccount) -> String {
account.address.clone()
}
#[rhai_fn(name = "get_balance")]
pub fn get_balance(account: &mut RhaiAccount) -> f64 {
account.balance
}
#[rhai_fn(name = "get_currency")]
pub fn get_currency(account: &mut RhaiAccount) -> String {
account.currency.clone()
}
}
// ============================================================================
// Asset Module
// ============================================================================
type RhaiAsset = Asset;
#[export_module]
mod rhai_asset_module {
use super::RhaiAsset;
#[rhai_fn(name = "new_asset", return_raw)]
pub fn new_asset() -> Result<RhaiAsset, Box<EvalAltResult>> {
Ok(Asset::new(0))
}
#[rhai_fn(name = "address", return_raw)]
pub fn set_address(
asset: &mut RhaiAsset,
address: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.address(address);
Ok(asset.clone())
}
#[rhai_fn(name = "asset_type", return_raw)]
pub fn set_asset_type(
asset: &mut RhaiAsset,
asset_type: String,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.asset_type(asset_type);
Ok(asset.clone())
}
#[rhai_fn(name = "issuer", return_raw)]
pub fn set_issuer(
asset: &mut RhaiAsset,
issuer: i64,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.issuer(issuer as u32);
Ok(asset.clone())
}
#[rhai_fn(name = "supply", return_raw)]
pub fn set_supply(
asset: &mut RhaiAsset,
supply: f64,
) -> Result<RhaiAsset, Box<EvalAltResult>> {
let owned = std::mem::take(asset);
*asset = owned.supply(supply);
Ok(asset.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(asset: &mut RhaiAsset) -> i64 {
asset.base_data.id as i64
}
#[rhai_fn(name = "get_address")]
pub fn get_address(asset: &mut RhaiAsset) -> String {
asset.address.clone()
}
#[rhai_fn(name = "get_asset_type")]
pub fn get_asset_type(asset: &mut RhaiAsset) -> String {
asset.asset_type.clone()
}
#[rhai_fn(name = "get_supply")]
pub fn get_supply(asset: &mut RhaiAsset) -> f64 {
asset.supply
}
}
// ============================================================================
// Transaction Module
// ============================================================================
type RhaiTransaction = Transaction;
#[export_module]
mod rhai_transaction_module {
use super::RhaiTransaction;
#[rhai_fn(name = "new_transaction", return_raw)]
pub fn new_transaction() -> Result<RhaiTransaction, Box<EvalAltResult>> {
Ok(Transaction::new(0))
}
#[rhai_fn(name = "source", return_raw)]
pub fn set_source(
tx: &mut RhaiTransaction,
source: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.source(source as u32);
Ok(tx.clone())
}
#[rhai_fn(name = "destination", return_raw)]
pub fn set_destination(
tx: &mut RhaiTransaction,
destination: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.destination(destination as u32);
Ok(tx.clone())
}
#[rhai_fn(name = "amount", return_raw)]
pub fn set_amount(
tx: &mut RhaiTransaction,
amount: f64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.amount(amount);
Ok(tx.clone())
}
#[rhai_fn(name = "assetid", return_raw)]
pub fn set_assetid(
tx: &mut RhaiTransaction,
assetid: i64,
) -> Result<RhaiTransaction, Box<EvalAltResult>> {
let owned = std::mem::take(tx);
*tx = owned.assetid(assetid as u32);
Ok(tx.clone())
}
// Getters
#[rhai_fn(name = "get_id")]
pub fn get_id(tx: &mut RhaiTransaction) -> i64 {
tx.base_data.id as i64
}
#[rhai_fn(name = "get_source")]
pub fn get_source(tx: &mut RhaiTransaction) -> i64 {
tx.source as i64
}
#[rhai_fn(name = "get_destination")]
pub fn get_destination(tx: &mut RhaiTransaction) -> i64 {
tx.destination as i64
}
#[rhai_fn(name = "get_amount")]
pub fn get_amount(tx: &mut RhaiTransaction) -> f64 {
tx.amount
}
#[rhai_fn(name = "get_assetid")]
pub fn get_assetid(tx: &mut RhaiTransaction) -> i64 {
tx.assetid as i64
}
}
// ============================================================================
// Registration Functions
// ============================================================================
/// Register money modules with the Rhai engine
pub fn register_money_modules(parent_module: &mut Module) {
// Register custom types
parent_module.set_custom_type::<Account>("Account");
parent_module.set_custom_type::<Asset>("Asset");
parent_module.set_custom_type::<Transaction>("Transaction");
parent_module.set_custom_type::<PaymentClient>("PaymentClient");
parent_module.set_custom_type::<PaymentRequest>("PaymentRequest");
parent_module.set_custom_type::<PaymentResponse>("PaymentResponse");
parent_module.set_custom_type::<PaymentStatus>("PaymentStatus");
// Merge account functions
let account_module = exported_module!(rhai_account_module);
parent_module.merge(&account_module);
// Merge asset functions
let asset_module = exported_module!(rhai_asset_module);
parent_module.merge(&asset_module);
// Merge transaction functions
let transaction_module = exported_module!(rhai_transaction_module);
parent_module.merge(&transaction_module);
// Merge payment client functions
let payment_module = exported_module!(rhai_payment_module);
parent_module.merge(&payment_module);
// Merge ethereum wallet functions
let eth_module = exported_module!(rhai_ethereum_module);
parent_module.merge(&eth_module);
}
// ============================================================================
// Payment Provider Module
// ============================================================================
type RhaiPaymentClient = PaymentClient;
type RhaiPaymentRequest = PaymentRequest;
type RhaiPaymentResponse = PaymentResponse;
type RhaiPaymentStatus = PaymentStatus;
#[export_module]
mod rhai_payment_module {
use super::{RhaiPaymentClient, RhaiPaymentRequest, RhaiPaymentResponse, RhaiPaymentStatus};
use super::super::payments::{PaymentClient, PaymentRequest, PaymentResponse, PaymentStatus};
use ::rhai::EvalAltResult;
// PaymentClient constructors
#[rhai_fn(name = "new_payment_client_pesapal", return_raw)]
pub fn new_pesapal_client(
id: i64,
consumer_key: String,
consumer_secret: String,
) -> Result<RhaiPaymentClient, Box<EvalAltResult>> {
Ok(PaymentClient::pesapal(id as u32, consumer_key, consumer_secret))
}
#[rhai_fn(name = "new_payment_client_pesapal_sandbox", return_raw)]
pub fn new_pesapal_sandbox_client(
id: i64,
consumer_key: String,
consumer_secret: String,
) -> Result<RhaiPaymentClient, Box<EvalAltResult>> {
Ok(PaymentClient::pesapal_sandbox(id as u32, consumer_key, consumer_secret))
}
// PaymentRequest constructor and builder methods
#[rhai_fn(name = "new_payment_request", return_raw)]
pub fn new_payment_request() -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
Ok(PaymentRequest {
merchant_reference: String::new(),
amount: 0.0,
currency: String::from("USD"),
description: String::new(),
callback_url: String::new(),
redirect_url: None,
cancel_url: None,
customer_email: None,
customer_phone: None,
customer_first_name: None,
customer_last_name: None,
})
}
#[rhai_fn(name = "amount", return_raw)]
pub fn set_amount(
request: &mut RhaiPaymentRequest,
amount: f64,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.amount = amount;
Ok(request.clone())
}
#[rhai_fn(name = "currency", return_raw)]
pub fn set_currency(
request: &mut RhaiPaymentRequest,
currency: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.currency = currency;
Ok(request.clone())
}
#[rhai_fn(name = "description", return_raw)]
pub fn set_description(
request: &mut RhaiPaymentRequest,
description: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.description = description;
Ok(request.clone())
}
#[rhai_fn(name = "callback_url", return_raw)]
pub fn set_callback_url(
request: &mut RhaiPaymentRequest,
url: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.callback_url = url;
Ok(request.clone())
}
#[rhai_fn(name = "merchant_reference", return_raw)]
pub fn set_merchant_reference(
request: &mut RhaiPaymentRequest,
reference: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.merchant_reference = reference;
Ok(request.clone())
}
#[rhai_fn(name = "customer_email", return_raw)]
pub fn set_customer_email(
request: &mut RhaiPaymentRequest,
email: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_email = Some(email);
Ok(request.clone())
}
#[rhai_fn(name = "customer_phone", return_raw)]
pub fn set_customer_phone(
request: &mut RhaiPaymentRequest,
phone: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_phone = Some(phone);
Ok(request.clone())
}
#[rhai_fn(name = "customer_name", return_raw)]
pub fn set_customer_name(
request: &mut RhaiPaymentRequest,
first_name: String,
last_name: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.customer_first_name = Some(first_name);
request.customer_last_name = Some(last_name);
Ok(request.clone())
}
#[rhai_fn(name = "redirect_url", return_raw)]
pub fn set_redirect_url(
request: &mut RhaiPaymentRequest,
url: String,
) -> Result<RhaiPaymentRequest, Box<EvalAltResult>> {
request.redirect_url = Some(url);
Ok(request.clone())
}
// PaymentClient methods
#[rhai_fn(name = "create_payment_link", return_raw)]
pub fn create_payment_link(
client: &mut RhaiPaymentClient,
request: RhaiPaymentRequest,
) -> Result<RhaiPaymentResponse, Box<EvalAltResult>> {
client.create_payment_link(&request)
.map_err(|e| e.into())
}
#[rhai_fn(name = "get_payment_status", return_raw)]
pub fn get_payment_status(
client: &mut RhaiPaymentClient,
order_tracking_id: String,
) -> Result<RhaiPaymentStatus, Box<EvalAltResult>> {
client.get_payment_status(&order_tracking_id)
.map_err(|e| e.into())
}
// PaymentResponse getters
#[rhai_fn(name = "get_payment_url", pure)]
pub fn get_payment_url(response: &mut RhaiPaymentResponse) -> String {
response.payment_url.clone()
}
#[rhai_fn(name = "get_order_tracking_id", pure)]
pub fn get_order_tracking_id(response: &mut RhaiPaymentResponse) -> String {
response.order_tracking_id.clone()
}
#[rhai_fn(name = "get_merchant_reference", pure)]
pub fn get_merchant_reference(response: &mut RhaiPaymentResponse) -> String {
response.merchant_reference.clone()
}
#[rhai_fn(name = "get_status", pure)]
pub fn get_response_status(response: &mut RhaiPaymentResponse) -> String {
response.status.clone()
}
// PaymentStatus getters
#[rhai_fn(name = "get_status", pure)]
pub fn get_payment_status_value(status: &mut RhaiPaymentStatus) -> String {
status.status.clone()
}
#[rhai_fn(name = "get_amount", pure)]
pub fn get_amount(status: &mut RhaiPaymentStatus) -> f64 {
status.amount
}
#[rhai_fn(name = "get_currency", pure)]
pub fn get_currency(status: &mut RhaiPaymentStatus) -> String {
status.currency.clone()
}
#[rhai_fn(name = "get_payment_method", pure)]
pub fn get_payment_method(status: &mut RhaiPaymentStatus) -> String {
status.payment_method.clone().unwrap_or_default()
}
#[rhai_fn(name = "get_transaction_id", pure)]
pub fn get_transaction_id(status: &mut RhaiPaymentStatus) -> String {
status.transaction_id.clone().unwrap_or_default()
}
}
// ============================================================================
// CustomType Implementations
// ============================================================================
impl CustomType for Account {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Account");
}
}
impl CustomType for Asset {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Asset");
}
}
impl CustomType for Transaction {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("Transaction");
}
}
impl CustomType for PaymentClient {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentClient");
}
}
impl CustomType for PaymentRequest {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentRequest");
}
}
impl CustomType for PaymentResponse {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentResponse");
}
}
impl CustomType for PaymentStatus {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("PaymentStatus");
}
}
// ============================================================================
// Ethereum Wallet Module (Stub Implementation)
// ============================================================================
/// Simple Ethereum wallet representation
#[derive(Debug, Clone, Default)]
pub struct EthereumWallet {
pub owner_id: u32,
pub address: String,
pub network: String,
}
impl EthereumWallet {
pub fn new() -> Self {
// Generate a mock Ethereum address (in production, use ethers-rs or similar)
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let mock_address = format!("0x{:040x}", timestamp as u128);
Self {
owner_id: 0,
address: mock_address,
network: String::from("mainnet"),
}
}
pub fn owner_id(mut self, id: u32) -> Self {
self.owner_id = id;
self
}
pub fn network(mut self, network: impl ToString) -> Self {
self.network = network.to_string();
self
}
}
type RhaiEthereumWallet = EthereumWallet;
#[export_module]
mod rhai_ethereum_module {
use super::RhaiEthereumWallet;
use ::rhai::EvalAltResult;
#[rhai_fn(name = "new_ethereum_wallet", return_raw)]
pub fn new_ethereum_wallet() -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
Ok(EthereumWallet::new())
}
#[rhai_fn(name = "owner_id", return_raw)]
pub fn set_owner_id(
wallet: &mut RhaiEthereumWallet,
owner_id: i64,
) -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
let owned = std::mem::take(wallet);
*wallet = owned.owner_id(owner_id as u32);
Ok(wallet.clone())
}
#[rhai_fn(name = "network", return_raw)]
pub fn set_network(
wallet: &mut RhaiEthereumWallet,
network: String,
) -> Result<RhaiEthereumWallet, Box<EvalAltResult>> {
let owned = std::mem::take(wallet);
*wallet = owned.network(network);
Ok(wallet.clone())
}
#[rhai_fn(name = "get_address")]
pub fn get_address(wallet: &mut RhaiEthereumWallet) -> String {
wallet.address.clone()
}
#[rhai_fn(name = "get_network")]
pub fn get_network(wallet: &mut RhaiEthereumWallet) -> String {
wallet.network.clone()
}
}
impl CustomType for EthereumWallet {
fn build(mut builder: TypeBuilder<Self>) {
builder.with_name("EthereumWallet");
}
}

View File

@@ -26,7 +26,7 @@ impl Note {
/// Create a new note /// Create a new note
pub fn new(ns: String) -> Self { pub fn new(ns: String) -> Self {
Self { Self {
base_data: BaseData::new(ns), base_data: BaseData::with_ns(ns),
title: None, title: None,
content: None, content: None,
tags: BTreeMap::new(), tags: BTreeMap::new(),
@@ -35,8 +35,9 @@ impl Note {
/// Create a note with specific ID /// Create a note with specific ID
pub fn with_id(id: String, ns: String) -> Self { pub fn with_id(id: String, ns: String) -> Self {
let id_u32 = id.parse::<u32>().unwrap_or(0);
Self { Self {
base_data: BaseData::with_id(id, ns), base_data: BaseData::with_id(id_u32, ns),
title: None, title: None,
content: None, content: None,
tags: BTreeMap::new(), tags: BTreeMap::new(),

View File

@@ -6,7 +6,7 @@ use time::OffsetDateTime;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct BaseData { pub struct BaseData {
/// Unique ID (auto-generated or user-assigned) /// Unique ID (auto-generated or user-assigned)
pub id: String, pub id: u32,
/// Namespace this object belongs to /// Namespace this object belongs to
pub ns: String, pub ns: String,
@@ -27,12 +27,25 @@ pub struct BaseData {
} }
impl BaseData { impl BaseData {
/// Create new base data with generated UUID /// Create new base data with ID 0 (no namespace required)
pub fn new(ns: String) -> Self { pub fn new() -> Self {
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
Self { Self {
id: uuid::Uuid::new_v4().to_string(), id: 0,
ns, ns: String::new(),
created_at: now,
modified_at: now,
mime: None,
size: None,
}
}
/// Create new base data with namespace
pub fn with_ns(ns: impl ToString) -> Self {
let now = OffsetDateTime::now_utc();
Self {
id: 0,
ns: ns.to_string(),
created_at: now, created_at: now,
modified_at: now, modified_at: now,
mime: None, mime: None,
@@ -41,7 +54,7 @@ impl BaseData {
} }
/// Create new base data with specific ID /// Create new base data with specific ID
pub fn with_id(id: String, ns: String) -> Self { pub fn with_id(id: u32, ns: String) -> Self {
let now = OffsetDateTime::now_utc(); let now = OffsetDateTime::now_utc();
Self { Self {
id, id,
@@ -73,6 +86,6 @@ impl BaseData {
impl Default for BaseData { impl Default for BaseData {
fn default() -> Self { fn default() -> Self {
Self::new(String::from("default")) Self::new()
} }
} }

View File

@@ -73,12 +73,12 @@ impl GenericStore {
for key in index_keys { for key in index_keys {
let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value); let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value);
self.client.sadd(&field_key, obj.id()).await?; self.client.sadd(&field_key, &obj.id().to_string()).await?;
} }
// Add to scan index for full-text search // Add to scan index for full-text search
let scan_key = format!("scan:{}", obj.namespace()); let scan_key = format!("scan:{}", obj.namespace());
self.client.sadd(&scan_key, obj.id()).await?; self.client.sadd(&scan_key, &obj.id().to_string()).await?;
Ok(()) Ok(())
} }
@@ -89,12 +89,12 @@ impl GenericStore {
for key in index_keys { for key in index_keys {
let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value); let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value);
self.client.srem(&field_key, obj.id()).await?; self.client.srem(&field_key, &obj.id().to_string()).await?;
} }
// Remove from scan index // Remove from scan index
let scan_key = format!("scan:{}", obj.namespace()); let scan_key = format!("scan:{}", obj.namespace());
self.client.srem(&scan_key, obj.id()).await?; self.client.srem(&scan_key, &obj.id().to_string()).await?;
Ok(()) Ok(())
} }

View File

@@ -37,13 +37,13 @@ pub trait Object: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send +
fn base_data_mut(&mut self) -> &mut BaseData; fn base_data_mut(&mut self) -> &mut BaseData;
/// Get the unique ID for this object /// Get the unique ID for this object
fn id(&self) -> &str { fn id(&self) -> u32 {
&self.base_data().id self.base_data().id
} }
/// Set the unique ID for this object /// Set the unique ID for this object
fn set_id(&mut self, id: impl ToString) { fn set_id(&mut self, id: u32) {
self.base_data_mut().id = id.to_string(); self.base_data_mut().id = id;
} }
/// Get the namespace for this object /// Get the namespace for this object