Merge branch 'development_rhai'
This commit is contained in:
@@ -68,10 +68,26 @@ fn main() {
|
||||
.build();
|
||||
|
||||
// Save all users to database and get their assigned IDs and updated models
|
||||
let (user1_id, db_user1) = db.collection().expect("can open user collection").set(&user1).expect("can set user");
|
||||
let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user");
|
||||
let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user");
|
||||
let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user");
|
||||
let (user1_id, db_user1) = db
|
||||
.collection()
|
||||
.expect("can open user collection")
|
||||
.set(&user1)
|
||||
.expect("can set user");
|
||||
let (user2_id, db_user2) = db
|
||||
.collection()
|
||||
.expect("can open user collection")
|
||||
.set(&user2)
|
||||
.expect("can set user");
|
||||
let (user3_id, db_user3) = db
|
||||
.collection()
|
||||
.expect("can open user collection")
|
||||
.set(&user3)
|
||||
.expect("can set user");
|
||||
let (user4_id, db_user4) = db
|
||||
.collection()
|
||||
.expect("can open user collection")
|
||||
.set(&user4)
|
||||
.expect("can set user");
|
||||
|
||||
println!("User 1 assigned ID: {}", user1_id);
|
||||
println!("User 2 assigned ID: {}", user2_id);
|
||||
@@ -170,7 +186,8 @@ fn main() {
|
||||
.build();
|
||||
|
||||
// Save the comment and get its assigned ID and updated model
|
||||
let (comment_id, db_comment) = db.collection()
|
||||
let (comment_id, db_comment) = db
|
||||
.collection()
|
||||
.expect("can open comment collection")
|
||||
.set(&comment)
|
||||
.expect("can set comment");
|
||||
@@ -186,7 +203,8 @@ fn main() {
|
||||
updated_user.base_data.add_comment(db_comment.get_id());
|
||||
|
||||
// Save the updated user and get the new version
|
||||
let (_, user_with_comment) = db.collection::<User>()
|
||||
let (_, user_with_comment) = db
|
||||
.collection::<User>()
|
||||
.expect("can open user collection")
|
||||
.set(&updated_user)
|
||||
.expect("can set updated user");
|
||||
|
@@ -1,306 +0,0 @@
|
||||
// Hero Models - Biz Rhai Example
|
||||
|
||||
print("Hero Models - Biz Rhai Example");
|
||||
print("===============================");
|
||||
print("DB instance will be implicitly passed to DB functions.");
|
||||
|
||||
// --- Enum Constants ---
|
||||
print("\n--- Enum Constants ---");
|
||||
print(`CompanyStatus PendingPayment: ${CompanyStatusConstants::PendingPayment}`);
|
||||
print(`CompanyStatus Active: ${CompanyStatusConstants::Active}`);
|
||||
print(`CompanyStatus Suspended: ${CompanyStatusConstants::Suspended}`);
|
||||
print(`CompanyStatus Inactive: ${CompanyStatusConstants::Inactive}`);
|
||||
print(`BusinessType Coop: ${BusinessTypeConstants::Coop}`);
|
||||
print(`BusinessType Global: ${BusinessTypeConstants::Global}`);
|
||||
|
||||
// --- Testing Company Model ---
|
||||
print("\n--- Testing Company Model ---");
|
||||
|
||||
let company1_uuid = "comp-uuid-alpha-001";
|
||||
let company1_name = "Innovatech Solutions Ltd.";
|
||||
let company1_reg = "REG-ISL-2024";
|
||||
let company1_inc_date = 1672531200; // Jan 1, 2023
|
||||
|
||||
print(`Creating a new company (UUID: ${company1_uuid})...`);
|
||||
let company1 = new_company(company1_name, company1_reg, company1_inc_date)
|
||||
.email("contact@innovatech.com")
|
||||
.phone("+1-555-0100")
|
||||
.website("https://innovatech.com")
|
||||
.address("123 Innovation Drive, Tech City, TC 54321")
|
||||
.business_type(BusinessTypeConstants::Global)
|
||||
.industry("Technology")
|
||||
.description("Leading provider of innovative tech solutions.")
|
||||
// Note: status defaults to PendingPayment for new companies
|
||||
.fiscal_year_end("12-31")
|
||||
.set_base_created_at(1672531200)
|
||||
.set_base_modified_at(1672531205);
|
||||
|
||||
print(`Company 1 Name: ${company1.name}, Status: ${company1.status} (default for new companies)`);
|
||||
print(`Company 1 Email: ${company1.email}, Industry: ${company1.industry}`);
|
||||
// Save the company to the database
|
||||
print("\nSaving company1 to database...");
|
||||
company1 = set_company(company1); // Capture the company with the DB-assigned ID
|
||||
print("Company1 saved.");
|
||||
|
||||
// Demonstrate payment flow for the company
|
||||
print("\n--- Payment Processing for Company ---");
|
||||
print("Creating payment record for company registration...");
|
||||
let payment_intent_id = `pi_demo_${company1.id}`;
|
||||
let payment = new_payment(
|
||||
payment_intent_id,
|
||||
company1.id,
|
||||
"yearly",
|
||||
500.0, // Setup fee
|
||||
99.0, // Monthly fee
|
||||
1688.0 // Total amount (setup + 12 months)
|
||||
);
|
||||
|
||||
payment = set_payment(payment);
|
||||
print(`Payment created: ${payment.payment_intent_id}, Status: ${payment.status}`);
|
||||
|
||||
// Simulate successful payment processing
|
||||
print("Processing payment...");
|
||||
payment = payment.complete_payment(`cus_demo_${company1.id}`);
|
||||
payment = set_payment(payment);
|
||||
print(`Payment completed: ${payment.status}`);
|
||||
|
||||
// Update company status to Active after successful payment
|
||||
print("Updating company status to Active after payment...");
|
||||
company1 = company1.status(CompanyStatusConstants::Active);
|
||||
company1 = set_company(company1);
|
||||
print(`Company status updated: ${company1.status}`);
|
||||
|
||||
// Retrieve the company
|
||||
print(`\nRetrieving company by ID (${company1.id})...`);
|
||||
let retrieved_company = get_company_by_id(company1.id);
|
||||
print(`Retrieved Company: ${retrieved_company.name}, Status: ${retrieved_company.status}`);
|
||||
print(`Retrieved Company Reg No: ${retrieved_company.registration_number}, Website: ${retrieved_company.website}`);
|
||||
|
||||
// --- Testing Shareholder Model ---
|
||||
print("\n--- Testing Shareholder Model ---");
|
||||
|
||||
|
||||
let sh1_user_id = 3001; // Example user ID
|
||||
let sh1_name = "Alice Wonderland";
|
||||
let sh1_since = 1672617600; // Example timestamp (Jan 2, 2023)
|
||||
|
||||
print(`Creating shareholder 1 for company ${company1.id}...`);
|
||||
let shareholder1 = new_shareholder()
|
||||
.company_id(company1.id)
|
||||
.user_id(sh1_user_id)
|
||||
.name(sh1_name)
|
||||
.shares(1000.0)
|
||||
.percentage(10.0) // CALCULATED
|
||||
.type_("Individual")
|
||||
.since(sh1_since)
|
||||
.set_base_created_at(1672617600);
|
||||
|
||||
shareholder1 = set_shareholder(shareholder1);
|
||||
print("Shareholder 1 saved.");
|
||||
|
||||
let retrieved_sh1 = get_shareholder_by_id(shareholder1.id);
|
||||
print(`Retrieved Shareholder 1: ${retrieved_sh1.name}, Type: ${retrieved_sh1.type_}, Shares: ${retrieved_sh1.shares}`);
|
||||
|
||||
|
||||
let sh2_entity_id = 4001; // Example corporate entity ID
|
||||
let sh2_name = "Mad Hatter Inc.";
|
||||
let sh2_since = 1672704000; // Example timestamp (Jan 3, 2023)
|
||||
|
||||
print(`\nCreating shareholder 2 for company ${company1.id}...`);
|
||||
let shareholder2 = new_shareholder()
|
||||
.company_id(company1.id)
|
||||
.user_id(sh2_entity_id) // Using user_id field for entity_id for simplicity in example
|
||||
.name(sh2_name)
|
||||
.shares(5000.0)
|
||||
.percentage(50.0)
|
||||
.type_(ShareholderTypeConstants::Corporate)
|
||||
.since(sh2_since)
|
||||
.set_base_created_at(1672704000);
|
||||
|
||||
shareholder2 = set_shareholder(shareholder2);
|
||||
print("Shareholder 2 saved.");
|
||||
|
||||
let retrieved_sh2 = get_shareholder_by_id(shareholder2.id);
|
||||
print(`Retrieved Shareholder 2: ${retrieved_sh2.name}, Type: ${retrieved_sh2.type_}, Percentage: ${retrieved_sh2.percentage}`);
|
||||
|
||||
// --- Testing Update for Company (Example - if setters were fully implemented for complex updates) ---
|
||||
print("\n--- Testing Update for Company ---");
|
||||
let updated_company = retrieved_company
|
||||
.description("Leading global provider of cutting-edge technology solutions and services.")
|
||||
// Note: Company is already Active from payment processing above
|
||||
.phone("+1-555-0199"); // Assume modified_at would be updated by set_company
|
||||
|
||||
print(`Updated Company - Name: ${updated_company.name}, New Phone: ${updated_company.phone}`);
|
||||
print(`Company Status: ${updated_company.status} (already Active from payment)`);
|
||||
set_company(updated_company);
|
||||
print("Updated Company saved.");
|
||||
|
||||
let final_retrieved_company = get_company_by_id(company1.id);
|
||||
print(`Final Retrieved Company - Description: '${final_retrieved_company.description}', Phone: ${final_retrieved_company.phone}`);
|
||||
|
||||
print("\n--- Testing Product Model ---");
|
||||
|
||||
// Print ProductType constants
|
||||
print("\n--- ProductType Constants ---");
|
||||
print(`Product Type Product: ${ProductTypeConstants::Product}`);
|
||||
print(`Product Type Service: ${ProductTypeConstants::Service}`);
|
||||
|
||||
// Print ProductStatus constants
|
||||
print("\n--- ProductStatus Constants ---");
|
||||
print(`Product Status Available: ${ProductStatusConstants::Available}`);
|
||||
print(`Product Status Unavailable: ${ProductStatusConstants::Unavailable}`);
|
||||
|
||||
// Create a product component
|
||||
let component1 = new_product_component("Super Capacitor")
|
||||
.description("High-capacity energy storage unit")
|
||||
.quantity(2);
|
||||
print(`\nCreated Product Component: ${component1.name}, Qty: ${component1.quantity}`);
|
||||
|
||||
// Create Product 1 (a physical product with a component)
|
||||
|
||||
let product1_name = "Advanced Gadget X";
|
||||
let product1_creation_time = 1672876800; // Example timestamp (Jan 5, 2023)
|
||||
|
||||
print(`\nCreating Product 1: ${product1_name}...`);
|
||||
let product1 = new_product()
|
||||
.name(product1_name)
|
||||
.description("A revolutionary gadget with cutting-edge features.")
|
||||
.price(299.99)
|
||||
.type_(ProductTypeConstants::Product)
|
||||
.category("Electronics")
|
||||
.status(ProductStatusConstants::Available)
|
||||
.max_amount(1000)
|
||||
.purchase_till(1704067199) // Dec 31, 2023, 23:59:59
|
||||
.active_till(1735689599) // Dec 31, 2024, 23:59:59
|
||||
.add_component(component1)
|
||||
.set_base_created_at(product1_creation_time);
|
||||
|
||||
print("Saving Product 1...");
|
||||
product1 = set_product(product1);
|
||||
print("Product 1 saved.");
|
||||
|
||||
print(`\nRetrieving Product 1 (ID: ${product1.id})...`);
|
||||
let retrieved_product1 = get_product_by_id(product1.id);
|
||||
print(`Retrieved Product 1: ${retrieved_product1.name}, Price: ${retrieved_product1.price}, Type: ${retrieved_product1.type_}`);
|
||||
if retrieved_product1.components.len() > 0 {
|
||||
print(`Product 1 Component 1: ${retrieved_product1.components[0].name}, Desc: ${retrieved_product1.components[0].description}`);
|
||||
} else {
|
||||
print("Product 1 has no components.");
|
||||
}
|
||||
|
||||
// Create Product 2 (a service)
|
||||
|
||||
let product2_name = "Cloud Backup Service - Pro Plan";
|
||||
let product2_creation_time = 1672963200; // Example timestamp (Jan 6, 2023)
|
||||
|
||||
print(`\nCreating Product 2: ${product2_name}...`);
|
||||
let product2 = new_product()
|
||||
.name(product2_name)
|
||||
.description("Unlimited cloud backup with 24/7 support.")
|
||||
.price(19.99) // Monthly price
|
||||
.type_(ProductTypeConstants::Service)
|
||||
.category("Cloud Services")
|
||||
.status(ProductStatusConstants::Available)
|
||||
.active_till(1735689599) // Valid for a long time, or represents subscription cycle end
|
||||
.set_base_created_at(product2_creation_time);
|
||||
|
||||
print("Saving Product 2...");
|
||||
product2 = set_product(product2);
|
||||
print("Product 2 saved.");
|
||||
|
||||
print(`\nRetrieving Product 2 (ID: ${product2.id})...`);
|
||||
let retrieved_product2 = get_product_by_id(product2.id);
|
||||
print(`Retrieved Product 2: ${retrieved_product2.name}, Category: ${retrieved_product2.category}, Status: ${retrieved_product2.status}`);
|
||||
if retrieved_product2.components.len() > 0 {
|
||||
print(`Product 2 has ${retrieved_product2.components.len()} components.`);
|
||||
} else {
|
||||
print("Product 2 has no components (as expected for a service).");
|
||||
}
|
||||
|
||||
|
||||
// --- Testing Sale Model ---
|
||||
print("\n--- Testing Sale Model ---");
|
||||
|
||||
// Print SaleStatus constants
|
||||
print("\n--- SaleStatus Constants ---");
|
||||
print(`Sale Status Pending: ${SaleStatusConstants::Pending}`);
|
||||
print(`Sale Status Completed: ${SaleStatusConstants::Completed}`);
|
||||
print(`Sale Status Cancelled: ${SaleStatusConstants::Cancelled}`);
|
||||
|
||||
// Create SaleItem 1 (using product1 from above)
|
||||
let sale_item1_product_id = product1.id; // Using product1.id after it's set // Using product1_id from product example
|
||||
let sale_item1_name = retrieved_product1.name; // Using name from retrieved product1
|
||||
let sale_item1_qty = 2;
|
||||
let sale_item1_unit_price = retrieved_product1.price;
|
||||
let sale_item1_subtotal = sale_item1_qty * sale_item1_unit_price;
|
||||
|
||||
print(`\nCreating SaleItem 1 for Product ID: ${sale_item1_product_id}, Name: ${sale_item1_name}...`);
|
||||
let sale_item1 = new_sale_item(sale_item1_product_id, sale_item1_name, sale_item1_qty, sale_item1_unit_price, sale_item1_subtotal);
|
||||
print(`SaleItem 1: Product ID ${sale_item1.product_id}, Qty: ${sale_item1.quantity}, Subtotal: ${sale_item1.subtotal}`);
|
||||
|
||||
// Create SaleItem 2 (using product2 from above)
|
||||
let sale_item2_product_id = product2.id; // Using product2.id after it's set // Using product2_id from product example
|
||||
let sale_item2_name = retrieved_product2.name;
|
||||
let sale_item2_qty = 1;
|
||||
let sale_item2_unit_price = retrieved_product2.price;
|
||||
let sale_item2_subtotal = sale_item2_qty * sale_item2_unit_price;
|
||||
|
||||
print(`\nCreating SaleItem 2 for Product ID: ${sale_item2_product_id}, Name: ${sale_item2_name}...`);
|
||||
let sale_item2 = new_sale_item(sale_item2_product_id, sale_item2_name, sale_item2_qty, sale_item2_unit_price, sale_item2_subtotal);
|
||||
print(`SaleItem 2: Product ID ${sale_item2.product_id}, Qty: ${sale_item2.quantity}, Subtotal: ${sale_item2.subtotal}`);
|
||||
|
||||
// Create a Sale
|
||||
|
||||
let sale1_customer_id = company1.id; // Example: company1 is the customer
|
||||
let sale1_date = 1673049600; // Example timestamp (Jan 7, 2023)
|
||||
let sale1_total_amount = sale_item1.subtotal + sale_item2.subtotal;
|
||||
|
||||
print(`\nCreating Sale 1 for Customer ID: ${sale1_customer_id}...`);
|
||||
let sale1 = new_sale(
|
||||
sale1_customer_id, // for company_id_i64 in Rhai registration
|
||||
"Temp Buyer Name", // for buyer_name in Rhai registration
|
||||
"temp@buyer.com", // for buyer_email in Rhai registration
|
||||
0.0, // for total_amount (will be overridden by builder)
|
||||
SaleStatusConstants::Pending, // for status (will be overridden by builder)
|
||||
0 // for sale_date (will be overridden by builder)
|
||||
)
|
||||
.customer_id(sale1_customer_id) // Actual field on Sale struct
|
||||
.status(SaleStatusConstants::Pending)
|
||||
.sale_date(sale1_date)
|
||||
.add_item(sale_item1) // Add item one by one
|
||||
.add_item(sale_item2)
|
||||
// Alternatively, to set all items at once (if items were already in an array):
|
||||
// .items([sale_item1, sale_item2])
|
||||
.total_amount(sale1_total_amount)
|
||||
.notes("First major sale of the year. Includes Advanced Gadget X and Cloud Backup Pro.")
|
||||
.set_base_created_at(sale1_date)
|
||||
.set_base_modified_at(sale1_date + 300) // 5 mins later
|
||||
.add_base_comment(1) // Example comment ID
|
||||
.add_base_comment(2);
|
||||
|
||||
print(`Sale 1 Created: ID ${sale1.id}, Customer ID: ${sale1.customer_id}, Status: ${sale1.status}, Total: ${sale1.total_amount}`);
|
||||
print(`Sale 1 Notes: ${sale1.notes}`);
|
||||
print(`Sale 1 Item Count: ${sale1.items.len()}`);
|
||||
if sale1.items.len() > 0 {
|
||||
print(`Sale 1 Item 1: ${sale1.items[0].name}, Qty: ${sale1.items[0].quantity}`);
|
||||
}
|
||||
if sale1.items.len() > 1 {
|
||||
print(`Sale 1 Item 2: ${sale1.items[1].name}, Qty: ${sale1.items[1].quantity}`);
|
||||
}
|
||||
print(`Sale 1 Base Comments: ${sale1.comments}`);
|
||||
|
||||
// Save Sale 1 to database
|
||||
print("\nSaving Sale 1 to database...");
|
||||
sale1 = set_sale(sale1);
|
||||
print("Sale 1 saved.");
|
||||
|
||||
// Retrieve Sale 1 from database
|
||||
print(`\nRetrieving Sale 1 by ID (${sale1.id})...`);
|
||||
let retrieved_sale1 = get_sale_by_id(sale1.id);
|
||||
print(`Retrieved Sale 1: ID ${retrieved_sale1.id}, Customer: ${retrieved_sale1.customer_id}, Status: ${retrieved_sale1.status}`);
|
||||
print(`Retrieved Sale 1 Total: ${retrieved_sale1.total_amount}, Notes: '${retrieved_sale1.notes}'`);
|
||||
if retrieved_sale1.items.len() > 0 {
|
||||
print(`Retrieved Sale 1 Item 1: ${retrieved_sale1.items[0].name} (Product ID: ${retrieved_sale1.items[0].product_id})`);
|
||||
}
|
||||
|
||||
print("\nBiz Rhai example script finished.");
|
@@ -1,41 +0,0 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope};
|
||||
use std::sync::Arc;
|
||||
use heromodels::db::hero::OurDB; // Corrected path for OurDB
|
||||
use heromodels::models::biz::register_biz_rhai_module; // Corrected path
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Executing Rhai script: examples/biz_rhai/biz.rhai");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Create an Arc<Mutex<OurDB>> instance
|
||||
// For this example, we'll use an in-memory DB.
|
||||
// The actual DB path or configuration might come from elsewhere in a real app.
|
||||
let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB")); // Corrected OurDB::new args and removed Mutex
|
||||
|
||||
// Register the biz module with the engine
|
||||
register_biz_rhai_module(&mut engine, Arc::clone(&db_instance));
|
||||
|
||||
// Read the Rhai script from file
|
||||
let script_path = "examples/biz_rhai/biz.rhai";
|
||||
let script_content = fs::read_to_string(script_path)
|
||||
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?;
|
||||
|
||||
// Create a new scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Execute the script
|
||||
match engine.run_with_scope(&mut scope, &script_content) {
|
||||
Ok(_) => {
|
||||
println!("Rhai script executed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Rhai script execution failed: {}", e);
|
||||
println!("Details: {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
// Get the database instance
|
||||
let db = get_db();
|
||||
|
||||
// Create a new calendar using the constructor and builder methods
|
||||
print("Creating a new calendar (ID will be DB-assigned) via registered constructor...");
|
||||
let calendar = new_calendar(). // ID removed
|
||||
name("My First Calendar").
|
||||
description("A calendar for testing Rhai integration");
|
||||
|
||||
let event = new_event(). // ID removed
|
||||
title("My First Event").
|
||||
description("An event for testing Rhai integration")
|
||||
.add_attendee(new_attendee(1)); // new_attendee(contact_id), not Attendee ID
|
||||
|
||||
// Add event's ID to calendar. event.id will be 0 if not saved separately.
|
||||
// Calendar::add_event returns the modified calendar, so we re-assign.
|
||||
calendar = calendar.add_event(event.id);
|
||||
|
||||
print("Type of calendar object: " + type_of(calendar));
|
||||
print("Created calendar: " + calendar.name);
|
||||
|
||||
// Save the calendar to the database and capture the result with DB-assigned ID
|
||||
let calendar = set_calendar(db, calendar); // Capture result
|
||||
print("Calendar saved to database");
|
||||
|
||||
// Check if calendar exists and retrieve it
|
||||
if calendar_exists(db, calendar.id) { // Use calendar.id from the saved object
|
||||
let retrieved_calendar = get_calendar_by_id(db, calendar.id); // Use calendar.id
|
||||
print("Retrieved calendar ID: " + retrieved_calendar.id + ", Name: " + retrieved_calendar.name);
|
||||
// Access the 'description' field directly.
|
||||
// Note: 'description' is Option<String>. Rhai handles options.
|
||||
// You might want to check for 'is_some()' or 'is_none()' or use 'unwrap_or()' pattern if needed.
|
||||
let desc = retrieved_calendar.description;
|
||||
if desc != () && desc != "" { // Check against '()' for None and empty string
|
||||
print("Description: " + desc);
|
||||
} else {
|
||||
print("No description available or it's None");
|
||||
}
|
||||
} else {
|
||||
print("Failed to retrieve calendar with ID " + calendar.id);
|
||||
}
|
||||
|
||||
// Create another calendar
|
||||
print("Creating another new calendar (ID will be DB-assigned) using builder methods...");
|
||||
let calendar2 = new_calendar(). // ID removed
|
||||
name("My Second Calendar").
|
||||
description("Another calendar for testing");
|
||||
|
||||
let calendar2 = set_calendar(db, calendar2); // Capture result
|
||||
print("Second calendar saved with ID: " + calendar2.id);
|
||||
|
||||
// Get all calendars
|
||||
let all_calendars = get_all_calendars(db);
|
||||
print("Total calendars: " + all_calendars.len());
|
||||
|
||||
for cal_item in all_calendars { // Renamed loop variable to avoid conflict if 'calendar' is still in scope
|
||||
// Access 'base_data.id' and 'name' fields directly
|
||||
print("Calendar ID: " + cal_item.base_data.id + ", Name: " + cal_item.name);
|
||||
}
|
||||
|
||||
// Delete a calendar
|
||||
delete_calendar_by_id(db, calendar.id); // Use ID from the first calendar object
|
||||
print("Attempted to delete calendar with ID " + calendar.id);
|
||||
|
||||
// Verify deletion
|
||||
if !calendar_exists(db, calendar.id) { // Use ID from the first calendar object
|
||||
print("Calendar with ID " + calendar.id + " was successfully deleted");
|
||||
} else {
|
||||
print("Failed to delete calendar with ID " + calendar.id + ", or it was not the one intended.");
|
||||
}
|
||||
|
||||
// Count remaining calendars
|
||||
let remaining_calendars = get_all_calendars(db);
|
||||
print("Remaining calendars: " + remaining_calendars.len());
|
@@ -1,83 +0,0 @@
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::calendar::rhai::register_rhai_engine_functions;
|
||||
use rhai::Engine;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Initialize database with OurDB
|
||||
let db = Arc::new(OurDB::new("temp_calendar_db", true).expect("Failed to create database"));
|
||||
|
||||
// Register the Calendar type with Rhai
|
||||
// This function is generated by the #[rhai_model_export] attribute
|
||||
Calendar::register_rhai_bindings_for_calendar(&mut engine, db.clone());
|
||||
|
||||
// Register a function to get the database instance
|
||||
engine.register_fn("get_db", move || db.clone());
|
||||
|
||||
// Register a calendar builder function
|
||||
engine.register_fn("calendar__builder", |id: i64| {
|
||||
let id_option = if id <= 0 { None } else { Some(id as u32) };
|
||||
Calendar::new(id_option, "New Calendar")
|
||||
});
|
||||
|
||||
// Register setter methods for Calendar properties
|
||||
engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| {
|
||||
calendar.description = Some(desc);
|
||||
});
|
||||
|
||||
// Register getter methods for Calendar properties
|
||||
engine.register_fn("get_description", |calendar: Calendar| -> String {
|
||||
calendar.description.clone().unwrap_or_default()
|
||||
});
|
||||
|
||||
// Register getter for base_data.id
|
||||
engine.register_fn("get_id", |calendar: Calendar| -> i64 {
|
||||
calendar.base_data.id as i64
|
||||
});
|
||||
|
||||
// Register additional functions needed by the script
|
||||
engine.register_fn("set_calendar", |_db: Arc<OurDB>, _calendar: Calendar| {
|
||||
// In a real implementation, this would save the calendar to the database
|
||||
println!("Calendar saved: {}", _calendar.name);
|
||||
});
|
||||
|
||||
engine.register_fn("get_calendar_by_id", |_db: Arc<OurDB>, id: i64| -> Calendar {
|
||||
// In a real implementation, this would retrieve the calendar from the database
|
||||
Calendar::new(Some(id as u32), "Retrieved Calendar")
|
||||
});
|
||||
|
||||
// Register a function to check if a calendar exists
|
||||
engine.register_fn("calendar_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
||||
// In a real implementation, this would check if the calendar exists in the database
|
||||
id == 1 || id == 2
|
||||
});
|
||||
|
||||
// Define the function separately to use with the wrap_vec_return macro
|
||||
fn get_all_calendars(_db: Arc<OurDB>) -> Vec<Calendar> {
|
||||
// In a real implementation, this would retrieve all calendars from the database
|
||||
vec![Calendar::new(Some(1), "Calendar 1"), Calendar::new(Some(2), "Calendar 2")]
|
||||
}
|
||||
|
||||
// Register the function with the wrap_vec_return macro
|
||||
engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc<OurDB> => Calendar));
|
||||
|
||||
engine.register_fn("delete_calendar_by_id", |_db: Arc<OurDB>, _id: i64| {
|
||||
// In a real implementation, this would delete the calendar from the database
|
||||
println!("Calendar deleted with ID: {}", _id);
|
||||
});
|
||||
|
||||
// Load and evaluate the Rhai script
|
||||
let script_path = Path::new("examples/calendar_rhai/calendar.rhai");
|
||||
let script = fs::read_to_string(script_path)?;
|
||||
|
||||
match engine.eval::<()>(&script) {
|
||||
Ok(_) => println!("Script executed successfully!"),
|
||||
Err(e) => eprintln!("Script execution failed: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -41,11 +41,16 @@ fn main() {
|
||||
println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys());
|
||||
|
||||
// Save the model to the database
|
||||
let collection = db.collection::<CustomUser>().expect("can open user collection");
|
||||
let collection = db
|
||||
.collection::<CustomUser>()
|
||||
.expect("can open user collection");
|
||||
let (user_id, saved_user) = collection.set(&user).expect("can save user");
|
||||
|
||||
println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id());
|
||||
println!("After saving - CustomUser DB Keys: {:?}", saved_user.db_keys());
|
||||
println!(
|
||||
"After saving - CustomUser DB Keys: {:?}",
|
||||
saved_user.db_keys()
|
||||
);
|
||||
println!("Returned ID: {}", user_id);
|
||||
|
||||
// Verify that the ID was auto-generated
|
||||
@@ -53,5 +58,8 @@ fn main() {
|
||||
assert_ne!(saved_user.get_id(), 0);
|
||||
|
||||
println!("\nExample finished. DB stored at {}", db_path);
|
||||
println!("To clean up, you can manually delete the directory: {}", db_path);
|
||||
println!(
|
||||
"To clean up, you can manually delete the directory: {}",
|
||||
db_path
|
||||
);
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
// heromodels/examples/finance_example/main.rs
|
||||
|
||||
use chrono::{Utc, Duration};
|
||||
use chrono::{Duration, Utc};
|
||||
use heromodels::models::finance::marketplace::{
|
||||
Bid, BidStatus, Listing, ListingStatus, ListingType,
|
||||
};
|
||||
use heromodels::models::finance::{Account, Asset, AssetType};
|
||||
use heromodels::models::finance::marketplace::{Listing, ListingType, ListingStatus, Bid, BidStatus};
|
||||
|
||||
fn main() {
|
||||
println!("Finance Models Example\n");
|
||||
@@ -12,16 +14,19 @@ fn main() {
|
||||
|
||||
// Create a new account with auto-generated ID
|
||||
let mut account = Account::new(
|
||||
None, // id (auto-generated)
|
||||
"Main ETH Wallet", // name
|
||||
1001, // user_id
|
||||
"My primary Ethereum wallet", // description
|
||||
"ethereum", // ledger
|
||||
None, // id (auto-generated)
|
||||
"Main ETH Wallet", // name
|
||||
1001, // user_id
|
||||
"My primary Ethereum wallet", // description
|
||||
"ethereum", // ledger
|
||||
"0x1234567890abcdef1234567890abcdef12345678", // address
|
||||
"0xpubkey123456789" // pubkey
|
||||
"0xpubkey123456789", // pubkey
|
||||
);
|
||||
|
||||
println!("Created Account: '{}' (ID: {})", account.name, account.base_data.id);
|
||||
println!(
|
||||
"Created Account: '{}' (ID: {})",
|
||||
account.name, account.base_data.id
|
||||
);
|
||||
println!("Owner: User {}", account.user_id);
|
||||
println!("Blockchain: {}", account.ledger);
|
||||
println!("Address: {}", account.address);
|
||||
@@ -30,34 +35,34 @@ fn main() {
|
||||
// Create some assets
|
||||
// Asset with auto-generated ID
|
||||
let eth_asset = Asset::new(
|
||||
None, // id (auto-generated)
|
||||
"Ethereum", // name
|
||||
"Native ETH cryptocurrency", // description
|
||||
1.5, // amount
|
||||
None, // id (auto-generated)
|
||||
"Ethereum", // name
|
||||
"Native ETH cryptocurrency", // description
|
||||
1.5, // amount
|
||||
"0x0000000000000000000000000000000000000000", // address (ETH has no contract address)
|
||||
AssetType::Native, // asset_type
|
||||
18, // decimals
|
||||
AssetType::Native, // asset_type
|
||||
18, // decimals
|
||||
);
|
||||
|
||||
// Assets with explicit IDs
|
||||
let usdc_asset = Asset::new(
|
||||
Some(102), // id
|
||||
"USDC", // name
|
||||
"USD Stablecoin on Ethereum", // description
|
||||
1000.0, // amount
|
||||
Some(102), // id
|
||||
"USDC", // name
|
||||
"USD Stablecoin on Ethereum", // description
|
||||
1000.0, // amount
|
||||
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // address (USDC contract)
|
||||
AssetType::Erc20, // asset_type
|
||||
6, // decimals
|
||||
AssetType::Erc20, // asset_type
|
||||
6, // decimals
|
||||
);
|
||||
|
||||
let nft_asset = Asset::new(
|
||||
Some(103), // id
|
||||
"CryptoPunk #1234", // name
|
||||
"Rare digital collectible", // description
|
||||
1.0, // amount
|
||||
Some(103), // id
|
||||
"CryptoPunk #1234", // name
|
||||
"Rare digital collectible", // description
|
||||
1.0, // amount
|
||||
"0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb", // address (CryptoPunks contract)
|
||||
AssetType::Erc721, // asset_type
|
||||
0, // decimals
|
||||
AssetType::Erc721, // asset_type
|
||||
0, // decimals
|
||||
);
|
||||
|
||||
// Add assets to the account
|
||||
@@ -67,7 +72,12 @@ fn main() {
|
||||
|
||||
println!("Added Assets to Account:");
|
||||
for asset in &account.assets {
|
||||
println!("- {} ({:?}): {} units", asset.name, asset.asset_type, asset.formatted_amount());
|
||||
println!(
|
||||
"- {} ({:?}): {} units",
|
||||
asset.name,
|
||||
asset.asset_type,
|
||||
asset.formatted_amount()
|
||||
);
|
||||
}
|
||||
|
||||
println!("\nTotal Account Value (raw sum): {}", account.total_value());
|
||||
@@ -75,10 +85,10 @@ fn main() {
|
||||
|
||||
// Update account details
|
||||
account = account.update_details(
|
||||
Some("Primary Ethereum Wallet"), // new name
|
||||
None::<String>, // keep same description
|
||||
None::<String>, // keep same address
|
||||
Some("0xnewpubkey987654321"), // new pubkey
|
||||
Some("Primary Ethereum Wallet"), // new name
|
||||
None::<String>, // keep same description
|
||||
None::<String>, // keep same address
|
||||
Some("0xnewpubkey987654321"), // new pubkey
|
||||
);
|
||||
|
||||
println!("Updated Account Details:");
|
||||
@@ -99,23 +109,32 @@ fn main() {
|
||||
|
||||
// Create a fixed price listing with auto-generated ID
|
||||
let mut fixed_price_listing = Listing::new(
|
||||
None, // id (auto-generated)
|
||||
"1000 USDC for Sale", // title
|
||||
"Selling 1000 USDC tokens at fixed price", // description
|
||||
"102", // asset_id (referencing the USDC asset)
|
||||
AssetType::Erc20, // asset_type
|
||||
"1001", // seller_id
|
||||
1.05, // price (in ETH)
|
||||
"ETH", // currency
|
||||
ListingType::FixedPrice, // listing_type
|
||||
None, // id (auto-generated)
|
||||
"1000 USDC for Sale", // title
|
||||
"Selling 1000 USDC tokens at fixed price", // description
|
||||
"102", // asset_id (referencing the USDC asset)
|
||||
AssetType::Erc20, // asset_type
|
||||
"1001", // seller_id
|
||||
1.05, // price (in ETH)
|
||||
"ETH", // currency
|
||||
ListingType::FixedPrice, // listing_type
|
||||
Some(Utc::now() + Duration::days(7)), // expires_at (7 days from now)
|
||||
vec!["token".to_string(), "stablecoin".to_string()], // tags
|
||||
Some("https://example.com/usdc.png"), // image_url
|
||||
);
|
||||
|
||||
println!("Created Fixed Price Listing: '{}' (ID: {})", fixed_price_listing.title, fixed_price_listing.base_data.id);
|
||||
println!("Price: {} {}", fixed_price_listing.price, fixed_price_listing.currency);
|
||||
println!("Type: {:?}, Status: {:?}", fixed_price_listing.listing_type, fixed_price_listing.status);
|
||||
println!(
|
||||
"Created Fixed Price Listing: '{}' (ID: {})",
|
||||
fixed_price_listing.title, fixed_price_listing.base_data.id
|
||||
);
|
||||
println!(
|
||||
"Price: {} {}",
|
||||
fixed_price_listing.price, fixed_price_listing.currency
|
||||
);
|
||||
println!(
|
||||
"Type: {:?}, Status: {:?}",
|
||||
fixed_price_listing.listing_type, fixed_price_listing.status
|
||||
);
|
||||
println!("Expires: {}", fixed_price_listing.expires_at.unwrap());
|
||||
println!("");
|
||||
|
||||
@@ -126,54 +145,71 @@ fn main() {
|
||||
println!("Fixed Price Sale Completed:");
|
||||
println!("Status: {:?}", fixed_price_listing.status);
|
||||
println!("Buyer: {}", fixed_price_listing.buyer_id.unwrap());
|
||||
println!("Sale Price: {} {}", fixed_price_listing.sale_price.unwrap(), fixed_price_listing.currency);
|
||||
println!(
|
||||
"Sale Price: {} {}",
|
||||
fixed_price_listing.sale_price.unwrap(),
|
||||
fixed_price_listing.currency
|
||||
);
|
||||
println!("Sold At: {}", fixed_price_listing.sold_at.unwrap());
|
||||
println!("");
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Error completing sale: {}", e),
|
||||
}
|
||||
|
||||
// Create an auction listing for the NFT with explicit ID
|
||||
let mut auction_listing = Listing::new(
|
||||
Some(202), // id (explicit)
|
||||
"CryptoPunk #1234 Auction", // title
|
||||
"Rare CryptoPunk NFT for auction", // description
|
||||
"103", // asset_id (referencing the NFT asset)
|
||||
AssetType::Erc721, // asset_type
|
||||
"1001", // seller_id
|
||||
10.0, // starting_price (in ETH)
|
||||
"ETH", // currency
|
||||
ListingType::Auction, // listing_type
|
||||
Some(202), // id (explicit)
|
||||
"CryptoPunk #1234 Auction", // title
|
||||
"Rare CryptoPunk NFT for auction", // description
|
||||
"103", // asset_id (referencing the NFT asset)
|
||||
AssetType::Erc721, // asset_type
|
||||
"1001", // seller_id
|
||||
10.0, // starting_price (in ETH)
|
||||
"ETH", // currency
|
||||
ListingType::Auction, // listing_type
|
||||
Some(Utc::now() + Duration::days(3)), // expires_at (3 days from now)
|
||||
vec!["nft".to_string(), "collectible".to_string(), "cryptopunk".to_string()], // tags
|
||||
vec![
|
||||
"nft".to_string(),
|
||||
"collectible".to_string(),
|
||||
"cryptopunk".to_string(),
|
||||
], // tags
|
||||
Some("https://example.com/cryptopunk1234.png"), // image_url
|
||||
);
|
||||
|
||||
println!("Created Auction Listing: '{}' (ID: {})", auction_listing.title, auction_listing.base_data.id);
|
||||
println!("Starting Price: {} {}", auction_listing.price, auction_listing.currency);
|
||||
println!("Type: {:?}, Status: {:?}", auction_listing.listing_type, auction_listing.status);
|
||||
println!(
|
||||
"Created Auction Listing: '{}' (ID: {})",
|
||||
auction_listing.title, auction_listing.base_data.id
|
||||
);
|
||||
println!(
|
||||
"Starting Price: {} {}",
|
||||
auction_listing.price, auction_listing.currency
|
||||
);
|
||||
println!(
|
||||
"Type: {:?}, Status: {:?}",
|
||||
auction_listing.listing_type, auction_listing.status
|
||||
);
|
||||
println!("");
|
||||
|
||||
// Create some bids
|
||||
let bid1 = Bid::new(
|
||||
auction_listing.base_data.id.to_string(), // listing_id
|
||||
2001, // bidder_id
|
||||
11.0, // amount
|
||||
"ETH", // currency
|
||||
2001, // bidder_id
|
||||
11.0, // amount
|
||||
"ETH", // currency
|
||||
);
|
||||
|
||||
let bid2 = Bid::new(
|
||||
auction_listing.base_data.id.to_string(), // listing_id
|
||||
2002, // bidder_id
|
||||
12.5, // amount
|
||||
"ETH", // currency
|
||||
2002, // bidder_id
|
||||
12.5, // amount
|
||||
"ETH", // currency
|
||||
);
|
||||
|
||||
let bid3 = Bid::new(
|
||||
auction_listing.base_data.id.to_string(), // listing_id
|
||||
2003, // bidder_id
|
||||
15.0, // amount
|
||||
"ETH", // currency
|
||||
2003, // bidder_id
|
||||
15.0, // amount
|
||||
"ETH", // currency
|
||||
);
|
||||
|
||||
// Add bids to the auction
|
||||
@@ -184,7 +220,7 @@ fn main() {
|
||||
Ok(updated_listing) => {
|
||||
auction_listing = updated_listing;
|
||||
println!("- Bid added: 11.0 ETH from User 2001");
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Error adding bid: {}", e),
|
||||
}
|
||||
|
||||
@@ -192,7 +228,7 @@ fn main() {
|
||||
Ok(updated_listing) => {
|
||||
auction_listing = updated_listing;
|
||||
println!("- Bid added: 12.5 ETH from User 2002");
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Error adding bid: {}", e),
|
||||
}
|
||||
|
||||
@@ -200,18 +236,21 @@ fn main() {
|
||||
Ok(updated_listing) => {
|
||||
auction_listing = updated_listing;
|
||||
println!("- Bid added: 15.0 ETH from User 2003");
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Error adding bid: {}", e),
|
||||
}
|
||||
|
||||
println!("\nCurrent Auction Status:");
|
||||
println!("Current Price: {} {}", auction_listing.price, auction_listing.currency);
|
||||
println!(
|
||||
"Current Price: {} {}",
|
||||
auction_listing.price, auction_listing.currency
|
||||
);
|
||||
|
||||
if let Some(highest_bid) = auction_listing.highest_bid() {
|
||||
println!("Highest Bid: {} {} from User {}",
|
||||
highest_bid.amount,
|
||||
highest_bid.currency,
|
||||
highest_bid.bidder_id);
|
||||
println!(
|
||||
"Highest Bid: {} {} from User {}",
|
||||
highest_bid.amount, highest_bid.currency, highest_bid.bidder_id
|
||||
);
|
||||
}
|
||||
|
||||
println!("Total Bids: {}", auction_listing.bids.len());
|
||||
@@ -223,42 +262,57 @@ fn main() {
|
||||
auction_listing = updated_listing;
|
||||
println!("Auction Completed:");
|
||||
println!("Status: {:?}", auction_listing.status);
|
||||
println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap());
|
||||
println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency);
|
||||
println!(
|
||||
"Winner: User {}",
|
||||
auction_listing.buyer_id.as_ref().unwrap()
|
||||
);
|
||||
println!(
|
||||
"Winning Bid: {} {}",
|
||||
auction_listing.sale_price.as_ref().unwrap(),
|
||||
auction_listing.currency
|
||||
);
|
||||
println!("");
|
||||
|
||||
println!("Final Bid Statuses:");
|
||||
for bid in &auction_listing.bids {
|
||||
println!("- User {}: {} {} (Status: {:?})",
|
||||
bid.bidder_id,
|
||||
bid.amount,
|
||||
bid.currency,
|
||||
bid.status);
|
||||
println!(
|
||||
"- User {}: {} {} (Status: {:?})",
|
||||
bid.bidder_id, bid.amount, bid.currency, bid.status
|
||||
);
|
||||
}
|
||||
println!("");
|
||||
},
|
||||
}
|
||||
Err(e) => println!("Error completing auction: {}", e),
|
||||
}
|
||||
|
||||
// Create an exchange listing with auto-generated ID
|
||||
let exchange_listing = Listing::new(
|
||||
None, // id (auto-generated)
|
||||
"ETH for BTC Exchange", // title
|
||||
"Looking to exchange ETH for BTC", // description
|
||||
"101", // asset_id (referencing the ETH asset)
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
1.0, // amount (1 ETH)
|
||||
"BTC", // currency (what they want in exchange)
|
||||
ListingType::Exchange, // listing_type
|
||||
Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now)
|
||||
None, // id (auto-generated)
|
||||
"ETH for BTC Exchange", // title
|
||||
"Looking to exchange ETH for BTC", // description
|
||||
"101", // asset_id (referencing the ETH asset)
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
1.0, // amount (1 ETH)
|
||||
"BTC", // currency (what they want in exchange)
|
||||
ListingType::Exchange, // listing_type
|
||||
Some(Utc::now() + Duration::days(14)), // expires_at (14 days from now)
|
||||
vec!["exchange".to_string(), "crypto".to_string()], // tags
|
||||
None::<String>, // image_url
|
||||
None::<String>, // image_url
|
||||
);
|
||||
|
||||
println!("Created Exchange Listing: '{}' (ID: {})", exchange_listing.title, exchange_listing.base_data.id);
|
||||
println!("Offering: Asset {} ({:?})", exchange_listing.asset_id, exchange_listing.asset_type);
|
||||
println!("Wanted: {} {}", exchange_listing.price, exchange_listing.currency);
|
||||
println!(
|
||||
"Created Exchange Listing: '{}' (ID: {})",
|
||||
exchange_listing.title, exchange_listing.base_data.id
|
||||
);
|
||||
println!(
|
||||
"Offering: Asset {} ({:?})",
|
||||
exchange_listing.asset_id, exchange_listing.asset_type
|
||||
);
|
||||
println!(
|
||||
"Wanted: {} {}",
|
||||
exchange_listing.price, exchange_listing.currency
|
||||
);
|
||||
println!("");
|
||||
|
||||
// --- PART 3: DEMONSTRATING EDGE CASES ---
|
||||
@@ -266,26 +320,26 @@ fn main() {
|
||||
|
||||
// Create a new auction listing for edge case testing with explicit ID
|
||||
let test_auction = Listing::new(
|
||||
Some(205), // id (explicit)
|
||||
"Test Auction", // title
|
||||
"For testing edge cases", // description
|
||||
"101", // asset_id
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
10.0, // starting_price
|
||||
"ETH", // currency
|
||||
ListingType::Auction, // listing_type
|
||||
Some(205), // id (explicit)
|
||||
"Test Auction", // title
|
||||
"For testing edge cases", // description
|
||||
"101", // asset_id
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
10.0, // starting_price
|
||||
"ETH", // currency
|
||||
ListingType::Auction, // listing_type
|
||||
Some(Utc::now() + Duration::days(1)), // expires_at
|
||||
vec![], // tags
|
||||
None::<String>, // image_url
|
||||
vec![], // tags
|
||||
None::<String>, // image_url
|
||||
);
|
||||
|
||||
// Try to add a bid that's too low
|
||||
let low_bid = Bid::new(
|
||||
test_auction.base_data.id.to_string(), // listing_id
|
||||
2004, // bidder_id
|
||||
5.0, // amount (lower than starting price)
|
||||
"ETH", // currency
|
||||
2004, // bidder_id
|
||||
5.0, // amount (lower than starting price)
|
||||
"ETH", // currency
|
||||
);
|
||||
|
||||
println!("Attempting to add a bid that's too low (5.0 ETH):");
|
||||
@@ -305,21 +359,24 @@ fn main() {
|
||||
|
||||
// Create a listing that will expire with auto-generated ID
|
||||
let mut expiring_listing = Listing::new(
|
||||
None, // id (auto-generated)
|
||||
"About to Expire", // title
|
||||
None, // id (auto-generated)
|
||||
"About to Expire", // title
|
||||
"This listing will expire immediately", // description
|
||||
"101", // asset_id
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
0.1, // price
|
||||
"ETH", // currency
|
||||
ListingType::FixedPrice, // listing_type
|
||||
Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago)
|
||||
vec![], // tags
|
||||
None::<String>, // image_url
|
||||
"101", // asset_id
|
||||
AssetType::Native, // asset_type
|
||||
"1001", // seller_id
|
||||
0.1, // price
|
||||
"ETH", // currency
|
||||
ListingType::FixedPrice, // listing_type
|
||||
Some(Utc::now() - Duration::hours(1)), // expires_at (1 hour ago)
|
||||
vec![], // tags
|
||||
None::<String>, // image_url
|
||||
);
|
||||
|
||||
println!("Created Expiring Listing: '{}' (ID: {})", expiring_listing.title, expiring_listing.base_data.id);
|
||||
println!(
|
||||
"Created Expiring Listing: '{}' (ID: {})",
|
||||
expiring_listing.title, expiring_listing.base_data.id
|
||||
);
|
||||
println!("Initial Status: {:?}", expiring_listing.status);
|
||||
|
||||
// Check expiration
|
||||
|
@@ -1,109 +0,0 @@
|
||||
use rhai::{Engine, Scope, EvalAltResult};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
// Import the models and the registration function
|
||||
use heromodels::models::finance::account::Account;
|
||||
use heromodels::models::finance::asset::{Asset};
|
||||
use heromodels::models::finance::marketplace::{Listing};
|
||||
use heromodels::models::finance::rhai::register_rhai_engine_functions;
|
||||
|
||||
// Define a simple in-memory mock database for the example
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MockDb {
|
||||
pub accounts: Arc<Mutex<HashMap<u32, Account>>>,
|
||||
pub assets: Arc<Mutex<HashMap<u32, Asset>>>,
|
||||
pub listings: Arc<Mutex<HashMap<u32, Listing>>>,
|
||||
// Bids are often part of Listings, so a separate HashMap for Bids might not be needed
|
||||
// unless we want to query bids globally by a unique bid ID.
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
accounts: Arc::new(Mutex::new(HashMap::new())),
|
||||
assets: Arc::new(Mutex::new(HashMap::new())),
|
||||
listings: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("--- Finance Rhai Example ---");
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mock_db = Arc::new(MockDb::new());
|
||||
|
||||
// Register finance functions and types with the engine
|
||||
register_rhai_engine_functions(
|
||||
&mut engine,
|
||||
Arc::clone(&mock_db.accounts),
|
||||
Arc::clone(&mock_db.assets),
|
||||
Arc::clone(&mock_db.listings)
|
||||
);
|
||||
println!("Rhai functions registered.");
|
||||
|
||||
scope.push("db_instance", mock_db.clone());
|
||||
|
||||
let script_path = "examples/finance_rhai/finance.rhai";
|
||||
println!("Loading script: {}", script_path);
|
||||
let script = match fs::read_to_string(script_path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading script file '{}': {}", script_path, e);
|
||||
return Err(Box::new(EvalAltResult::ErrorSystem(
|
||||
"Failed to read script".to_string(),
|
||||
Box::new(e),
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
println!("Executing script...");
|
||||
match engine.run_with_scope(&mut scope, &script) {
|
||||
Ok(_) => println!("Script executed successfully!"),
|
||||
Err(e) => {
|
||||
eprintln!("Script execution failed: {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Print final state of Accounts
|
||||
let final_accounts = mock_db.accounts.lock().unwrap();
|
||||
println!("\n--- Final Mock DB State (Accounts) ---");
|
||||
if final_accounts.is_empty() {
|
||||
println!("No accounts in mock DB.");
|
||||
}
|
||||
for (id, account) in final_accounts.iter() {
|
||||
println!("Account ID: {}, Name: '{}', User ID: {}, Assets: {}",
|
||||
id, account.name, account.user_id, account.assets.len());
|
||||
}
|
||||
|
||||
// Print final state of Assets
|
||||
let final_assets = mock_db.assets.lock().unwrap();
|
||||
println!("\n--- Final Mock DB State (Assets) ---");
|
||||
if final_assets.is_empty() {
|
||||
println!("No assets in mock DB.");
|
||||
}
|
||||
for (id, asset) in final_assets.iter() {
|
||||
println!("Asset ID: {}, Name: '{}', Amount: {}, Type: {:?}",
|
||||
id, asset.name, asset.amount, asset.asset_type);
|
||||
}
|
||||
|
||||
// Print final state of Listings
|
||||
let final_listings = mock_db.listings.lock().unwrap();
|
||||
println!("\n--- Final Mock DB State (Listings) ---");
|
||||
if final_listings.is_empty() {
|
||||
println!("No listings in mock DB.");
|
||||
}
|
||||
for (id, listing) in final_listings.iter() {
|
||||
println!(
|
||||
"Listing ID: {}, Title: '{}', Type: {:?}, Status: {:?}, Price: {}, Bids: {}",
|
||||
id, listing.title, listing.listing_type, listing.status, listing.price, listing.bids.len()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,143 +0,0 @@
|
||||
// Finance Rhai Script Example
|
||||
|
||||
print("--- Starting Finance Rhai Script ---");
|
||||
|
||||
// 1. Create an Account using the builder pattern
|
||||
let user1_id = 1; // Assuming this user_id is a separate concept, e.g. from an auth system
|
||||
let acc1 = new_account()
|
||||
.set_name("User1 Main Account")
|
||||
.set_user_id(user1_id) // user_id is i64 in Rhai, u32 in Rust. Conversion handled by setter.
|
||||
.set_description("Primary account for User 1")
|
||||
.set_ledger("LedgerX")
|
||||
.set_address("0x123MainSt")
|
||||
.set_pubkey("pubkeyUser1");
|
||||
print(`Created account (pre-save): ${acc1.name} with temp ID ${acc1.id}`);
|
||||
|
||||
// 2. Save Account to Mock DB and get the version with DB-assigned ID
|
||||
let acc1 = set_account(acc1); // Shadowing acc1 with the returned instance
|
||||
print(`Account ${acc1.name} saved to DB with ID ${acc1.id}.`);
|
||||
|
||||
// 3. Retrieve Account from Mock DB (using the new ID)
|
||||
let fetched_acc1 = get_account_by_id(acc1.id); // Use the ID from the saved acc1
|
||||
print(`Fetched account from DB: ${fetched_acc1.name}, User ID: ${fetched_acc1.user_id}`);
|
||||
|
||||
// 4. Create an Asset using the builder pattern
|
||||
let asset1 = new_asset()
|
||||
.set_name("HeroCoin")
|
||||
.set_description("Utility token for Hero Platform")
|
||||
.set_amount(1000.0)
|
||||
.set_address("0xTokenContract")
|
||||
.set_asset_type("Erc20") // Setter handles string to enum
|
||||
.set_decimals(18);
|
||||
print(`Created asset (pre-save): ${asset1.name} (temp ID: ${asset1.id}), Amount: ${asset1.amount}, Type: ${asset1.asset_type_str}`);
|
||||
|
||||
// 5. Save Asset to Mock DB and get the version with DB-assigned ID
|
||||
let asset1 = set_asset(asset1); // Shadowing asset1
|
||||
print(`Asset ${asset1.name} (ID: ${asset1.id}) saved to DB.`);
|
||||
|
||||
// 6. Retrieve Asset from Mock DB (using the new ID)
|
||||
let fetched_asset1 = get_asset_by_id(asset1.id); // Use the ID from the saved asset1
|
||||
print(`Fetched asset from DB: ${fetched_asset1.name}, Address: ${fetched_asset1.address}`);
|
||||
|
||||
// 7. Add Asset to Account
|
||||
// We have 'acc1' and 'asset1' from previous steps, both saved to DB and have their IDs.
|
||||
print(`Attempting to add asset ${asset1.id} to account ${acc1.id}`);
|
||||
|
||||
// Fetch the latest version of the account before modifying
|
||||
// let mut acc1_for_update = get_account_by_id(acc1.get_id());
|
||||
// // Fetch the asset to add (or use fetched_asset1 if it's the correct one)
|
||||
// let asset_to_add = get_asset_by_id(asset1.get_id());
|
||||
//
|
||||
// try {
|
||||
// acc1_for_update = acc1_for_update.add_asset(asset_to_add); // add_asset returns the modified account
|
||||
// acc1_for_update = set_account(acc1_for_update); // Save the account with the new asset
|
||||
// print(`Asset '${asset_to_add.name}' added to account '${acc1_for_update.name}'.`);
|
||||
// print(`Account now has ${acc1_for_update.get_assets_cloned().len()} assets.`);
|
||||
// // Verify the asset is there
|
||||
// if (acc1_for_update.get_assets_cloned().len() > 0) {
|
||||
// let first_asset_in_account = acc1_for_update.get_assets_cloned()[0];
|
||||
// print(`First asset in account: ${first_asset_in_account.name} (ID: ${first_asset_in_account.id})`);
|
||||
// }
|
||||
// } catch (err) {
|
||||
// print(`Error adding asset to account: ${err}`);
|
||||
// }
|
||||
|
||||
// 8. Create a Listing for the Asset using the builder pattern
|
||||
let current_timestamp = timestamp(); // Rhai's built-in for current unix timestamp (seconds)
|
||||
let expires_at_ts = current_timestamp + (24 * 60 * 60 * 7); // Expires in 7 days
|
||||
|
||||
let listing1 = new_listing()
|
||||
.set_title("Rare HeroCoin Batch")
|
||||
.set_description("100 HeroCoins for sale")
|
||||
.set_asset_id(asset1.id.to_string()) // Use ID from the saved asset1
|
||||
.set_asset_type_str("Erc20") // asset_type as string
|
||||
.set_seller_id(user1_id.to_string()) // seller_id as string (using the predefined user1_id)
|
||||
.set_price(50.0) // price
|
||||
.set_currency("USD") // currency
|
||||
.set_listing_type("FixedPrice") // listing_type as string
|
||||
.set_tags(["token", "herocoin", "sale"]); // tags as array of strings
|
||||
// image_url is None by default from new_listing(), so no need to call set_image_url_opt for None
|
||||
|
||||
print(`Created listing (pre-save): ${listing1.title} (temp ID: ${listing1.id}), Price: ${listing1.price} ${listing1.currency}`);
|
||||
print(`Listing type: ${listing1.listing_type}, Status: ${listing1.status}`);
|
||||
print(`Listing expires_at_ts_opt: ${listing1.expires_at_ts_opt}`);
|
||||
|
||||
// 9. Save Listing to Mock DB and get the version with DB-assigned ID
|
||||
let listing1 = set_listing(listing1); // Shadowing listing1
|
||||
print(`Listing ${listing1.get_title()} (ID: ${listing1.get_id()}) saved to DB.`);
|
||||
|
||||
// 10. Retrieve Listing from Mock DB (using the new ID)
|
||||
let fetched_listing1 = get_listing_by_id(listing1.get_id()); // Use the ID from the saved listing1
|
||||
print(`Fetched listing from DB: ${fetched_listing1.get_title()}, Seller ID: ${fetched_listing1.get_seller_id()}`);
|
||||
print(`Fetched listing asset_id: ${fetched_listing1.get_asset_id()}, asset_type: ${fetched_listing1.get_asset_type_str()}`);
|
||||
|
||||
// 11. Demonstrate an auction listing using the builder pattern
|
||||
let auction_listing = new_listing()
|
||||
.set_title("Vintage Hero Figurine")
|
||||
.set_description("Rare collectible, starting bid low!")
|
||||
.set_asset_id("asset_nft_123") // Mock asset ID for an NFT - this asset isn't created/saved in script
|
||||
.set_asset_type("Erc721")
|
||||
.set_seller_id(user1_id.to_string()) // Using the predefined user1_id
|
||||
.set_price(10.0) // Starting price
|
||||
.set_currency("USD")
|
||||
.set_listing_type("Auction")
|
||||
// expires_at_ts_opt is None by default
|
||||
.set_tags(["collectible", "rare", "auction"])
|
||||
.set_image_url_opt("http://example.com/figurine.png");
|
||||
|
||||
// Save Auction Listing to Mock DB and get the version with DB-assigned ID
|
||||
let auction_listing = set_listing(auction_listing); // Shadowing auction_listing
|
||||
print(`Created auction listing: ${auction_listing.get_title()} (ID: ${auction_listing.get_id()})`);
|
||||
|
||||
// 12. Create a Bid for the auction listing (Bid model not using builder pattern in this refactor)
|
||||
let bid1 = new_bid(auction_listing.get_id().to_string(), 2, 12.0, "USD"); // User 2 bids 12 USD
|
||||
print(`Created bid for listing ${bid1.listing_id} by bidder ${bid1.bidder_id} for ${bid1.amount} ${bid1.currency}`);
|
||||
print(`Bid status: ${bid1.status_str}, Created at: ${bid1.created_at_ts}`);
|
||||
|
||||
// 13. Add bid to listing
|
||||
let auction_listing_for_bid = get_listing_by_id(auction_listing.get_id());
|
||||
// print(`Listing '${auction_listing_for_bid.get_title()}' fetched for bidding. Current price: ${auction_listing_for_bid.get_price()}, Bids: ${auction_listing_for_bid.get_bids_cloned().len()}`);
|
||||
|
||||
try {
|
||||
let updated_listing_after_bid = auction_listing_for_bid.add_listing_bid(bid1);
|
||||
print(`Bid added to '${updated_listing_after_bid.get_title()}'. New bid count: ${updated_listing_after_bid.get_bids_cloned().len()}, New price: ${updated_listing_after_bid.get_price()};`);
|
||||
set_listing(updated_listing_after_bid); // Save updated listing to DB
|
||||
print("Auction listing with new bid saved to DB;");
|
||||
} catch (err) {
|
||||
print(`Error adding bid: ${err}`);
|
||||
}
|
||||
|
||||
// 14. Try to complete sale for the fixed price listing
|
||||
let listing_to_sell = get_listing_by_id(listing1.id);
|
||||
let buyer_user_id = 3;
|
||||
print(`Attempting to complete sale for listing: ${listing_to_sell.title} by buyer ${buyer_user_id}`);
|
||||
try {
|
||||
let sold_listing = listing_to_sell.complete_listing_sale(buyer_user_id.to_string(), listing_to_sell.price);
|
||||
print(`Sale completed for listing ${sold_listing.id}. New status: ${sold_listing.status}`);
|
||||
print(`Buyer ID: ${sold_listing.buyer_id_opt}, Sale Price: ${sold_listing.sale_price_opt}`);
|
||||
set_listing(sold_listing); // Save updated listing
|
||||
} catch (err) {
|
||||
print(`Error completing sale: ${err}`);
|
||||
}
|
||||
|
||||
print("--- Finance Rhai Script Finished ---");
|
@@ -9,8 +9,8 @@ use heromodels_core::Model;
|
||||
|
||||
fn main() {
|
||||
// Create a new DB instance in /tmp/ourdb_flowbroker, and reset before every run
|
||||
let db = heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true)
|
||||
.expect("Can create DB");
|
||||
let db =
|
||||
heromodels::db::hero::OurDB::new("/tmp/ourdb_flowbroker", true).expect("Can create DB");
|
||||
|
||||
println!("Hero Models - Flow Example");
|
||||
println!("===========================");
|
||||
@@ -20,56 +20,71 @@ fn main() {
|
||||
let new_flow_uuid = "a1b2c3d4-e5f6-7890-1234-567890abcdef"; // Example UUID
|
||||
|
||||
let flow1 = Flow::new(
|
||||
1, // id
|
||||
new_flow_uuid, // flow_uuid
|
||||
"Document Approval Flow", // name
|
||||
"Pending", // status
|
||||
1, // id
|
||||
new_flow_uuid, // flow_uuid
|
||||
"Document Approval Flow", // name
|
||||
"Pending", // status
|
||||
);
|
||||
db.collection().expect("can open flow collection").set(&flow1).expect("can set flow1");
|
||||
db.collection()
|
||||
.expect("can open flow collection")
|
||||
.set(&flow1)
|
||||
.expect("can set flow1");
|
||||
println!("Created Flow: {:?}", flow1);
|
||||
println!("Flow ID: {}", flow1.get_id());
|
||||
println!("Flow DB Prefix: {}", Flow::db_prefix());
|
||||
|
||||
// --- Create FlowSteps for Flow1 ---
|
||||
let step1_flow1 = FlowStep::new(
|
||||
101, // id
|
||||
flow1.get_id(), // flow_id
|
||||
1, // step_order
|
||||
"Pending", // status
|
||||
101, // id
|
||||
flow1.get_id(), // flow_id
|
||||
1, // step_order
|
||||
"Pending", // status
|
||||
)
|
||||
.description("Initial review by manager");
|
||||
db.collection().expect("can open flow_step collection").set(&step1_flow1).expect("can set step1_flow1");
|
||||
db.collection()
|
||||
.expect("can open flow_step collection")
|
||||
.set(&step1_flow1)
|
||||
.expect("can set step1_flow1");
|
||||
println!("Created FlowStep: {:?}", step1_flow1);
|
||||
|
||||
let step2_flow1 = FlowStep::new(
|
||||
102, // id
|
||||
flow1.get_id(), // flow_id
|
||||
2, // step_order
|
||||
"Pending", // status
|
||||
102, // id
|
||||
flow1.get_id(), // flow_id
|
||||
2, // step_order
|
||||
"Pending", // status
|
||||
)
|
||||
.description("Legal team sign-off");
|
||||
db.collection().expect("can open flow_step collection").set(&step2_flow1).expect("can set step2_flow1");
|
||||
db.collection()
|
||||
.expect("can open flow_step collection")
|
||||
.set(&step2_flow1)
|
||||
.expect("can set step2_flow1");
|
||||
println!("Created FlowStep: {:?}", step2_flow1);
|
||||
|
||||
// --- Create SignatureRequirements for step2_flow1 ---
|
||||
let sig_req1_step2 = SignatureRequirement::new(
|
||||
201, // id
|
||||
step2_flow1.get_id(), // flow_step_id
|
||||
"pubkey_legal_team_lead_hex", // public_key
|
||||
201, // id
|
||||
step2_flow1.get_id(), // flow_step_id
|
||||
"pubkey_legal_team_lead_hex", // public_key
|
||||
"I approve this document for legal compliance.", // message
|
||||
"Pending", // status
|
||||
"Pending", // status
|
||||
);
|
||||
db.collection().expect("can open sig_req collection").set(&sig_req1_step2).expect("can set sig_req1_step2");
|
||||
db.collection()
|
||||
.expect("can open sig_req collection")
|
||||
.set(&sig_req1_step2)
|
||||
.expect("can set sig_req1_step2");
|
||||
println!("Created SignatureRequirement: {:?}", sig_req1_step2);
|
||||
|
||||
let sig_req2_step2 = SignatureRequirement::new(
|
||||
202, // id
|
||||
step2_flow1.get_id(), // flow_step_id
|
||||
"pubkey_general_counsel_hex", // public_key
|
||||
202, // id
|
||||
step2_flow1.get_id(), // flow_step_id
|
||||
"pubkey_general_counsel_hex", // public_key
|
||||
"I, as General Counsel, approve this document.", // message
|
||||
"Pending", // status
|
||||
"Pending", // status
|
||||
);
|
||||
db.collection().expect("can open sig_req collection").set(&sig_req2_step2).expect("can set sig_req2_step2");
|
||||
db.collection()
|
||||
.expect("can open sig_req collection")
|
||||
.set(&sig_req2_step2)
|
||||
.expect("can set sig_req2_step2");
|
||||
println!("Created SignatureRequirement: {:?}", sig_req2_step2);
|
||||
|
||||
// --- Retrieve and Verify ---
|
||||
@@ -101,9 +116,18 @@ fn main() {
|
||||
.get::<flow_step_flow_id_idx, _>(&retrieved_flow.get_id())
|
||||
.expect("can load steps for flow1");
|
||||
assert_eq!(steps_for_flow1.len(), 2);
|
||||
println!("Retrieved {} FlowSteps for Flow ID {}:", steps_for_flow1.len(), retrieved_flow.get_id());
|
||||
println!(
|
||||
"Retrieved {} FlowSteps for Flow ID {}:",
|
||||
steps_for_flow1.len(),
|
||||
retrieved_flow.get_id()
|
||||
);
|
||||
for step in &steps_for_flow1 {
|
||||
println!(" - Step ID: {}, Order: {}, Desc: {:?}", step.get_id(), step.step_order, step.description);
|
||||
println!(
|
||||
" - Step ID: {}, Order: {}, Desc: {:?}",
|
||||
step.get_id(),
|
||||
step.step_order,
|
||||
step.description
|
||||
);
|
||||
}
|
||||
|
||||
// --- Update a SignatureRequirement (simulate signing) ---
|
||||
@@ -114,12 +138,18 @@ fn main() {
|
||||
.expect("can load sig_req1")
|
||||
.unwrap();
|
||||
|
||||
println!("\nUpdating SignatureRequirement ID: {}", retrieved_sig_req1.get_id());
|
||||
println!(
|
||||
"\nUpdating SignatureRequirement ID: {}",
|
||||
retrieved_sig_req1.get_id()
|
||||
);
|
||||
retrieved_sig_req1.status = "Signed".to_string();
|
||||
retrieved_sig_req1.signed_by = Some("pubkey_legal_team_lead_hex_actual_signer".to_string());
|
||||
retrieved_sig_req1.signature = Some("mock_signature_base64_encoded".to_string());
|
||||
|
||||
db.collection().expect("can open sig_req collection").set(&retrieved_sig_req1).expect("can update sig_req1");
|
||||
db.collection()
|
||||
.expect("can open sig_req collection")
|
||||
.set(&retrieved_sig_req1)
|
||||
.expect("can update sig_req1");
|
||||
|
||||
let updated_sig_req1 = db
|
||||
.collection::<SignatureRequirement>()
|
||||
@@ -129,10 +159,13 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated_sig_req1.status, "Signed");
|
||||
assert_eq!(updated_sig_req1.signature.as_deref(), Some("mock_signature_base64_encoded"));
|
||||
assert_eq!(
|
||||
updated_sig_req1.signature.as_deref(),
|
||||
Some("mock_signature_base64_encoded")
|
||||
);
|
||||
println!("Updated SignatureRequirement: {:?}", updated_sig_req1);
|
||||
|
||||
// --- Delete a FlowStep ---
|
||||
// --- Delete a FlowStep ---
|
||||
// (In a real app, you might also want to delete associated SignatureRequirements first, or handle via DB constraints/cascade if supported)
|
||||
let step1_id_to_delete = step1_flow1.get_id();
|
||||
db.collection::<FlowStep>()
|
||||
@@ -157,7 +190,11 @@ fn main() {
|
||||
.expect("can load remaining steps for flow1");
|
||||
assert_eq!(remaining_steps_for_flow1.len(), 1);
|
||||
assert_eq!(remaining_steps_for_flow1[0].get_id(), step2_flow1.get_id());
|
||||
println!("Remaining FlowSteps for Flow ID {}: count = {}", retrieved_flow.get_id(), remaining_steps_for_flow1.len());
|
||||
println!(
|
||||
"Remaining FlowSteps for Flow ID {}: count = {}",
|
||||
retrieved_flow.get_id(),
|
||||
remaining_steps_for_flow1.len()
|
||||
);
|
||||
|
||||
println!("\nFlow example finished successfully!");
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::flow::register_flow_rhai_module;
|
||||
use rhai::Engine;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Initialize database with OurDB
|
||||
// Using a temporary/in-memory database for the example
|
||||
let db = Arc::new(OurDB::new("temp_flow_rhai_db", true).expect("Failed to create database"));
|
||||
|
||||
// Register flow Rhai module functions
|
||||
register_flow_rhai_module(&mut engine, db.clone());
|
||||
|
||||
// Load and evaluate the Rhai script
|
||||
let script_path_str = "examples/flow_rhai/flow.rhai";
|
||||
let script_path = Path::new(script_path_str);
|
||||
if !script_path.exists() {
|
||||
eprintln!("Error: Rhai script not found at {}", script_path_str);
|
||||
eprintln!("Please ensure the script 'flow.rhai' exists in the 'examples/flow_rhai/' directory.");
|
||||
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
|
||||
}
|
||||
|
||||
println!("Executing Rhai script: {}", script_path_str);
|
||||
let script = fs::read_to_string(script_path)?;
|
||||
|
||||
match engine.eval::<()>(&script) {
|
||||
Ok(_) => println!("\nRhai script executed successfully!"),
|
||||
Err(e) => eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,107 +0,0 @@
|
||||
// Hero Models - Flow Rhai Example
|
||||
print("Hero Models - Flow Rhai Example");
|
||||
print("=============================");
|
||||
|
||||
// Helper to format Option<String> (Dynamic in Rhai: String or ()) for printing
|
||||
fn format_optional(val, placeholder) {
|
||||
if val == () {
|
||||
placeholder
|
||||
} else {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
// The database instance is now implicitly passed to DB functions.
|
||||
print("DB instance will be implicitly passed.");
|
||||
|
||||
// --- Test Flow Model ---
|
||||
print("\n--- Testing Flow Model ---");
|
||||
// Create a new flow using the constructor and builder methods
|
||||
print("Creating a new flow (ID: 1, UUID: flow-uuid-001)...");
|
||||
let flow1 = new_flow(1, "flow-uuid-001")
|
||||
.name("Document Approval Workflow")
|
||||
.status("Active");
|
||||
|
||||
print("Flow object created: " + flow1);
|
||||
print("Flow ID: " + flow1.id);
|
||||
print("Flow UUID: " + flow1.flow_uuid);
|
||||
print("Flow Name: " + flow1.name);
|
||||
print("Flow Status: " + flow1.status);
|
||||
|
||||
// Save the flow to the database
|
||||
set_flow(flow1);
|
||||
print("Flow saved to database.");
|
||||
|
||||
// Retrieve the flow
|
||||
let retrieved_flow = get_flow_by_id(1);
|
||||
print("Retrieved Flow by ID (1): " + retrieved_flow.name + ", Status: " + retrieved_flow.status);
|
||||
|
||||
// --- Test FlowStep Model (as part of Flow) ---
|
||||
print("\n--- Testing FlowStep Model (as part of Flow) ---");
|
||||
// Create FlowSteps
|
||||
print("Creating flow steps and adding to flow...");
|
||||
let step1 = new_flow_step(101, 1) // id, step_order
|
||||
.description("Initial Review by Manager")
|
||||
.status("Pending");
|
||||
|
||||
let step2 = new_flow_step(102, 2) // id, step_order. Note: FlowStep ID 102 will be used for sig_req1 & sig_req2
|
||||
.description("Legal Team Sign-off")
|
||||
.status("Pending");
|
||||
|
||||
// Add steps to the flow created earlier
|
||||
flow1 = flow1.add_step(step1);
|
||||
flow1 = flow1.add_step(step2);
|
||||
|
||||
print("Flow now has " + flow1.steps.len() + " steps.");
|
||||
print("First step description: " + format_optional(flow1.steps[0].description, "[No Description]"));
|
||||
|
||||
// Re-save the flow with its steps
|
||||
set_flow(flow1);
|
||||
print("Flow with steps saved to database.");
|
||||
|
||||
// Retrieve the flow and check its steps
|
||||
let retrieved_flow_with_steps = get_flow_by_id(1);
|
||||
print("Retrieved Flow by ID (1) has " + retrieved_flow_with_steps.steps.len() + " step(s).");
|
||||
if retrieved_flow_with_steps.steps.len() > 0 {
|
||||
print("First step of retrieved flow: " + format_optional(retrieved_flow_with_steps.steps[0].description, "[No Description]"));
|
||||
}
|
||||
|
||||
// --- Test SignatureRequirement Model ---
|
||||
print("\n--- Testing SignatureRequirement Model ---");
|
||||
// Create SignatureRequirements (referencing FlowStep ID 102, which is step2)
|
||||
print("Creating signature requirements for step with ID 102...");
|
||||
let sig_req1 = new_signature_requirement(201, 102, "pubkey_legal_lead", "Legal Lead: Approve terms.")
|
||||
.status("Required");
|
||||
|
||||
let sig_req2 = new_signature_requirement(202, 102, "pubkey_general_counsel", "General Counsel: Final Approval.")
|
||||
.status("Required"); // signed_by and signature will default to None (Rust) / () (Rhai)
|
||||
|
||||
print("SigReq 1: " + sig_req1.message + " for PubKey: " + sig_req1.public_key + " (Status: " + sig_req1.status + ")");
|
||||
if sig_req2.signed_by == () {
|
||||
print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Not signed yet)");
|
||||
} else {
|
||||
print("SigReq 2: " + sig_req2.message + " for PubKey: " + sig_req2.public_key + " (Status: " + sig_req2.status + ", Signed by: " + format_optional(sig_req2.signed_by, "[Not Signed Yet]") + ")");
|
||||
}
|
||||
|
||||
|
||||
// Save signature requirements
|
||||
set_signature_requirement(sig_req1);
|
||||
set_signature_requirement(sig_req2);
|
||||
print("SignatureRequirements saved to database.");
|
||||
|
||||
// Retrieve a signature requirement
|
||||
let retrieved_sig_req = get_signature_requirement_by_id(201);
|
||||
print("Retrieved SignatureRequirement by ID (201): " + retrieved_sig_req.message);
|
||||
|
||||
// --- Test updating a SignatureRequirement ---
|
||||
print("\n--- Testing Update for SignatureRequirement ---");
|
||||
let updated_sig_req = retrieved_sig_req
|
||||
.status("Signed")
|
||||
.signed_by("pubkey_legal_lead_actual_signer_id")
|
||||
.signature("base64_encoded_signature_data_here");
|
||||
|
||||
print("Updated SigReq 1 - Status: " + updated_sig_req.status + ", Signed By: " + format_optional(updated_sig_req.signed_by, "[Not Signed Yet]") + ", Signature: " + format_optional(updated_sig_req.signature, "[No Signature]"));
|
||||
set_signature_requirement(updated_sig_req); // Save updated
|
||||
print("Updated SignatureRequirement saved.");
|
||||
|
||||
print("\nFlow Rhai example script finished.");
|
@@ -1,290 +0,0 @@
|
||||
use chrono::{Duration, Utc};
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::governance::{
|
||||
Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption,
|
||||
};
|
||||
use rhai::Engine;
|
||||
use rhai_wrapper::wrap_vec_return;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Initialize database
|
||||
let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database"));
|
||||
|
||||
// Register the Proposal type with Rhai
|
||||
// This function is generated by the #[rhai_model_export] attribute
|
||||
Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone());
|
||||
|
||||
// Register the Ballot type with Rhai
|
||||
Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone());
|
||||
|
||||
// Register a function to get the database instance
|
||||
engine.register_fn("get_db", move || db.clone());
|
||||
|
||||
// Register builder functions for Proposal and related types
|
||||
engine.register_fn(
|
||||
"create_proposal",
|
||||
|id: i64, creator_id: String, creator_name: String, title: String, description: String| {
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
let id_option = if id <= 0 { None } else { Some(id as u32) };
|
||||
Proposal::new(
|
||||
id_option,
|
||||
creator_id,
|
||||
creator_name,
|
||||
title,
|
||||
description,
|
||||
ProposalStatus::Draft,
|
||||
Utc::now(),
|
||||
Utc::now(),
|
||||
start_date,
|
||||
end_date,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn("create_vote_option", |id: i64, text: String| {
|
||||
VoteOption::new(id as u8, text, Some("This is an optional comment"))
|
||||
});
|
||||
|
||||
engine.register_fn(
|
||||
"create_ballot",
|
||||
|id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
|
||||
let id_option = if id <= 0 { None } else { Some(id as u32) };
|
||||
Ballot::new(
|
||||
id_option,
|
||||
user_id as u32,
|
||||
vote_option_id as u8,
|
||||
shares_count,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// Register getter and setter methods for Proposal properties
|
||||
engine.register_fn("get_title", |proposal: Proposal| -> String {
|
||||
proposal.title.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_description", |proposal: Proposal| -> String {
|
||||
proposal.description.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_creator_id", |proposal: Proposal| -> String {
|
||||
proposal.creator_id.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_id", |proposal: Proposal| -> i64 {
|
||||
proposal.base_data.id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_status", |proposal: Proposal| -> String {
|
||||
format!("{:?}", proposal.status)
|
||||
});
|
||||
|
||||
engine.register_fn("get_vote_status", |proposal: Proposal| -> String {
|
||||
format!("{:?}", proposal.vote_status)
|
||||
});
|
||||
|
||||
// Register methods for proposal operations
|
||||
engine.register_fn(
|
||||
"add_option_to_proposal",
|
||||
|mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal {
|
||||
proposal.add_option(
|
||||
option_id as u8,
|
||||
option_text,
|
||||
Some("This is an optional comment".to_string()),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"cast_vote_on_proposal",
|
||||
|mut proposal: Proposal,
|
||||
ballot_id: i64,
|
||||
user_id: i64,
|
||||
option_id: i64,
|
||||
shares: i64|
|
||||
-> Proposal {
|
||||
let ballot_id_option = if ballot_id <= 0 {
|
||||
None
|
||||
} else {
|
||||
Some(ballot_id as u32)
|
||||
};
|
||||
proposal.cast_vote(ballot_id_option, user_id as u32, option_id as u8, shares)
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"change_proposal_status",
|
||||
|mut proposal: Proposal, status_str: String| -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Draft" => ProposalStatus::Draft,
|
||||
"Active" => ProposalStatus::Active,
|
||||
"Approved" => ProposalStatus::Approved,
|
||||
"Rejected" => ProposalStatus::Rejected,
|
||||
"Cancelled" => ProposalStatus::Cancelled,
|
||||
_ => ProposalStatus::Draft,
|
||||
};
|
||||
proposal.change_proposal_status(new_status)
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"change_vote_event_status",
|
||||
|mut proposal: Proposal, status_str: String| -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Open" => VoteEventStatus::Open,
|
||||
"Closed" => VoteEventStatus::Closed,
|
||||
"Cancelled" => VoteEventStatus::Cancelled,
|
||||
_ => VoteEventStatus::Open,
|
||||
};
|
||||
proposal.change_vote_event_status(new_status)
|
||||
},
|
||||
);
|
||||
|
||||
// Register functions for database operations
|
||||
engine.register_fn("save_proposal", |_db: Arc<OurDB>, proposal: Proposal| {
|
||||
println!("Proposal saved: {}", proposal.title);
|
||||
});
|
||||
|
||||
engine.register_fn(
|
||||
"get_proposal_by_id",
|
||||
|_db: Arc<OurDB>, id: i64| -> Proposal {
|
||||
// In a real implementation, this would retrieve the proposal from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(
|
||||
Some(id as u32),
|
||||
"Retrieved Creator",
|
||||
"Retrieved Creator Name",
|
||||
"Retrieved Proposal",
|
||||
"Retrieved Description",
|
||||
ProposalStatus::Draft,
|
||||
Utc::now(),
|
||||
Utc::now(),
|
||||
start_date,
|
||||
end_date,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// Register a function to check if a proposal exists
|
||||
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
||||
// In a real implementation, this would check if the proposal exists in the database
|
||||
id == 1 || id == 2
|
||||
});
|
||||
|
||||
// Define the function for get_all_proposals
|
||||
fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
|
||||
// In a real implementation, this would retrieve all proposals from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
vec![
|
||||
Proposal::new(
|
||||
Some(1),
|
||||
"Creator 1",
|
||||
"Creator Name 1",
|
||||
"Proposal 1",
|
||||
"Description 1",
|
||||
ProposalStatus::Draft,
|
||||
Utc::now(),
|
||||
Utc::now(),
|
||||
start_date,
|
||||
end_date,
|
||||
),
|
||||
Proposal::new(
|
||||
Some(2),
|
||||
"Creator 2",
|
||||
"Creator Name 2",
|
||||
"Proposal 2",
|
||||
"Description 2",
|
||||
ProposalStatus::Draft,
|
||||
Utc::now(),
|
||||
Utc::now(),
|
||||
start_date,
|
||||
end_date,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
// Register the function with the wrap_vec_return macro
|
||||
engine.register_fn(
|
||||
"get_all_proposals",
|
||||
wrap_vec_return!(get_all_proposals, Arc<OurDB> => Proposal),
|
||||
);
|
||||
|
||||
engine.register_fn("delete_proposal_by_id", |_db: Arc<OurDB>, _id: i64| {
|
||||
// In a real implementation, this would delete the proposal from the database
|
||||
println!("Proposal deleted with ID: {}", _id);
|
||||
});
|
||||
|
||||
// Register helper functions for accessing proposal options and ballots
|
||||
engine.register_fn("get_option_count", |proposal: Proposal| -> i64 {
|
||||
proposal.options.len() as i64
|
||||
});
|
||||
|
||||
engine.register_fn(
|
||||
"get_option_at",
|
||||
|proposal: Proposal, index: i64| -> VoteOption {
|
||||
if index >= 0 && index < proposal.options.len() as i64 {
|
||||
proposal.options[index as usize].clone()
|
||||
} else {
|
||||
VoteOption::new(
|
||||
0,
|
||||
"Invalid Option",
|
||||
Some("This is an invalid option".to_string()),
|
||||
)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn("get_option_text", |option: VoteOption| -> String {
|
||||
option.text.clone()
|
||||
});
|
||||
|
||||
engine.register_fn("get_option_votes", |option: VoteOption| -> i64 {
|
||||
option.count
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_count", |proposal: Proposal| -> i64 {
|
||||
proposal.ballots.len() as i64
|
||||
});
|
||||
|
||||
engine.register_fn(
|
||||
"get_ballot_at",
|
||||
|proposal: Proposal, index: i64| -> Ballot {
|
||||
if index >= 0 && index < proposal.ballots.len() as i64 {
|
||||
proposal.ballots[index as usize].clone()
|
||||
} else {
|
||||
Ballot::new(None, 0, 0, 0)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn("get_ballot_user_id", |ballot: Ballot| -> i64 {
|
||||
ballot.user_id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_option_id", |ballot: Ballot| -> i64 {
|
||||
ballot.vote_option_id as i64
|
||||
});
|
||||
|
||||
engine.register_fn("get_ballot_shares", |ballot: Ballot| -> i64 {
|
||||
ballot.shares_count
|
||||
});
|
||||
|
||||
// Load and evaluate the Rhai script
|
||||
let script_path = Path::new("examples/governance_rhai/governance.rhai");
|
||||
let script = fs::read_to_string(script_path)?;
|
||||
|
||||
match engine.eval::<()>(&script) {
|
||||
Ok(_) => println!("Script executed successfully!"),
|
||||
Err(e) => eprintln!("Script execution failed: {}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
// Get the database instance
|
||||
let db = get_db();
|
||||
|
||||
// Create a new proposal with auto-generated ID (pass 0 for auto-generated ID)
|
||||
let proposal = create_proposal(0, "user_creator_123", "Community Fund Allocation for Q3",
|
||||
"Proposal to allocate funds for community projects in the third quarter.");
|
||||
|
||||
print("Created Proposal: '" + get_title(proposal) + "' (ID: " + get_id(proposal) + ")");
|
||||
print("Status: " + get_status(proposal) + ", Vote Status: " + get_vote_status(proposal));
|
||||
|
||||
// Add vote options
|
||||
let proposal_with_options = add_option_to_proposal(proposal, 1, "Approve Allocation");
|
||||
proposal_with_options = add_option_to_proposal(proposal_with_options, 2, "Reject Allocation");
|
||||
proposal_with_options = add_option_to_proposal(proposal_with_options, 3, "Abstain");
|
||||
|
||||
print("\nAdded Vote Options:");
|
||||
let option_count = get_option_count(proposal_with_options);
|
||||
for i in range(0, option_count) {
|
||||
let option = get_option_at(proposal_with_options, i);
|
||||
print("- Option ID: " + i + ", Text: '" + get_option_text(option) + "', Votes: " + get_option_votes(option));
|
||||
}
|
||||
|
||||
// Save the proposal to the database
|
||||
save_proposal(db, proposal_with_options);
|
||||
print("\nProposal saved to database");
|
||||
|
||||
// Simulate casting votes
|
||||
print("\nSimulating Votes...");
|
||||
// User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID)
|
||||
let proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100);
|
||||
// User 2 votes for 'Reject Allocation' with 50 shares (with explicit ballot ID)
|
||||
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50);
|
||||
// User 3 votes for 'Approve Allocation' with 75 shares (with auto-generated ballot ID)
|
||||
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 3, 1, 75);
|
||||
// User 4 abstains with 20 shares (with auto-generated ballot ID)
|
||||
proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 4, 3, 20);
|
||||
|
||||
print("\nVote Counts After Simulation:");
|
||||
option_count = get_option_count(proposal_with_votes);
|
||||
for i in range(0, option_count) {
|
||||
let option = get_option_at(proposal_with_votes, i);
|
||||
print("- Option ID: " + i + ", Text: '" + get_option_text(option) + "', Votes: " + get_option_votes(option));
|
||||
}
|
||||
|
||||
print("\nBallots Cast:");
|
||||
let ballot_count = get_ballot_count(proposal_with_votes);
|
||||
for i in range(0, ballot_count) {
|
||||
let ballot = get_ballot_at(proposal_with_votes, i);
|
||||
print("- Ballot ID: " + i + ", User ID: " + get_ballot_user_id(ballot) +
|
||||
", Option ID: " + get_ballot_option_id(ballot) + ", Shares: " + get_ballot_shares(ballot));
|
||||
}
|
||||
|
||||
// Change proposal status
|
||||
let active_proposal = change_proposal_status(proposal_with_votes, "Active");
|
||||
print("\nChanged Proposal Status to: " + get_status(active_proposal));
|
||||
|
||||
// Simulate closing the vote
|
||||
let closed_proposal = change_vote_event_status(active_proposal, "Closed");
|
||||
print("Changed Vote Event Status to: " + get_vote_status(closed_proposal));
|
||||
|
||||
// Final proposal state
|
||||
print("\nFinal Proposal State:");
|
||||
print("Title: '" + get_title(closed_proposal) + "'");
|
||||
print("Status: " + get_status(closed_proposal));
|
||||
print("Vote Status: " + get_vote_status(closed_proposal));
|
||||
print("Options:");
|
||||
option_count = get_option_count(closed_proposal);
|
||||
for i in range(0, option_count) {
|
||||
let option = get_option_at(closed_proposal, i);
|
||||
print(" - " + i + ": " + get_option_text(option) + " (Votes: " + get_option_votes(option) + ")");
|
||||
}
|
||||
print("Total Ballots: " + get_ballot_count(closed_proposal));
|
||||
|
||||
// Get all proposals from the database
|
||||
let all_proposals = get_all_proposals(db);
|
||||
print("\nTotal Proposals in Database: " + all_proposals.len());
|
||||
for proposal in all_proposals {
|
||||
print("Proposal ID: " + get_id(proposal) + ", Title: '" + get_title(proposal) + "'");
|
||||
}
|
||||
|
||||
// Delete a proposal
|
||||
delete_proposal_by_id(db, 1);
|
||||
print("\nDeleted proposal with ID 1");
|
||||
|
||||
print("\nGovernance Proposal Example Finished.");
|
@@ -1,366 +0,0 @@
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot};
|
||||
use rhai::Engine;
|
||||
use rhai_wrapper::wrap_vec_return;
|
||||
use std::sync::Arc;
|
||||
use chrono::{Utc, Duration};
|
||||
use rhai_client_macros::rhai;
|
||||
|
||||
// Define the functions we want to expose to Rhai
|
||||
// We'll only use the #[rhai] attribute on functions with simple types
|
||||
|
||||
// Create a proposal (returns a complex type, but takes simple parameters)
|
||||
fn create_proposal(id: i64, creator_id: String, title: String, description: String) -> Proposal {
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
|
||||
}
|
||||
|
||||
// Getter functions for Proposal properties
|
||||
fn get_title(proposal: &Proposal) -> String {
|
||||
proposal.title.clone()
|
||||
}
|
||||
|
||||
fn get_description(proposal: &Proposal) -> String {
|
||||
proposal.description.clone()
|
||||
}
|
||||
|
||||
fn get_creator_id(proposal: &Proposal) -> String {
|
||||
proposal.creator_id.clone()
|
||||
}
|
||||
|
||||
fn get_id(proposal: &Proposal) -> i64 {
|
||||
proposal.base_data.id as i64
|
||||
}
|
||||
|
||||
fn get_status(proposal: &Proposal) -> String {
|
||||
format!("{:?}", proposal.status)
|
||||
}
|
||||
|
||||
fn get_vote_status(proposal: &Proposal) -> String {
|
||||
format!("{:?}", proposal.vote_status)
|
||||
}
|
||||
|
||||
// Functions that operate on Proposal objects
|
||||
fn add_option_to_proposal(proposal: Proposal, option_id: i64, option_text: String) -> Proposal {
|
||||
proposal.add_option(option_id as u8, option_text)
|
||||
}
|
||||
|
||||
fn cast_vote_on_proposal(proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64) -> Proposal {
|
||||
proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares)
|
||||
}
|
||||
|
||||
fn change_proposal_status(proposal: Proposal, status_str: String) -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Draft" => ProposalStatus::Draft,
|
||||
"Active" => ProposalStatus::Active,
|
||||
"Approved" => ProposalStatus::Approved,
|
||||
"Rejected" => ProposalStatus::Rejected,
|
||||
"Cancelled" => ProposalStatus::Cancelled,
|
||||
_ => ProposalStatus::Draft,
|
||||
};
|
||||
proposal.change_proposal_status(new_status)
|
||||
}
|
||||
|
||||
fn change_vote_event_status(proposal: Proposal, status_str: String) -> Proposal {
|
||||
let new_status = match status_str.as_str() {
|
||||
"Open" => VoteEventStatus::Open,
|
||||
"Closed" => VoteEventStatus::Closed,
|
||||
"Cancelled" => VoteEventStatus::Cancelled,
|
||||
_ => VoteEventStatus::Open,
|
||||
};
|
||||
proposal.change_vote_event_status(new_status)
|
||||
}
|
||||
|
||||
// Functions for accessing proposal options and ballots
|
||||
fn get_option_count(proposal: &Proposal) -> i64 {
|
||||
proposal.options.len() as i64
|
||||
}
|
||||
|
||||
fn get_option_at(proposal: &Proposal, index: i64) -> VoteOption {
|
||||
if index >= 0 && index < proposal.options.len() as i64 {
|
||||
proposal.options[index as usize].clone()
|
||||
} else {
|
||||
VoteOption::new(0, "Invalid Option")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_option_text(option: &VoteOption) -> String {
|
||||
option.text.clone()
|
||||
}
|
||||
|
||||
fn get_option_votes(option: &VoteOption) -> i64 {
|
||||
option.count
|
||||
}
|
||||
|
||||
fn get_ballot_count(proposal: &Proposal) -> i64 {
|
||||
proposal.ballots.len() as i64
|
||||
}
|
||||
|
||||
fn get_ballot_at(proposal: &Proposal, index: i64) -> Ballot {
|
||||
if index >= 0 && index < proposal.ballots.len() as i64 {
|
||||
proposal.ballots[index as usize].clone()
|
||||
} else {
|
||||
Ballot::new(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ballot_user_id(ballot: &Ballot) -> i64 {
|
||||
ballot.user_id as i64
|
||||
}
|
||||
|
||||
fn get_ballot_option_id(ballot: &Ballot) -> i64 {
|
||||
ballot.vote_option_id as i64
|
||||
}
|
||||
|
||||
fn get_ballot_shares(ballot: &Ballot) -> i64 {
|
||||
ballot.shares_count
|
||||
}
|
||||
|
||||
// Simple functions that we can use with the #[rhai] attribute
|
||||
#[rhai]
|
||||
fn create_proposal_wrapper(id: i64, creator_id: String, title: String, description: String) -> String {
|
||||
let proposal = create_proposal(id, creator_id, title, description);
|
||||
format!("Created proposal with ID: {}", proposal.base_data.id)
|
||||
}
|
||||
|
||||
#[rhai]
|
||||
fn add_option_wrapper(id: i64, option_id: i64, option_text: String) -> String {
|
||||
let proposal = create_proposal(id, "user".to_string(), "title".to_string(), "description".to_string());
|
||||
let updated = add_option_to_proposal(proposal, option_id, option_text.clone());
|
||||
format!("Added option '{}' to proposal {}", option_text, id)
|
||||
}
|
||||
|
||||
// Database operations
|
||||
fn save_proposal(_db: Arc<OurDB>, proposal: Proposal) {
|
||||
println!("Proposal saved: {}", proposal.title);
|
||||
}
|
||||
|
||||
fn get_all_proposals(_db: Arc<OurDB>) -> Vec<Proposal> {
|
||||
// In a real implementation, this would retrieve all proposals from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
vec![
|
||||
Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date),
|
||||
Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date)
|
||||
]
|
||||
}
|
||||
|
||||
fn delete_proposal_by_id(_db: Arc<OurDB>, id: i64) {
|
||||
// In a real implementation, this would delete the proposal from the database
|
||||
println!("Proposal deleted with ID: {}", id);
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Initialize database
|
||||
let db = Arc::new(OurDB::new("temp_governance_db", true).expect("Failed to create database"));
|
||||
|
||||
// Register the Proposal type with Rhai
|
||||
// This function is generated by the #[rhai_model_export] attribute
|
||||
Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone());
|
||||
|
||||
// Register the Ballot type with Rhai
|
||||
Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone());
|
||||
|
||||
// Create a clone of db for use in the get_db function
|
||||
let db_for_get_db = db.clone();
|
||||
|
||||
// Register a function to get the database instance
|
||||
engine.register_fn("get_db", move || db_for_get_db.clone());
|
||||
|
||||
// Register builder functions for Proposal and related types
|
||||
engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| {
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(id as u32, creator_id, title, description, start_date, end_date)
|
||||
});
|
||||
|
||||
engine.register_fn("create_vote_option", |id: i64, text: String| {
|
||||
VoteOption::new(id as u8, text)
|
||||
});
|
||||
|
||||
engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| {
|
||||
Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count)
|
||||
});
|
||||
|
||||
// Register getter and setter methods for Proposal properties
|
||||
engine.register_fn("get_title", get_title);
|
||||
engine.register_fn("get_description", get_description);
|
||||
engine.register_fn("get_creator_id", get_creator_id);
|
||||
engine.register_fn("get_id", get_id);
|
||||
engine.register_fn("get_status", get_status);
|
||||
engine.register_fn("get_vote_status", get_vote_status);
|
||||
|
||||
// Register methods for proposal operations
|
||||
engine.register_fn("add_option_to_proposal", add_option_to_proposal);
|
||||
engine.register_fn("cast_vote_on_proposal", cast_vote_on_proposal);
|
||||
engine.register_fn("change_proposal_status", change_proposal_status);
|
||||
engine.register_fn("change_vote_event_status", change_vote_event_status);
|
||||
|
||||
// Register functions for database operations
|
||||
engine.register_fn("save_proposal", save_proposal);
|
||||
|
||||
engine.register_fn("get_proposal_by_id", |_db: Arc<OurDB>, id: i64| -> Proposal {
|
||||
// In a real implementation, this would retrieve the proposal from the database
|
||||
let start_date = Utc::now();
|
||||
let end_date = start_date + Duration::days(14);
|
||||
Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date)
|
||||
});
|
||||
|
||||
// Register a function to check if a proposal exists
|
||||
engine.register_fn("proposal_exists", |_db: Arc<OurDB>, id: i64| -> bool {
|
||||
// In a real implementation, this would check if the proposal exists in the database
|
||||
id == 1 || id == 2
|
||||
});
|
||||
|
||||
// Register the function with the wrap_vec_return macro
|
||||
engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc<OurDB> => Proposal));
|
||||
|
||||
engine.register_fn("delete_proposal_by_id", delete_proposal_by_id);
|
||||
|
||||
// Register helper functions for accessing proposal options and ballots
|
||||
engine.register_fn("get_option_count", get_option_count);
|
||||
engine.register_fn("get_option_at", get_option_at);
|
||||
engine.register_fn("get_option_text", get_option_text);
|
||||
engine.register_fn("get_option_votes", get_option_votes);
|
||||
engine.register_fn("get_ballot_count", get_ballot_count);
|
||||
engine.register_fn("get_ballot_at", get_ballot_at);
|
||||
engine.register_fn("get_ballot_user_id", get_ballot_user_id);
|
||||
engine.register_fn("get_ballot_option_id", get_ballot_option_id);
|
||||
engine.register_fn("get_ballot_shares", get_ballot_shares);
|
||||
|
||||
// Register our wrapper functions that use the #[rhai] attribute
|
||||
engine.register_fn("create_proposal_wrapper", create_proposal_wrapper);
|
||||
engine.register_fn("add_option_wrapper", add_option_wrapper);
|
||||
|
||||
// Now instead of loading and evaluating a Rhai script, we'll use direct function calls
|
||||
// to implement the same functionality
|
||||
|
||||
// Use the database instance
|
||||
|
||||
// Create a new proposal
|
||||
let proposal = create_proposal(1,
|
||||
"user_creator_123".to_string(),
|
||||
"Community Fund Allocation for Q3".to_string(),
|
||||
"Proposal to allocate funds for community projects in the third quarter.".to_string());
|
||||
|
||||
println!("Created Proposal: '{}' (ID: {})",
|
||||
get_title(&proposal),
|
||||
get_id(&proposal));
|
||||
println!("Status: {}, Vote Status: {}",
|
||||
get_status(&proposal),
|
||||
get_vote_status(&proposal));
|
||||
|
||||
// Add vote options
|
||||
let mut proposal_with_options = add_option_to_proposal(
|
||||
proposal, 1, "Approve Allocation".to_string());
|
||||
proposal_with_options = add_option_to_proposal(
|
||||
proposal_with_options, 2, "Reject Allocation".to_string());
|
||||
proposal_with_options = add_option_to_proposal(
|
||||
proposal_with_options, 3, "Abstain".to_string());
|
||||
|
||||
println!("\nAdded Vote Options:");
|
||||
let option_count = get_option_count(&proposal_with_options);
|
||||
for i in 0..option_count {
|
||||
let option = get_option_at(&proposal_with_options, i);
|
||||
println!("- Option ID: {}, Text: '{}', Votes: {}",
|
||||
i, get_option_text(&option),
|
||||
get_option_votes(&option));
|
||||
}
|
||||
|
||||
// Save the proposal to the database
|
||||
save_proposal(db.clone(), proposal_with_options.clone());
|
||||
println!("\nProposal saved to database");
|
||||
|
||||
// Simulate casting votes
|
||||
println!("\nSimulating Votes...");
|
||||
// User 1 votes for 'Approve Allocation' with 100 shares
|
||||
let mut proposal_with_votes = cast_vote_on_proposal(
|
||||
proposal_with_options, 101, 1, 1, 100);
|
||||
// User 2 votes for 'Reject Allocation' with 50 shares
|
||||
proposal_with_votes = cast_vote_on_proposal(
|
||||
proposal_with_votes, 102, 2, 2, 50);
|
||||
// User 3 votes for 'Approve Allocation' with 75 shares
|
||||
proposal_with_votes = cast_vote_on_proposal(
|
||||
proposal_with_votes, 103, 3, 1, 75);
|
||||
// User 4 abstains with 20 shares
|
||||
proposal_with_votes = cast_vote_on_proposal(
|
||||
proposal_with_votes, 104, 4, 3, 20);
|
||||
|
||||
println!("\nVote Counts After Simulation:");
|
||||
let option_count = get_option_count(&proposal_with_votes);
|
||||
for i in 0..option_count {
|
||||
let option = get_option_at(&proposal_with_votes, i);
|
||||
println!("- Option ID: {}, Text: '{}', Votes: {}",
|
||||
i, get_option_text(&option),
|
||||
get_option_votes(&option));
|
||||
}
|
||||
|
||||
println!("\nBallots Cast:");
|
||||
let ballot_count = get_ballot_count(&proposal_with_votes);
|
||||
for i in 0..ballot_count {
|
||||
let ballot = get_ballot_at(&proposal_with_votes, i);
|
||||
println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
|
||||
i, get_ballot_user_id(&ballot),
|
||||
get_ballot_option_id(&ballot),
|
||||
get_ballot_shares(&ballot));
|
||||
}
|
||||
|
||||
// Change proposal status
|
||||
let active_proposal = change_proposal_status(
|
||||
proposal_with_votes, "Active".to_string());
|
||||
println!("\nChanged Proposal Status to: {}",
|
||||
get_status(&active_proposal));
|
||||
|
||||
// Simulate closing the vote
|
||||
let closed_proposal = change_vote_event_status(
|
||||
active_proposal, "Closed".to_string());
|
||||
println!("Changed Vote Event Status to: {}",
|
||||
get_vote_status(&closed_proposal));
|
||||
|
||||
// Final proposal state
|
||||
println!("\nFinal Proposal State:");
|
||||
println!("Title: '{}'", get_title(&closed_proposal));
|
||||
println!("Status: {}", get_status(&closed_proposal));
|
||||
println!("Vote Status: {}", get_vote_status(&closed_proposal));
|
||||
println!("Options:");
|
||||
let option_count = get_option_count(&closed_proposal);
|
||||
for i in 0..option_count {
|
||||
let option = get_option_at(&closed_proposal, i);
|
||||
println!(" - {}: {} (Votes: {})",
|
||||
i, get_option_text(&option),
|
||||
get_option_votes(&option));
|
||||
}
|
||||
println!("Total Ballots: {}", get_ballot_count(&closed_proposal));
|
||||
|
||||
// Get all proposals from the database
|
||||
let all_proposals = get_all_proposals(db.clone());
|
||||
println!("\nTotal Proposals in Database: {}", all_proposals.len());
|
||||
for proposal in all_proposals {
|
||||
println!("Proposal ID: {}, Title: '{}'",
|
||||
get_id(&proposal),
|
||||
get_title(&proposal));
|
||||
}
|
||||
|
||||
// Delete a proposal
|
||||
delete_proposal_by_id(db.clone(), 1);
|
||||
println!("\nDeleted proposal with ID 1");
|
||||
|
||||
// Demonstrate the use of Rhai client functions for simple types
|
||||
println!("\nUsing Rhai client functions for simple types:");
|
||||
let create_result = create_proposal_wrapper_rhai_client(&engine, 2,
|
||||
"rhai_user".to_string(),
|
||||
"Rhai Proposal".to_string(),
|
||||
"This proposal was created using a Rhai client function".to_string());
|
||||
println!("{}", create_result);
|
||||
|
||||
let add_option_result = add_option_wrapper_rhai_client(&engine, 2, 4, "Rhai Option".to_string());
|
||||
println!("{}", add_option_result);
|
||||
|
||||
println!("\nGovernance Proposal Example Finished.");
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::legal::register_legal_rhai_module;
|
||||
use rhai::Engine;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Initialize database with OurDB
|
||||
// Using a temporary/in-memory database for the example (creates files in current dir)
|
||||
let db_name = "temp_legal_rhai_db";
|
||||
let db = Arc::new(OurDB::new(db_name, true).expect("Failed to create database"));
|
||||
|
||||
// Register legal Rhai module functions
|
||||
register_legal_rhai_module(&mut engine, db.clone());
|
||||
|
||||
// Load and evaluate the Rhai script
|
||||
let script_path_str = "examples/legal_rhai/legal.rhai";
|
||||
let script_path = Path::new(script_path_str);
|
||||
|
||||
if !script_path.exists() {
|
||||
eprintln!("Error: Rhai script not found at {}", script_path_str);
|
||||
eprintln!("Please ensure the script 'legal.rhai' exists in the 'examples/legal_rhai/' directory.");
|
||||
return Err(Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Rhai script not found: {}", script_path_str))));
|
||||
}
|
||||
|
||||
println!("Executing Rhai script: {}", script_path_str);
|
||||
let script = fs::read_to_string(script_path)?;
|
||||
|
||||
match engine.eval::<()>(&script) {
|
||||
Ok(_) => println!("\nRhai script executed successfully!"),
|
||||
Err(e) => {
|
||||
eprintln!("\nRhai script execution failed: {}\nDetails: {:#?}", e, e);
|
||||
// No explicit cleanup in this example, similar to flow_rhai_example
|
||||
return Err(e.into()); // Propagate the Rhai error
|
||||
}
|
||||
}
|
||||
|
||||
// No explicit cleanup in this example, similar to flow_rhai_example
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -1,174 +0,0 @@
|
||||
// heromodels - Legal Module Rhai Example
|
||||
print("Hero Models - Legal Rhai Example");
|
||||
print("===============================");
|
||||
|
||||
// Helper to format Option<String> (Dynamic in Rhai: String or ()) for printing
|
||||
fn format_optional_string(val, placeholder) {
|
||||
if val == () {
|
||||
placeholder
|
||||
} else {
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to format Option<i64> (Dynamic in Rhai: i64 or ()) for printing
|
||||
fn format_optional_int(val, placeholder) {
|
||||
if val == () {
|
||||
placeholder
|
||||
} else {
|
||||
"" + val // Convert int to string for concatenation
|
||||
}
|
||||
}
|
||||
|
||||
print("DB instance will be implicitly passed to DB functions.");
|
||||
|
||||
// --- Using Enum Constants ---
|
||||
print(`\n--- Enum Constants ---`);
|
||||
print(`ContractStatus Draft: ${ContractStatusConstants::Draft}`);
|
||||
print(`ContractStatus Active: ${ContractStatusConstants::Active}`);
|
||||
print(`SignerStatus Pending: ${SignerStatusConstants::Pending}`);
|
||||
print(`SignerStatus Signed: ${SignerStatusConstants::Signed}`);
|
||||
|
||||
// --- Test ContractSigner Model ---
|
||||
print("\n--- Testing ContractSigner Model ---");
|
||||
let signer1_id = "signer-uuid-001";
|
||||
let signer1 = new_contract_signer(signer1_id, "Alice Wonderland", "alice@example.com")
|
||||
.status(SignerStatusConstants::Pending)
|
||||
.comments("Alice is the primary signatory.");
|
||||
|
||||
print(`Signer 1 ID: ${signer1.id}, Name: ${signer1.name}, Email: ${signer1.email}`);
|
||||
print(`Signer 1 Status: ${signer1.status}, Comments: ${format_optional_string(signer1.comments, "N/A")}`);
|
||||
print(`Signer 1 Signed At: ${format_optional_int(signer1.signed_at, "Not signed")}`);
|
||||
print(`Signer 1 Last Reminder: ${format_optional_int(signer1.last_reminder_mail_sent_at, "Never sent")}`);
|
||||
print(`Signer 1 Signature Data: ${format_optional_string(signer1.signature_data, "No signature")}`);
|
||||
|
||||
let signer2_id = "signer-uuid-002";
|
||||
let signer2 = new_contract_signer(signer2_id, "Bob The Builder", "bob@example.com")
|
||||
.status(SignerStatusConstants::Signed)
|
||||
.signed_at(1678886400) // Example timestamp
|
||||
.comments("Bob has already signed.")
|
||||
.last_reminder_mail_sent_at(1678880000) // Example reminder timestamp
|
||||
.signature_data("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="); // Example signature
|
||||
|
||||
print(`Signer 2 ID: ${signer2.id}, Name: ${signer2.name}, Status: ${signer2.status}, Signed At: ${format_optional_int(signer2.signed_at, "N/A")}`);
|
||||
print(`Signer 2 Last Reminder: ${format_optional_int(signer2.last_reminder_mail_sent_at, "Never sent")}`);
|
||||
print(`Signer 2 Signature Data Length: ${signer2.signature_data.len()} characters`);
|
||||
|
||||
// --- Test ContractRevision Model ---
|
||||
print("\n--- Testing ContractRevision Model ---");
|
||||
let revision1_version = 1;
|
||||
let revision1 = new_contract_revision(revision1_version, "Initial draft content for the agreement, version 1.", 1678880000, "user-admin-01")
|
||||
.comments("First version of the contract.");
|
||||
|
||||
print(`Revision 1 Version: ${revision1.version}, Content: '${revision1.content}', Created At: ${revision1.created_at}, By: ${revision1.created_by}`);
|
||||
print(`Revision 1 Comments: ${format_optional_string(revision1.comments, "N/A")}`);
|
||||
|
||||
let revision2_version = 2;
|
||||
let revision2 = new_contract_revision(revision2_version, "Updated content with new clauses, version 2.", 1678882200, "user-legal-02");
|
||||
|
||||
// --- Test Contract Model ---
|
||||
print("\n--- Testing Contract Model ---");
|
||||
let contract1_base_id = 101;
|
||||
let contract1_uuid = "contract-uuid-xyz-001";
|
||||
|
||||
print(`Creating a new contract (ID: ${contract1_base_id}, UUID: ${contract1_uuid})...`);
|
||||
let contract1 = new_contract(contract1_base_id, contract1_uuid)
|
||||
.title("Master Service Agreement")
|
||||
.description("MSA between ACME Corp and Client Inc.")
|
||||
.contract_type("Services")
|
||||
.status(ContractStatusConstants::Draft)
|
||||
.created_by("user-admin-01")
|
||||
.terms_and_conditions("Standard terms and conditions apply. See Appendix A.")
|
||||
.start_date(1678900000)
|
||||
.current_version(revision1.version)
|
||||
.add_signer(signer1)
|
||||
.add_revision(revision1);
|
||||
|
||||
print(`Contract 1 Title: ${contract1.title}, Status: ${contract1.status}`);
|
||||
print(`Contract 1 Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}`);
|
||||
|
||||
// Add more data
|
||||
contract1 = contract1.add_signer(signer2).add_revision(revision2).current_version(revision2.version);
|
||||
print(`Contract 1 Updated Signers: ${contract1.signers.len()}, Revisions: ${contract1.revisions.len()}, Current Version: ${contract1.current_version}`);
|
||||
|
||||
// Save the contract to the database
|
||||
print("Saving contract1 to database...");
|
||||
set_contract(contract1);
|
||||
print("Contract1 saved.");
|
||||
|
||||
// Retrieve the contract
|
||||
print(`Retrieving contract by ID (${contract1_base_id})...`);
|
||||
let retrieved_contract = get_contract_by_id(contract1_base_id);
|
||||
print(`Retrieved Contract: ${retrieved_contract.title}, Status: ${retrieved_contract.status}`);
|
||||
print(`Retrieved Contract Signers: ${retrieved_contract.signers.len()}, Revisions: ${retrieved_contract.revisions.len()}`);
|
||||
if retrieved_contract.signers.len() > 0 {
|
||||
print(`First signer of retrieved contract: ${retrieved_contract.signers[0].name}`);
|
||||
}
|
||||
if retrieved_contract.revisions.len() > 0 {
|
||||
print(`First revision content of retrieved contract: '${retrieved_contract.revisions[0].content}'`);
|
||||
}
|
||||
|
||||
// --- Test updating a Contract ---
|
||||
print("\n--- Testing Update for Contract ---");
|
||||
let updated_contract = retrieved_contract
|
||||
.status(ContractStatusConstants::Active)
|
||||
.end_date(1700000000)
|
||||
.description("MSA (Active) between ACME Corp and Client Inc. with new addendum.");
|
||||
|
||||
print(`Updated Contract - Title: ${updated_contract.title}, Status: ${updated_contract.status}, End Date: ${format_optional_int(updated_contract.end_date, "N/A")}`);
|
||||
set_contract(updated_contract); // Save updated
|
||||
print("Updated Contract saved.");
|
||||
|
||||
let final_retrieved_contract = get_contract_by_id(contract1_base_id);
|
||||
print(`Final Retrieved Contract - Status: ${final_retrieved_contract.status}, Description: '${final_retrieved_contract.description}'`);
|
||||
|
||||
// --- Test Reminder Functionality ---
|
||||
print("\n--- Testing Reminder Functionality ---");
|
||||
let current_time = 1678900000; // Example current timestamp
|
||||
|
||||
// Test reminder functionality on signers
|
||||
if final_retrieved_contract.signers.len() > 0 {
|
||||
let test_signer = final_retrieved_contract.signers[0];
|
||||
print(`Testing reminder for signer: ${test_signer.name}`);
|
||||
|
||||
let can_send = can_send_reminder(test_signer, current_time);
|
||||
print(`Can send reminder: ${can_send}`);
|
||||
|
||||
let cooldown_remaining = reminder_cooldown_remaining(test_signer, current_time);
|
||||
print(`Cooldown remaining: ${format_optional_int(cooldown_remaining, "No cooldown")}`);
|
||||
|
||||
// Simulate sending a reminder
|
||||
if can_send {
|
||||
print("Simulating reminder sent...");
|
||||
mark_reminder_sent(test_signer, current_time);
|
||||
print("Reminder timestamp updated");
|
||||
|
||||
// Check cooldown after sending
|
||||
let new_cooldown = reminder_cooldown_remaining(test_signer, current_time);
|
||||
print(`New cooldown: ${format_optional_int(new_cooldown, "No cooldown")} seconds`);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Test Signature Functionality ---
|
||||
print("\n--- Testing Signature Functionality ---");
|
||||
|
||||
// Test signing with signature data
|
||||
let test_signer = new_contract_signer("test-signer-001", "Test Signer", "test@example.com");
|
||||
print(`Before signing: Status = ${test_signer.status}, Signature Data = ${format_optional_string(test_signer.signature_data, "None")}`);
|
||||
|
||||
// Sign with signature data
|
||||
sign(test_signer, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==", "I agree to the terms");
|
||||
print(`After signing: Status = ${test_signer.status}, Signature Data Length = ${test_signer.signature_data.len()}`);
|
||||
print(`Comments: ${format_optional_string(test_signer.comments, "No comments")}`);
|
||||
|
||||
// Test signing without signature data
|
||||
let test_signer2 = new_contract_signer("test-signer-002", "Test Signer 2", "test2@example.com");
|
||||
sign_without_signature(test_signer2, "Electronic signature without visual data");
|
||||
print(`Signer 2 after signing: Status = ${test_signer2.status}, Signature Data = ${format_optional_string(test_signer2.signature_data, "None")}`);
|
||||
|
||||
// Test simple signing
|
||||
let test_signer3 = new_contract_signer("test-signer-003", "Test Signer 3", "test3@example.com");
|
||||
sign_simple(test_signer3);
|
||||
print(`Signer 3 after simple signing: Status = ${test_signer3.status}`);
|
||||
|
||||
print("\nLegal Rhai example script finished.");
|
@@ -59,21 +59,39 @@ fn main() {
|
||||
println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys());
|
||||
|
||||
println!("\nBefore saving - CustomUser ID: {}", custom_user.get_id());
|
||||
println!("Before saving - CustomUser DB Keys: {:?}", custom_user.db_keys());
|
||||
println!(
|
||||
"Before saving - CustomUser DB Keys: {:?}",
|
||||
custom_user.db_keys()
|
||||
);
|
||||
|
||||
// Save the models to the database
|
||||
let simple_collection = db.collection::<SimpleUser>().expect("can open simple user collection");
|
||||
let custom_collection = db.collection::<CustomUser>().expect("can open custom user collection");
|
||||
let simple_collection = db
|
||||
.collection::<SimpleUser>()
|
||||
.expect("can open simple user collection");
|
||||
let custom_collection = db
|
||||
.collection::<CustomUser>()
|
||||
.expect("can open custom user collection");
|
||||
|
||||
let (user_id, saved_user) = simple_collection.set(&user).expect("can save simple user");
|
||||
let (custom_user_id, saved_custom_user) = custom_collection.set(&custom_user).expect("can save custom user");
|
||||
let (custom_user_id, saved_custom_user) = custom_collection
|
||||
.set(&custom_user)
|
||||
.expect("can save custom user");
|
||||
|
||||
println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id());
|
||||
println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys());
|
||||
println!(
|
||||
"After saving - SimpleUser DB Keys: {:?}",
|
||||
saved_user.db_keys()
|
||||
);
|
||||
println!("Returned SimpleUser ID: {}", user_id);
|
||||
|
||||
println!("\nAfter saving - CustomUser ID: {}", saved_custom_user.get_id());
|
||||
println!("After saving - CustomUser DB Keys: {:?}", saved_custom_user.db_keys());
|
||||
println!(
|
||||
"\nAfter saving - CustomUser ID: {}",
|
||||
saved_custom_user.get_id()
|
||||
);
|
||||
println!(
|
||||
"After saving - CustomUser DB Keys: {:?}",
|
||||
saved_custom_user.db_keys()
|
||||
);
|
||||
println!("Returned CustomUser ID: {}", custom_user_id);
|
||||
|
||||
// Verify that the IDs were auto-generated
|
||||
@@ -83,5 +101,8 @@ fn main() {
|
||||
assert_ne!(saved_custom_user.get_id(), 0);
|
||||
|
||||
println!("\nExample finished. DB stored at {}", db_path);
|
||||
println!("To clean up, you can manually delete the directory: {}", db_path);
|
||||
println!(
|
||||
"To clean up, you can manually delete the directory: {}",
|
||||
db_path
|
||||
);
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope};
|
||||
use std::sync::Arc;
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::models::projects::register_projects_rhai_module;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Executing Rhai script: examples/project_rhai/project_test.rhai");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Create an Arc<OurDB> instance
|
||||
let db_instance = Arc::new(OurDB::new(".", false).expect("Failed to create DB"));
|
||||
|
||||
// Register the projects module with the engine
|
||||
register_projects_rhai_module(&mut engine, Arc::clone(&db_instance));
|
||||
|
||||
// Read the Rhai script from file
|
||||
let script_path = "examples/project_rhai/project_test.rhai";
|
||||
let script_content = fs::read_to_string(script_path)
|
||||
.map_err(|e| Box::new(EvalAltResult::ErrorSystem(format!("Cannot read script file: {}", script_path), e.into())))?;
|
||||
|
||||
// Create a new scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Execute the script
|
||||
match engine.run_with_scope(&mut scope, &script_content) {
|
||||
Ok(_) => {
|
||||
println!("Rhai script executed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Rhai script execution failed: {}", e);
|
||||
println!("Details: {:?}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
// Test script for Project Rhai integration
|
||||
|
||||
print("--- Testing Project Rhai Integration ---");
|
||||
|
||||
// Create a new project
|
||||
let p1 = new_project()
|
||||
.name("Project Alpha")
|
||||
.description("This is the first test project.")
|
||||
.owner_id(101)
|
||||
.add_member_id(102)
|
||||
.add_member_id(103)
|
||||
.member_ids([201, 202, 203]) // Test setting multiple IDs
|
||||
.add_tag("important")
|
||||
.add_tag("rhai_test")
|
||||
.tags(["core", "feature_test"]) // Test setting multiple tags
|
||||
.status(Status::InProgress)
|
||||
.priority(Priority::High)
|
||||
.item_type(ItemType::Feature)
|
||||
.add_base_comment(1001);
|
||||
|
||||
print("Created project p1: " + p1);
|
||||
print("p1.name: " + p1.name);
|
||||
print("p1.description: " + p1.description);
|
||||
print("p1.owner_id: " + p1.owner_id);
|
||||
print("p1.member_ids: " + p1.member_ids);
|
||||
print("p1.tags: " + p1.tags);
|
||||
print(`p1.status: ${p1.status.to_string()}`);
|
||||
print(`p1.priority: ${p1.priority.to_string()}`);
|
||||
print(`p1.item_type: ${p1.item_type.to_string()}`);
|
||||
print("p1.id: " + p1.id);
|
||||
print("p1.created_at: " + p1.created_at);
|
||||
print("p1.modified_at: " + p1.modified_at);
|
||||
print("p1.comments: " + p1.comments);
|
||||
|
||||
// Save to DB
|
||||
try {
|
||||
set_project(p1);
|
||||
print("Project p1 saved successfully.");
|
||||
} catch (err) {
|
||||
print("Error saving project p1: " + err);
|
||||
}
|
||||
|
||||
// Retrieve from DB
|
||||
try {
|
||||
let retrieved_p1 = get_project_by_id(1);
|
||||
if retrieved_p1 != () { // Check if Some(project) was returned (None becomes '()')
|
||||
print("Retrieved project by ID 1: " + retrieved_p1);
|
||||
print("Retrieved project name: " + retrieved_p1.name);
|
||||
print("Retrieved project tags: " + retrieved_p1.tags);
|
||||
} else {
|
||||
print("Project with ID 1 not found.");
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error retrieving project by ID 1: " + err);
|
||||
}
|
||||
|
||||
// Test non-existent project
|
||||
try {
|
||||
let non_existent_project = get_project_by_id(999);
|
||||
if non_existent_project != () {
|
||||
print("Error: Found non-existent project 999: " + non_existent_project);
|
||||
} else {
|
||||
print("Correctly did not find project with ID 999.");
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error checking for non-existent project: " + err);
|
||||
}
|
||||
|
||||
print("--- Project Rhai Integration Test Complete ---");
|
@@ -1,9 +0,0 @@
|
||||
[build]
|
||||
# Set the default build target for this project
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
# Configuration for the wasm32-unknown-unknown target
|
||||
[target.wasm32-unknown-unknown]
|
||||
# Pass --cfg=wasm_js to rustc when compiling for this target.
|
||||
# This is required by the getrandom crate.
|
||||
rustflags = ["--cfg=wasm_js"] # For getrandom v0.3.x WASM support (required by rhai via ahash)
|
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "project_rhai_wasm_example"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
heromodels = { path = "../..", features = ["rhai"] } # Match heromodels main crate
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = "0.3"
|
||||
getrandom = { version = "0.3.3", features = ["js"] } # Align with rhai's dependency
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
lto = true
|
||||
opt-level = 's'
|
@@ -1,52 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HeroModels Project Rhai WASM Test</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
|
||||
#output { margin-top: 20px; padding: 10px; border: 1px solid #ccc; background-color: #fff; white-space: pre-wrap; }
|
||||
button { padding: 10px 15px; font-size: 16px; cursor: pointer; background-color: #007bff; color: white; border: none; border-radius: 5px; }
|
||||
button:hover { background-color: #0056b3; }
|
||||
h1 { color: #0056b3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>HeroModels Project Rhai WASM Test</h1>
|
||||
<p>Open your browser's developer console to see detailed logs from the Rhai script.</p>
|
||||
<button id="runScriptButton">Run Rhai Script in WASM</button>
|
||||
|
||||
<script type="module">
|
||||
// Import the WASM module
|
||||
import init, { run_project_script_wasm } from './pkg/project_rhai_wasm_example.js';
|
||||
|
||||
async function main() {
|
||||
// Initialize the WASM module
|
||||
await init();
|
||||
console.log("WASM module initialized.");
|
||||
|
||||
const runButton = document.getElementById('runScriptButton');
|
||||
runButton.onclick = () => {
|
||||
console.log("Button clicked, attempting to run script...");
|
||||
try {
|
||||
run_project_script_wasm();
|
||||
console.log("run_project_script_wasm called.");
|
||||
} catch (e) {
|
||||
console.error("Error calling run_project_script_wasm:", e);
|
||||
}
|
||||
};
|
||||
// Automatically run the script on load if desired
|
||||
// console.log("Attempting to run script on load...");
|
||||
// try {
|
||||
// run_project_script_wasm();
|
||||
// console.log("run_project_script_wasm called on load.");
|
||||
// } catch (e) {
|
||||
// console.error("Error calling run_project_script_wasm on load:", e);
|
||||
// }
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,126 +0,0 @@
|
||||
use heromodels::db::{OurDB, Db}; // Import Db trait
|
||||
use heromodels::models::projects::rhai::register_projects_rhai_module;
|
||||
use rhai::{Engine, Scope, Dynamic, EvalAltResult, Position};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::console;
|
||||
|
||||
// Called once when the WASM module is instantiated.
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main_wasm() -> Result<(), JsValue> {
|
||||
// For better panic messages in the browser console
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn run_project_script_wasm() -> Result<(), JsValue> {
|
||||
console::log_1(&"Starting Rhai script execution in WASM...".into());
|
||||
|
||||
let script = r#"
|
||||
// Test script for Project Rhai integration
|
||||
|
||||
print("--- Testing Project Rhai Integration (WASM) ---");
|
||||
|
||||
// Create a new project
|
||||
let p1 = new_project()
|
||||
.name("Project Alpha WASM")
|
||||
.description("This is the first test project in WASM.")
|
||||
.owner_id(101)
|
||||
.add_member_id(102)
|
||||
.add_member_id(103)
|
||||
.member_ids([201, 202, 203])
|
||||
.add_tag("important")
|
||||
.add_tag("rhai_test")
|
||||
.add_tag("wasm")
|
||||
.tags(["core", "feature_test", "wasm_run"])
|
||||
.status(Status::InProgress)
|
||||
.priority(Priority::High)
|
||||
.item_type(ItemType::Feature)
|
||||
.add_base_comment(1001);
|
||||
|
||||
print("Created project p1: " + p1);
|
||||
print("p1.name: " + p1.name);
|
||||
print("p1.description: " + p1.description);
|
||||
print("p1.owner_id: " + p1.owner_id);
|
||||
print("p1.member_ids: " + p1.member_ids);
|
||||
print("p1.tags: " + p1.tags);
|
||||
print(`p1.status: ${p1.status.to_string()}`);
|
||||
print(`p1.priority: ${p1.priority.to_string()}`);
|
||||
print(`p1.item_type: ${p1.item_type.to_string()}`);
|
||||
print("p1.id: " + p1.id);
|
||||
print("p1.created_at: " + p1.created_at);
|
||||
print("p1.modified_at: " + p1.modified_at);
|
||||
print("p1.comments: " + p1.comments);
|
||||
|
||||
// Save to DB
|
||||
try {
|
||||
set_project(p1);
|
||||
print("Project p1 saved successfully.");
|
||||
} catch (err) {
|
||||
print("Error saving project p1: " + err);
|
||||
}
|
||||
|
||||
// Retrieve from DB
|
||||
try {
|
||||
let retrieved_p1 = get_project_by_id(1);
|
||||
if retrieved_p1 != () { // Check if Some(project) was returned (None becomes '()')
|
||||
print("Retrieved project by ID 1: " + retrieved_p1);
|
||||
print("Retrieved project name: " + retrieved_p1.name);
|
||||
print("Retrieved project tags: " + retrieved_p1.tags);
|
||||
} else {
|
||||
print("Project with ID 1 not found.");
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error retrieving project by ID 1: " + err);
|
||||
}
|
||||
|
||||
// Test non-existent project
|
||||
try {
|
||||
let non_existent_project = get_project_by_id(999);
|
||||
if non_existent_project != () {
|
||||
print("Error: Found non-existent project 999: " + non_existent_project);
|
||||
} else {
|
||||
print("Correctly did not find project with ID 999.");
|
||||
}
|
||||
} catch (err) {
|
||||
print("Error checking for non-existent project: " + err);
|
||||
}
|
||||
|
||||
print("--- Project Rhai Integration Test Complete (WASM) ---");
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Redirect Rhai's print to browser console
|
||||
engine.on_print(|text| {
|
||||
console::log_1(&text.into());
|
||||
});
|
||||
|
||||
// Attempt to initialize OurDB. Sled's behavior in WASM with paths is experimental.
|
||||
// It might work as an in-memory/temporary DB, or it might fail.
|
||||
// Using a specific path and reset=true.
|
||||
let db = match OurDB::new("/project_rhai_wasm_db", true) {
|
||||
Ok(db_instance) => Arc::new(db_instance),
|
||||
Err(e) => {
|
||||
let err_msg = format!("Failed to initialize OurDB for WASM: {}", e);
|
||||
console::error_1(&err_msg.into());
|
||||
return Err(JsValue::from_str(&err_msg));
|
||||
}
|
||||
};
|
||||
|
||||
register_projects_rhai_module(&mut engine, db);
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
match engine.run_with_scope(&mut scope, script) {
|
||||
Ok(_) => console::log_1(&"Rhai script executed successfully in WASM!".into()),
|
||||
Err(e) => {
|
||||
let err_msg = format!("Rhai script execution failed in WASM: {}\nDetails: {:?}", e, e.to_string());
|
||||
console::error_1(&err_msg.into());
|
||||
return Err(JsValue::from_str(&e.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@@ -34,11 +34,16 @@ fn main() {
|
||||
println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys());
|
||||
|
||||
// Save the user to the database
|
||||
let collection = db.collection::<SimpleUser>().expect("can open user collection");
|
||||
let collection = db
|
||||
.collection::<SimpleUser>()
|
||||
.expect("can open user collection");
|
||||
let (user_id, saved_user) = collection.set(&user).expect("can save user");
|
||||
|
||||
println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id());
|
||||
println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys());
|
||||
println!(
|
||||
"After saving - SimpleUser DB Keys: {:?}",
|
||||
saved_user.db_keys()
|
||||
);
|
||||
println!("Returned ID: {}", user_id);
|
||||
|
||||
// Verify that the ID was auto-generated
|
||||
@@ -46,6 +51,8 @@ fn main() {
|
||||
assert_ne!(saved_user.get_id(), 0);
|
||||
|
||||
println!("\nExample finished. DB stored at {}", db_path);
|
||||
println!("To clean up, you can manually delete the directory: {}", db_path);
|
||||
println!(
|
||||
"To clean up, you can manually delete the directory: {}",
|
||||
db_path
|
||||
);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user