wip
This commit is contained in:
10
Cargo.toml
10
Cargo.toml
@@ -11,6 +11,14 @@ path = "src/lib.rs"
|
||||
name = "runner"
|
||||
path = "src/bin/runner.rs"
|
||||
|
||||
[[example]]
|
||||
name = "engine"
|
||||
path = "examples/engine/main.rs"
|
||||
|
||||
[[example]]
|
||||
name = "freezone"
|
||||
path = "examples/freezone/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
redis = { version = "0.24", features = ["aio", "tokio-comp"] }
|
||||
@@ -24,8 +32,10 @@ uuid = { version = "1.6", features = ["v4", "serde"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
osiris_derive = { path = "osiris_derive" }
|
||||
lettre = "0.11"
|
||||
rhai = { version = "1.21.0", features = ["std", "sync", "serde"] }
|
||||
env_logger = "0.10"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8"
|
||||
|
||||
232
docs/CREATING_NEW_OBJECTS.md
Normal file
232
docs/CREATING_NEW_OBJECTS.md
Normal 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`
|
||||
113
docs/FREEZONE_IMPLEMENTATION_TODO.md
Normal file
113
docs/FREEZONE_IMPLEMENTATION_TODO.md
Normal 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
|
||||
53
examples/engine/01_note.rhai
Normal file
53
examples/engine/01_note.rhai
Normal 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 ===");
|
||||
44
examples/engine/02_event.rhai
Normal file
44
examples/engine/02_event.rhai
Normal 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 ===");
|
||||
54
examples/engine/03_user.rhai
Normal file
54
examples/engine/03_user.rhai
Normal 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 ===");
|
||||
54
examples/engine/04_group.rhai
Normal file
54
examples/engine/04_group.rhai
Normal 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 ===");
|
||||
55
examples/engine/05_account.rhai
Normal file
55
examples/engine/05_account.rhai
Normal 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 ===");
|
||||
50
examples/engine/06_dnszone.rhai
Normal file
50
examples/engine/06_dnszone.rhai
Normal 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
104
examples/engine/07_kyc.rhai
Normal 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 ===");
|
||||
90
examples/engine/08_flow.rhai
Normal file
90
examples/engine/08_flow.rhai
Normal 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 ===");
|
||||
119
examples/engine/09_money.rhai
Normal file
119
examples/engine/09_money.rhai
Normal 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)");
|
||||
103
examples/engine/10_email.rhai
Normal file
103
examples/engine/10_email.rhai
Normal 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 ===");
|
||||
104
examples/engine/11_payments.rhai
Normal file
104
examples/engine/11_payments.rhai
Normal 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");
|
||||
95
examples/engine/12_contract.rhai
Normal file
95
examples/engine/12_contract.rhai
Normal 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
84
examples/engine/main.rs
Normal 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(())
|
||||
}
|
||||
365
examples/freezone/freezone.rhai
Normal file
365
examples/freezone/freezone.rhai
Normal 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
48
examples/freezone/main.rs
Normal 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(())
|
||||
}
|
||||
@@ -9,6 +9,12 @@
|
||||
/// - Generic CRUD operations for any data
|
||||
|
||||
use crate::objects::Note;
|
||||
use crate::objects::heroledger::{
|
||||
user::User,
|
||||
group::Group,
|
||||
money::Account,
|
||||
dnsrecord::DNSZone,
|
||||
};
|
||||
use crate::store::{GenericStore, HeroDbClient};
|
||||
use rhai::{CustomType, EvalAltResult, TypeBuilder};
|
||||
use std::sync::Arc;
|
||||
@@ -117,7 +123,8 @@ impl OsirisContext {
|
||||
tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(async move {
|
||||
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);
|
||||
|
||||
store.put(¬e).await
|
||||
@@ -227,30 +234,19 @@ impl OsirisContext {
|
||||
}
|
||||
|
||||
impl OsirisContext {
|
||||
/// Save a Note object (typed)
|
||||
pub fn save_note(&self, note: Note) -> Result<String, Box<EvalAltResult>> {
|
||||
/// Generic save method for any Storable object
|
||||
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 id = note.base_data.id.clone();
|
||||
let id = object.base_data().id;
|
||||
|
||||
tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(async move {
|
||||
store.put(¬e).await
|
||||
.map_err(|e| format!("Failed to save note: {}", e))?;
|
||||
Ok(id)
|
||||
})
|
||||
}).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)
|
||||
store.put(&object).await
|
||||
.map_err(|e| format!("Failed to save object: {}", e))?;
|
||||
Ok(id.to_string())
|
||||
})
|
||||
}).map_err(|e: String| e.into())
|
||||
}
|
||||
@@ -264,9 +260,13 @@ impl CustomType for OsirisContext {
|
||||
.with_fn("context_id", |ctx: &mut OsirisContext| ctx.context_id())
|
||||
// Generic CRUD (with collection name)
|
||||
.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)
|
||||
.with_fn("save", |ctx: &mut OsirisContext, note: Note| ctx.save_note(note))
|
||||
.with_fn("save", |ctx: &mut OsirisContext, event: crate::objects::Event| ctx.save_event(event))
|
||||
// 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_object(note))
|
||||
.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("delete", |ctx: &mut OsirisContext, collection: String, id: String| ctx.delete(collection, id))
|
||||
.with_fn("list", |ctx: &mut OsirisContext, collection: String| ctx.list(collection))
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
use crate::context::OsirisContext;
|
||||
use crate::objects::note::rhai::register_note_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::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));
|
||||
if !has_signatory {
|
||||
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()
|
||||
)));
|
||||
}
|
||||
@@ -63,15 +69,76 @@ pub fn register_context_api(engine: &mut rhai::Engine) {
|
||||
def_package! {
|
||||
/// OSIRIS package with all OSIRIS types and functions
|
||||
pub OsirisPackage(module) : StandardPackage {
|
||||
// Register OsirisContext type
|
||||
// Register OsirisContext type with all its methods
|
||||
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(module);
|
||||
|
||||
// Register Event functions
|
||||
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
|
||||
FuncRegistration::new("get_context")
|
||||
.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)
|
||||
pub fn create_osiris_engine() -> Result<Engine, Box<dyn std::error::Error>> {
|
||||
let mut engine = Engine::new_raw();
|
||||
let package = OsirisPackage::new();
|
||||
package.register_into_engine(&mut engine);
|
||||
register_osiris_full(&mut engine);
|
||||
Ok(engine)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ pub use objects::{Event, Note};
|
||||
pub use context::{OsirisContext, OsirisInstance, OsirisContextBuilder};
|
||||
pub use engine::{
|
||||
create_osiris_engine,
|
||||
register_osiris_full,
|
||||
OsirisPackage,
|
||||
};
|
||||
|
||||
|
||||
151
src/objects/accounting/expense.rs
Normal file
151
src/objects/accounting/expense.rs
Normal 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
|
||||
}
|
||||
}
|
||||
130
src/objects/accounting/invoice.rs
Normal file
130
src/objects/accounting/invoice.rs
Normal 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
|
||||
}
|
||||
}
|
||||
11
src/objects/accounting/mod.rs
Normal file
11
src/objects/accounting/mod.rs
Normal 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;
|
||||
0
src/objects/accounting/rhai.rs
Normal file
0
src/objects/accounting/rhai.rs
Normal file
316
src/objects/communication/email.rs
Normal file
316
src/objects/communication/email.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
10
src/objects/communication/mod.rs
Normal file
10
src/objects/communication/mod.rs
Normal 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;
|
||||
180
src/objects/communication/rhai.rs
Normal file
180
src/objects/communication/rhai.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
239
src/objects/communication/verification.rs
Normal file
239
src/objects/communication/verification.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
155
src/objects/communication/verification_old.rs
Normal file
155
src/objects/communication/verification_old.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ impl Event {
|
||||
pub fn new(ns: String, title: impl ToString) -> Self {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Self {
|
||||
base_data: BaseData::new(ns),
|
||||
base_data: BaseData::with_ns(ns),
|
||||
title: title.to_string(),
|
||||
description: None,
|
||||
start_time: now,
|
||||
@@ -71,8 +71,9 @@ impl Event {
|
||||
/// Create an event with specific ID
|
||||
pub fn with_id(id: String, ns: String, title: impl ToString) -> Self {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let id_u32 = id.parse::<u32>().unwrap_or(0);
|
||||
Self {
|
||||
base_data: BaseData::with_id(id, ns),
|
||||
base_data: BaseData::with_id(id_u32, ns),
|
||||
title: title.to_string(),
|
||||
description: None,
|
||||
start_time: now,
|
||||
|
||||
241
src/objects/flow/instance.rs
Normal file
241
src/objects/flow/instance.rs
Normal 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
10
src/objects/flow/mod.rs
Normal 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
183
src/objects/flow/rhai.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
117
src/objects/flow/template.rs
Normal file
117
src/objects/flow/template.rs
Normal 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
126
src/objects/grid4/bid.rs
Normal 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
|
||||
}
|
||||
}
|
||||
39
src/objects/grid4/common.rs
Normal file
39
src/objects/grid4/common.rs
Normal 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 }
|
||||
}
|
||||
217
src/objects/grid4/contract.rs
Normal file
217
src/objects/grid4/contract.rs
Normal 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
18
src/objects/grid4/mod.rs
Normal 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
279
src/objects/grid4/node.rs
Normal 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
|
||||
}
|
||||
}
|
||||
50
src/objects/grid4/nodegroup.rs
Normal file
50
src/objects/grid4/nodegroup.rs
Normal 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 }
|
||||
}
|
||||
83
src/objects/grid4/reputation.rs
Normal file
83
src/objects/grid4/reputation.rs
Normal 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
|
||||
}
|
||||
}
|
||||
56
src/objects/grid4/reservation.rs
Normal file
56
src/objects/grid4/reservation.rs
Normal 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 }
|
||||
}
|
||||
194
src/objects/grid4/specs/README.md
Normal file
194
src/objects/grid4/specs/README.md
Normal 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]) |
|
||||
|
||||
|
||||
37
src/objects/grid4/specs/model_bid.v
Normal file
37
src/objects/grid4/specs/model_bid.v
Normal 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
|
||||
}
|
||||
52
src/objects/grid4/specs/model_contract.v
Normal file
52
src/objects/grid4/specs/model_contract.v
Normal 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
|
||||
}
|
||||
|
||||
|
||||
104
src/objects/grid4/specs/model_node.v
Normal file
104
src/objects/grid4/specs/model_node.v
Normal 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
|
||||
}
|
||||
33
src/objects/grid4/specs/model_nodegroup.v
Normal file
33
src/objects/grid4/specs/model_nodegroup.v
Normal 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
19
src/objects/grid4/specs/model_reputation.v
Normal file
19
src/objects/grid4/specs/model_reputation.v
Normal 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
311
src/objects/heroledger/dnsrecord.rs
Normal file
311
src/objects/heroledger/dnsrecord.rs
Normal 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
|
||||
}
|
||||
}
|
||||
227
src/objects/heroledger/group.rs
Normal file
227
src/objects/heroledger/group.rs
Normal 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
|
||||
}
|
||||
}
|
||||
110
src/objects/heroledger/membership.rs
Normal file
110
src/objects/heroledger/membership.rs
Normal 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
|
||||
}
|
||||
}
|
||||
10
src/objects/heroledger/mod.rs
Normal file
10
src/objects/heroledger/mod.rs
Normal 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;
|
||||
498
src/objects/heroledger/money.rs
Normal file
498
src/objects/heroledger/money.rs
Normal 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
|
||||
}
|
||||
}
|
||||
364
src/objects/heroledger/rhai.rs
Normal file
364
src/objects/heroledger/rhai.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
137
src/objects/heroledger/secretbox.rs
Normal file
137
src/objects/heroledger/secretbox.rs
Normal 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
|
||||
}
|
||||
}
|
||||
115
src/objects/heroledger/signature.rs
Normal file
115
src/objects/heroledger/signature.rs
Normal 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
|
||||
}
|
||||
}
|
||||
365
src/objects/heroledger/user.rs
Normal file
365
src/objects/heroledger/user.rs
Normal 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
|
||||
}
|
||||
}
|
||||
111
src/objects/heroledger/user_kvs.rs
Normal file
111
src/objects/heroledger/user_kvs.rs
Normal 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
238
src/objects/kyc/client.rs
Normal 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
319
src/objects/kyc/info.rs
Normal 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
13
src/objects/kyc/mod.rs
Normal 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
326
src/objects/kyc/rhai.rs
Normal 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
186
src/objects/kyc/session.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
129
src/objects/legal/contract.rs
Normal file
129
src/objects/legal/contract.rs
Normal 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
7
src/objects/legal/mod.rs
Normal 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
150
src/objects/legal/rhai.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,17 @@
|
||||
pub mod note;
|
||||
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 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
10
src/objects/money/mod.rs
Normal 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
498
src/objects/money/models.rs
Normal 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
|
||||
}
|
||||
}
|
||||
457
src/objects/money/payments.rs
Normal file
457
src/objects/money/payments.rs
Normal 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
630
src/objects/money/rhai.rs
Normal 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(ð_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");
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ impl Note {
|
||||
/// Create a new note
|
||||
pub fn new(ns: String) -> Self {
|
||||
Self {
|
||||
base_data: BaseData::new(ns),
|
||||
base_data: BaseData::with_ns(ns),
|
||||
title: None,
|
||||
content: None,
|
||||
tags: BTreeMap::new(),
|
||||
@@ -35,8 +35,9 @@ impl Note {
|
||||
|
||||
/// Create a note with specific ID
|
||||
pub fn with_id(id: String, ns: String) -> Self {
|
||||
let id_u32 = id.parse::<u32>().unwrap_or(0);
|
||||
Self {
|
||||
base_data: BaseData::with_id(id, ns),
|
||||
base_data: BaseData::with_id(id_u32, ns),
|
||||
title: None,
|
||||
content: None,
|
||||
tags: BTreeMap::new(),
|
||||
|
||||
@@ -6,7 +6,7 @@ use time::OffsetDateTime;
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct BaseData {
|
||||
/// Unique ID (auto-generated or user-assigned)
|
||||
pub id: String,
|
||||
pub id: u32,
|
||||
|
||||
/// Namespace this object belongs to
|
||||
pub ns: String,
|
||||
@@ -27,12 +27,25 @@ pub struct BaseData {
|
||||
}
|
||||
|
||||
impl BaseData {
|
||||
/// Create new base data with generated UUID
|
||||
pub fn new(ns: String) -> Self {
|
||||
/// Create new base data with ID 0 (no namespace required)
|
||||
pub fn new() -> Self {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Self {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
ns,
|
||||
id: 0,
|
||||
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,
|
||||
modified_at: now,
|
||||
mime: None,
|
||||
@@ -41,7 +54,7 @@ impl BaseData {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
Self {
|
||||
id,
|
||||
@@ -73,6 +86,6 @@ impl BaseData {
|
||||
|
||||
impl Default for BaseData {
|
||||
fn default() -> Self {
|
||||
Self::new(String::from("default"))
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ impl GenericStore {
|
||||
|
||||
for key in index_keys {
|
||||
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
|
||||
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(())
|
||||
}
|
||||
@@ -89,12 +89,12 @@ impl GenericStore {
|
||||
|
||||
for key in index_keys {
|
||||
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
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -37,13 +37,13 @@ pub trait Object: Debug + Clone + Serialize + for<'de> Deserialize<'de> + Send +
|
||||
fn base_data_mut(&mut self) -> &mut BaseData;
|
||||
|
||||
/// Get the unique ID for this object
|
||||
fn id(&self) -> &str {
|
||||
&self.base_data().id
|
||||
fn id(&self) -> u32 {
|
||||
self.base_data().id
|
||||
}
|
||||
|
||||
/// Set the unique ID for this object
|
||||
fn set_id(&mut self, id: impl ToString) {
|
||||
self.base_data_mut().id = id.to_string();
|
||||
fn set_id(&mut self, id: u32) {
|
||||
self.base_data_mut().id = id;
|
||||
}
|
||||
|
||||
/// Get the namespace for this object
|
||||
|
||||
Reference in New Issue
Block a user