332 lines
12 KiB
Rust
332 lines
12 KiB
Rust
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<OurDB>) {
|
|
// 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<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(id as u32)
|
|
});
|
|
|
|
// 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(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<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);
|
|
});
|
|
}
|
|
|
|
// Function to set up the governance model in the Rhai engine
|
|
fn setup_governance_engine(engine: &mut Engine, db: Arc<OurDB>) {
|
|
// 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<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(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
|
|
});
|
|
|
|
// 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(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<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")
|
|
}
|
|
});
|
|
|
|
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<ScriptRequest>) -> 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<String>) -> 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
|
|
}
|