diff --git a/sandcrate-backend/src/api.rs b/sandcrate-backend/src/api.rs index 6592dd7..8576589 100644 --- a/sandcrate-backend/src/api.rs +++ b/sandcrate-backend/src/api.rs @@ -1,10 +1,16 @@ -use axum::{routing::get, Json, Router, extract::State}; -use serde::Serialize; +use axum::{ + routing::{get, post}, + Json, Router, extract::{State, Path}, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::{Serialize, Deserialize}; use std::sync::Arc; use std::fs; -use std::path::Path; +use std::path::Path as FsPath; use crate::auth::AuthConfig; +use crate::plugin; #[derive(Serialize)] struct Plugin { @@ -13,6 +19,7 @@ struct Plugin { filename: String, size: u64, created_at: String, + status: String, } #[derive(Serialize)] @@ -20,10 +27,38 @@ struct PluginList { plugins: Vec, } +#[derive(Serialize)] +struct PluginExecutionRequest { + parameters: Option, + timeout: Option, +} + +#[derive(Serialize)] +struct PluginExecutionResponse { + success: bool, + result: String, + execution_time_ms: u64, + error: Option, +} + +#[derive(Serialize)] +struct ApiResponse { + success: bool, + data: Option, + error: Option, +} + +#[derive(Serialize)] +struct ErrorResponse { + success: bool, + error: String, + code: String, +} + async fn get_plugins( State(_config): State>, -) -> Json { - let plugins_dir = Path::new("../assets/plugins"); +) -> Json> { + let plugins_dir = FsPath::new("assets/plugins"); let mut plugins = Vec::new(); if let Ok(entries) = fs::read_dir(plugins_dir) { @@ -58,6 +93,7 @@ async fn get_plugins( filename, size: metadata.len(), created_at, + status: "ready".to_string(), }); } } @@ -66,9 +102,151 @@ async fn get_plugins( } } - Json(PluginList { plugins }) + Json(ApiResponse { + success: true, + data: Some(PluginList { plugins }), + error: None, + }) +} + +async fn get_plugin( + Path(plugin_id): Path, +) -> Response { + let plugins_dir = FsPath::new("assets/plugins"); + let plugin_path = plugins_dir.join(format!("{}.wasm", plugin_id)); + + if !plugin_path.exists() { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse:: { + success: false, + data: None, + error: Some(format!("Plugin '{}' not found", plugin_id)), + }) + ).into_response(); + } + + if let Ok(metadata) = fs::metadata(&plugin_path) { + let filename = plugin_path.file_name() + .and_then(|n| n.to_str()) + .unwrap_or("unknown") + .to_string(); + + let name = filename.replace(".wasm", ""); + let id = name.clone(); + + let created_at = metadata.created() + .ok() + .and_then(|time| time.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|duration| { + chrono::DateTime::from_timestamp(duration.as_secs() as i64, 0) + .unwrap_or_default() + .format("%Y-%m-%d %H:%M:%S") + .to_string() + }) + .unwrap_or_else(|| "Unknown".to_string()); + + let plugin = Plugin { + id, + name, + filename, + size: metadata.len(), + created_at, + status: "ready".to_string(), + }; + + ( + StatusCode::OK, + Json(ApiResponse { + success: true, + data: Some(plugin), + error: None, + }) + ).into_response() + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiResponse:: { + success: false, + data: None, + error: Some("Failed to read plugin metadata".to_string()), + }) + ).into_response() + } +} + +async fn execute_plugin( + Path(plugin_id): Path, + Json(request): Json, +) -> Response { + let start_time = std::time::Instant::now(); + + // Find the plugin file + let plugins_dir = FsPath::new("assets/plugins"); + let plugin_path = plugins_dir.join(format!("{}.wasm", plugin_id)); + + if !plugin_path.exists() { + return ( + StatusCode::NOT_FOUND, + Json(ApiResponse:: { + success: false, + data: None, + error: Some(format!("Plugin '{}' not found", plugin_id)), + }) + ).into_response(); + } + + // Execute the plugin with parameters and timeout + let plugin_path_str = plugin_path.to_str().unwrap_or(""); + let execution_result = plugin::run_plugin_with_params( + plugin_path_str, + request.parameters, + request.timeout + ); + + let execution_time = start_time.elapsed(); + let execution_time_ms = execution_time.as_millis() as u64; + + match execution_result { + Ok(result) => { + let response = PluginExecutionResponse { + success: true, + result, + execution_time_ms, + error: None, + }; + + ( + StatusCode::OK, + Json(ApiResponse { + success: true, + data: Some(response), + error: None, + }) + ).into_response() + } + Err(e) => { + let response = PluginExecutionResponse { + success: false, + result: String::new(), + execution_time_ms, + error: Some(e.to_string()), + }; + + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ApiResponse { + success: true, + data: Some(response), + error: None, + }) + ).into_response() + } + } } pub fn routes() -> Router> { - Router::new().route("/plugins", get(get_plugins)) + Router::new() + .route("/plugins", get(get_plugins)) + .route("/plugins/:id", get(get_plugin)) } diff --git a/sandcrate-backend/src/plugin.rs b/sandcrate-backend/src/plugin.rs index 27c8a74..b98462c 100644 --- a/sandcrate-backend/src/plugin.rs +++ b/sandcrate-backend/src/plugin.rs @@ -1,7 +1,9 @@ use std::fs; use std::path::Path; +use std::time::Duration; use wasmtime::*; use wasmtime_wasi::WasiCtxBuilder; +use serde_json::Value; pub fn list_plugins() -> Vec { let plugins_dir = Path::new("assets/plugins"); @@ -26,6 +28,14 @@ pub fn list_plugins() -> Vec { } pub fn run_plugin(plugin_path: &str) -> Result> { + run_plugin_with_params(plugin_path, None, None) +} + +pub fn run_plugin_with_params( + plugin_path: &str, + parameters: Option, + _timeout: Option +) -> Result> { // Create a WASM engine let engine = Engine::default(); @@ -53,10 +63,13 @@ pub fn run_plugin(plugin_path: &str) -> Result(&mut store, func_name) { func.call(&mut store, ())?; executed = true; + result = format!("Plugin executed successfully using function '{}'", func_name); break; } } @@ -65,5 +78,47 @@ pub fn run_plugin(plugin_path: &str) -> Result Result> { + let path = Path::new(plugin_path); + + if !path.exists() { + return Err("Plugin file does not exist".into()); + } + + let metadata = fs::metadata(path)?; + let file_size = metadata.len(); + + // Try to read WASM module info + let wasm_bytes = fs::read(path)?; + let engine = Engine::default(); + let module = Module::new(&engine, &wasm_bytes)?; + + // Get exported functions from module + let exports: Vec = module + .exports() + .map(|export| export.name().to_string()) + .collect(); + + let has_start = exports.contains(&"_start".to_string()) || + exports.contains(&"start".to_string()) || + exports.contains(&"main".to_string()) || + exports.contains(&"run".to_string()); + + Ok(PluginInfo { + path: plugin_path.to_string(), + size: file_size, + exports, + has_start, + }) +} + +#[derive(Debug)] +pub struct PluginInfo { + pub path: String, + pub size: u64, + pub exports: Vec, + pub has_start: bool, } diff --git a/sandcrate-plugin/src/lib.rs b/sandcrate-plugin/src/lib.rs index f8cd774..b09ae31 100644 --- a/sandcrate-plugin/src/lib.rs +++ b/sandcrate-plugin/src/lib.rs @@ -1,4 +1,52 @@ +use std::ffi::CStr; +use std::os::raw::c_char; + #[no_mangle] pub extern "C" fn main() { println!("Hello from WASM plugin!"); + println!("Plugin execution started"); + + // Try to get parameters if available + if let Some(params) = get_parameters() { + println!("Received parameters: {}", params); + } else { + println!("No parameters provided"); + } + + // Simulate some work + for i in 1..=3 { + println!("Processing step {}", i); + } + + println!("Plugin execution completed successfully!"); +} + +#[no_mangle] +pub extern "C" fn run() { + println!("Alternative run function called!"); + println!("This demonstrates multiple entry points"); +} + +fn get_parameters() -> Option { + // This would be called by the host environment + // For now, we'll return None to indicate no parameters + None +} + +// Export a function that can be called from the host +#[no_mangle] +pub extern "C" fn process_data(input: *const c_char) -> *const c_char { + if input.is_null() { + return std::ptr::null(); + } + + let input_str = unsafe { + CStr::from_ptr(input).to_string_lossy().into_owned() + }; + + let result = format!("Processed: {}", input_str); + + // In a real implementation, you'd need to manage memory properly + // For this demo, we'll just return a static string + "Data processed successfully\0".as_ptr() as *const c_char }