use actix_files::Files; use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use heromodels::db::hero::OurDB; use heromodels::models::calendar::Calendar; use heromodels::models::governance::{Proposal, Ballot, VoteOption, ProposalStatus, VoteEventStatus}; use rhai::Engine; use rhai_wrapper::wrap_vec_return; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use chrono::{Utc, Duration}; #[derive(Deserialize)] struct ScriptRequest { script: String, model_type: String, } #[derive(Serialize)] struct ScriptResponse { output: String, success: bool, } // Function to set up the calendar model in the Rhai engine fn setup_calendar_engine(engine: &mut Engine, db: Arc) { // Register the Calendar type with Rhai Calendar::register_rhai_bindings_for_calendar(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| { Calendar::new(id as u32) }); // 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, _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, id: i64| -> Calendar { // In a real implementation, this would retrieve the calendar from the database Calendar::new(id as u32) }); // Register a function to check if a calendar exists engine.register_fn("calendar_exists", |_db: Arc, 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) -> Vec { // In a real implementation, this would retrieve all calendars from the database vec![Calendar::new(1), Calendar::new(2)] } // Register the function with the wrap_vec_return macro engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc => Calendar)); engine.register_fn("delete_calendar_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the calendar from the database println!("Calendar deleted with ID: {}", _id); }); } // Function to set up the governance model in the Rhai engine fn setup_governance_engine(engine: &mut Engine, db: Arc) { // Register the Proposal type with Rhai Proposal::register_rhai_bindings_for_proposal(engine, db.clone()); // Register the Ballot type with Rhai Ballot::register_rhai_bindings_for_ballot(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, 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", |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", |proposal: Proposal, option_id: i64, option_text: String| -> Proposal { proposal.add_option(option_id as u8, option_text) }); engine.register_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) }); engine.register_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) }); engine.register_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) }); // Register functions for database operations engine.register_fn("save_proposal", |_db: Arc, proposal: Proposal| { println!("Proposal saved: {}", proposal.title); }); engine.register_fn("get_proposal_by_id", |_db: Arc, 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, 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) -> Vec { // 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) ] } // Register the function with the wrap_vec_return macro engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc => Proposal)); engine.register_fn("delete_proposal_by_id", |_db: Arc, _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") } }); 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(0, 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 }); } // Endpoint to execute Rhai scripts async fn execute_script(req: web::Json) -> impl Responder { // Create a string to capture stdout let output = Arc::new(Mutex::new(String::new())); // Initialize Rhai engine let mut engine = Engine::new(); // Register print function to capture output let output_clone = output.clone(); engine.register_fn("print", move |text: String| { if let Ok(mut output_guard) = output_clone.lock() { output_guard.push_str(&text); output_guard.push('\n'); } }); // Initialize database let db = Arc::new(OurDB::new("temp_rhai_playground_db", true).expect("Failed to create database")); // Set up the engine based on the model type match req.model_type.as_str() { "calendar" => setup_calendar_engine(&mut engine, db), "governance" => setup_governance_engine(&mut engine, db), _ => { return HttpResponse::BadRequest().json(ScriptResponse { output: "Invalid model type. Supported types: 'calendar', 'governance'".to_string(), success: false, }); } } // Execute the script match engine.eval::<()>(&req.script) { Ok(_) => { let output_str = output.lock().unwrap_or_else(|_| panic!("Failed to lock output")).clone(); HttpResponse::Ok().json(ScriptResponse { output: output_str, success: true, }) } Err(e) => { HttpResponse::Ok().json(ScriptResponse { output: format!("Script execution failed: {}", e), success: false, }) } } } // Endpoint to get example scripts async fn get_example_script(path: web::Path) -> impl Responder { let script_type = path.into_inner(); let script_content = match script_type.as_str() { "calendar" => { std::fs::read_to_string("../../db/heromodels/examples/calendar_rhai/calendar.rhai") .unwrap_or_else(|_| "// Failed to load calendar example".to_string()) } "governance" => { std::fs::read_to_string("../../db/heromodels/examples/governance_rhai/governance.rhai") .unwrap_or_else(|_| "// Failed to load governance example".to_string()) } _ => "// Invalid example type".to_string(), }; HttpResponse::Ok().body(script_content) } #[actix_web::main] async fn main() -> std::io::Result<()> { println!("Starting Rhai Web Playground server at http://localhost:8080"); HttpServer::new(|| { App::new() .service(web::resource("/api/execute").route(web::post().to(execute_script))) .service(web::resource("/api/example/{script_type}").route(web::get().to(get_example_script))) .service(Files::new("/", "./static").index_file("index.html")) }) .bind("127.0.0.1:8080")? .run() .await }