diff --git a/Cargo.toml b/Cargo.toml index 676d7e9..b3e14a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,49 +1,7 @@ [workspace] members = [ - ".", + "core", "client", "server", ] resolver = "2" - -[package] -name = "osiris" -version = "0.1.0" -edition = "2021" - -[lib] -name = "osiris" -path = "src/lib.rs" - -[[bin]] -name = "runner" -path = "src/bin/runner.rs" - -[[example]] -name = "engine" -path = "examples/engine/main.rs" - -[[example]] -name = "freezone" -path = "examples/freezone/main.rs" - -[dependencies] -anyhow = "1.0" -redis = { version = "0.24", features = ["aio", "tokio-comp"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -time = { version = "0.3", features = ["serde", "formatting", "parsing", "macros"] } -tokio = { version = "1.23", features = ["full"] } -clap = { version = "4.5", features = ["derive"] } -toml = "0.8" -uuid = { version = "1.6", features = ["v4", "serde"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -osiris_derive = { path = "osiris_derive" } -lettre = "0.11" -rhai = { version = "1.21.0", features = ["std", "sync", "serde"] } -env_logger = "0.10" -reqwest = { version = "0.11", features = ["json"] } - -[dev-dependencies] -tempfile = "3.8" diff --git a/client/Cargo.toml b/client/Cargo.toml index e537207..8ce09a4 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -6,9 +6,18 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -reqwest = { version = "0.11", features = ["json"] } +reqwest = { version = "0.12", default-features = false, features = ["json"] } anyhow = "1.0" thiserror = "1.0" +chrono = "0.4" +hero-supervisor-openrpc-client = { path = "../../supervisor/clients/openrpc" } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +uuid = { version = "1.0", features = ["v4"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +uuid = { version = "1.0", features = ["v4", "js"] } +getrandom = { version = "0.2", features = ["js"] } [dev-dependencies] tokio = { version = "1.23", features = ["full", "macros"] } diff --git a/client/src/communication.rs b/client/src/communication.rs index 36beb8a..697ca65 100644 --- a/client/src/communication.rs +++ b/client/src/communication.rs @@ -1,4 +1,4 @@ -//! Communication query methods (email verification, etc.) +//! Communication methods (queries and commands) use serde::{Deserialize, Serialize}; use crate::{OsirisClient, OsirisClientError}; @@ -24,14 +24,77 @@ pub enum VerificationStatus { Failed, } +// ========== Request/Response Models ========== + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendVerificationRequest { + pub email: String, + pub verification_url: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SendVerificationResponse { + pub verification_id: String, + pub email: String, + pub expires_at: i64, +} + +// ========== Client Methods ========== + impl OsirisClient { - /// Get verification by ID + // ========== Query Methods ========== + + /// Get verification by ID (query) pub async fn get_verification(&self, verification_id: &str) -> Result { self.get("verification", verification_id).await } - /// Get verification by email + /// Get verification by email (query) pub async fn get_verification_by_email(&self, email: &str) -> Result, OsirisClientError> { self.query("verification", &format!("email={}", email)).await } + + /// Get verification status - alias for get_verification (query) + pub async fn get_verification_status(&self, verification_id: &str) -> Result { + self.get_verification(verification_id).await + } + + // ========== Command Methods ========== + + /// Send verification email (command) + pub async fn send_verification_email( + &self, + request: SendVerificationRequest, + ) -> Result { + let email = &request.email; + let verification_url = request.verification_url.as_deref().unwrap_or(""); + + // Generate verification code + let verification_id = format!("ver_{}", uuid::Uuid::new_v4()); + let code = format!("{:06}", (uuid::Uuid::new_v4().as_u128() % 1_000_000)); + + let script = format!(r#" +// Send email verification +let email = "{}"; +let code = "{}"; +let verification_url = "{}"; +let verification_id = "{}"; + +// TODO: Implement actual email sending logic +print("Sending verification email to: " + email); +print("Verification code: " + code); +print("Verification URL: " + verification_url); + +// Return verification details +verification_id +"#, email, code, verification_url, verification_id); + + let _response = self.execute_script(&script).await?; + + Ok(SendVerificationResponse { + verification_id, + email: request.email, + expires_at: chrono::Utc::now().timestamp() + 3600, // 1 hour + }) + } } diff --git a/client/src/kyc.rs b/client/src/kyc.rs index 92c876f..05555d0 100644 --- a/client/src/kyc.rs +++ b/client/src/kyc.rs @@ -1,4 +1,4 @@ -//! KYC query methods +//! KYC methods (queries and commands) use serde::{Deserialize, Serialize}; use crate::{OsirisClient, OsirisClientError}; @@ -25,7 +25,26 @@ pub enum KycSessionStatus { Expired, } +// ========== Request/Response Models ========== + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KycVerificationRequest { + pub resident_id: String, + pub callback_url: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KycVerificationResponse { + pub session_id: String, + pub kyc_url: String, + pub expires_at: i64, +} + +// ========== Client Methods ========== + impl OsirisClient { + // ========== Query Methods ========== + /// Get KYC session by ID pub async fn get_kyc_session(&self, session_id: &str) -> Result { self.get("kyc_session", session_id).await @@ -35,4 +54,49 @@ impl OsirisClient { pub async fn list_kyc_sessions_by_resident(&self, resident_id: &str) -> Result, OsirisClientError> { self.query("kyc_session", &format!("resident_id={}", resident_id)).await } + + // ========== Command Methods ========== + + /// Start KYC verification (command) + pub async fn start_kyc_verification( + &self, + request: KycVerificationRequest, + ) -> Result { + let resident_id = &request.resident_id; + let callback_url = request.callback_url.as_deref().unwrap_or(""); + + // Generate session ID + let session_id = format!("kyc_{}", uuid::Uuid::new_v4()); + + let script = format!(r#" +// Start KYC verification +let resident_id = "{}"; +let callback_url = "{}"; +let session_id = "{}"; + +// TODO: Implement actual KYC provider integration +print("Starting KYC verification for resident: " + resident_id); +print("Session ID: " + session_id); +print("Callback URL: " + callback_url); + +// Return session details +session_id +"#, resident_id, callback_url, session_id); + + let _response = self.execute_script(&script).await?; + + Ok(KycVerificationResponse { + session_id, + kyc_url: "https://kyc.example.com/verify".to_string(), + expires_at: chrono::Utc::now().timestamp() + 86400, + }) + } + + /// Check KYC status (query) + pub async fn check_kyc_status( + &self, + session_id: String, + ) -> Result { + self.get_kyc_session(&session_id).await + } } diff --git a/client/src/lib.rs b/client/src/lib.rs index c0a1c71..a8209a5 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,9 +1,13 @@ -//! Osiris Client - Query API for Osiris data structures +//! Osiris Client - Unified CQRS Client //! -//! This client provides read-only access to Osiris data via REST API. -//! Follows CQRS pattern: queries go through this client, commands go through Rhai scripts. +//! This client provides both: +//! - Commands (writes) via Rhai scripts to Hero Supervisor +//! - Queries (reads) via REST API to Osiris server +//! +//! Follows CQRS pattern with a single unified interface. use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use thiserror::Error; pub mod kyc; @@ -24,30 +28,122 @@ pub enum OsirisClientError { #[error("Deserialization failed: {0}")] DeserializationFailed(String), + + #[error("Configuration error: {0}")] + ConfigError(String), + + #[error("Command execution failed: {0}")] + CommandFailed(String), } -/// Osiris client for querying data +/// Osiris client with CQRS support #[derive(Clone, Debug)] pub struct OsirisClient { - base_url: String, + // Query side (Osiris REST API) + osiris_url: String, + + // Command side (Supervisor + Rhai) + supervisor_url: Option, + runner_name: String, + supervisor_secret: Option, + timeout: u64, + + // HTTP client client: reqwest::Client, } -impl OsirisClient { - /// Create a new Osiris client - pub fn new(base_url: impl Into) -> Self { +/// Builder for OsirisClient +#[derive(Clone, Debug, Default)] +pub struct OsirisClientBuilder { + osiris_url: Option, + supervisor_url: Option, + runner_name: Option, + supervisor_secret: Option, + timeout: u64, +} + +impl OsirisClientBuilder { + /// Create a new builder + pub fn new() -> Self { Self { - base_url: base_url.into(), + osiris_url: None, + supervisor_url: None, + runner_name: None, + supervisor_secret: None, + timeout: 30, + } + } + + /// Set the Osiris server URL (for queries) + pub fn osiris_url(mut self, url: impl Into) -> Self { + self.osiris_url = Some(url.into()); + self + } + + /// Set the Supervisor URL (for commands) + pub fn supervisor_url(mut self, url: impl Into) -> Self { + self.supervisor_url = Some(url.into()); + self + } + + /// Set the runner name (default: "osiris") + pub fn runner_name(mut self, name: impl Into) -> Self { + self.runner_name = Some(name.into()); + self + } + + /// Set the supervisor authentication secret + pub fn supervisor_secret(mut self, secret: impl Into) -> Self { + self.supervisor_secret = Some(secret.into()); + self + } + + /// Set the timeout in seconds (default: 30) + pub fn timeout(mut self, timeout: u64) -> Self { + self.timeout = timeout; + self + } + + /// Build the OsirisClient + pub fn build(self) -> Result { + let osiris_url = self.osiris_url + .ok_or_else(|| OsirisClientError::ConfigError("osiris_url is required".to_string()))?; + + Ok(OsirisClient { + osiris_url, + supervisor_url: self.supervisor_url, + runner_name: self.runner_name.unwrap_or_else(|| "osiris".to_string()), + supervisor_secret: self.supervisor_secret, + timeout: self.timeout, + client: reqwest::Client::new(), + }) + } +} + +impl OsirisClient { + /// Create a new Osiris client (query-only) + pub fn new(osiris_url: impl Into) -> Self { + Self { + osiris_url: osiris_url.into(), + supervisor_url: None, + runner_name: "osiris".to_string(), + supervisor_secret: None, + timeout: 30, client: reqwest::Client::new(), } } + /// Create a builder for full CQRS configuration + pub fn builder() -> OsirisClientBuilder { + OsirisClientBuilder::new() + } + /// Generic GET request for any struct by ID pub async fn get(&self, struct_name: &str, id: &str) -> Result where T: for<'de> Deserialize<'de>, { - let url = format!("{}/api/{}/{}", self.base_url, struct_name, id); + let url = format!("{}/api/{}/{}", self.osiris_url, struct_name, id); let response = self.client .get(&url) @@ -71,7 +167,7 @@ impl OsirisClient { where T: for<'de> Deserialize<'de>, { - let url = format!("{}/api/{}", self.base_url, struct_name); + let url = format!("{}/api/{}", self.osiris_url, struct_name); let response = self.client .get(&url) @@ -91,7 +187,7 @@ impl OsirisClient { where T: for<'de> Deserialize<'de>, { - let url = format!("{}/api/{}?{}", self.base_url, struct_name, query); + let url = format!("{}/api/{}?{}", self.osiris_url, struct_name, query); let response = self.client .get(&url) @@ -105,6 +201,114 @@ impl OsirisClient { Ok(data) } + + // ========== Command Methods (Supervisor + Rhai) ========== + + /// Execute a Rhai script via the Supervisor + pub async fn execute_script(&self, script: &str) -> Result { + let supervisor_url = self.supervisor_url.as_ref() + .ok_or_else(|| OsirisClientError::ConfigError("supervisor_url not configured for commands".to_string()))?; + let secret = self.supervisor_secret.as_ref() + .ok_or_else(|| OsirisClientError::ConfigError("supervisor_secret not configured for commands".to_string()))?; + + // Use supervisor client for native builds + #[cfg(not(target_arch = "wasm32"))] + { + use hero_supervisor_openrpc_client::{SupervisorClient, JobBuilder}; + + let supervisor_client = SupervisorClient::new(supervisor_url) + .map_err(|e| OsirisClientError::CommandFailed(e.to_string()))?; + + let job = JobBuilder::new() + .runner_name(&self.runner_name) + .script(script) + .timeout(self.timeout) + .build(); + + let result = supervisor_client.run_job(secret, job) + .await + .map_err(|e| OsirisClientError::CommandFailed(e.to_string()))?; + + // Convert JobResult to RunJobResponse + match result { + hero_supervisor_openrpc_client::JobResult::Success { success } => { + Ok(RunJobResponse { + job_id: success.clone(), + status: "completed".to_string(), + }) + } + hero_supervisor_openrpc_client::JobResult::Error { error } => { + Err(OsirisClientError::CommandFailed(error)) + } + } + } + + // Use WASM client for browser builds + #[cfg(target_arch = "wasm32")] + { + use hero_supervisor_openrpc_client::wasm::{WasmSupervisorClient, WasmJobBuilder}; + + let supervisor_client = WasmSupervisorClient::new(supervisor_url.clone()); + + let job = WasmJobBuilder::new() + .runner_name(&self.runner_name) + .script(script) + .timeout(self.timeout as i32) + .build(); + + let result_str = supervisor_client.run_job(secret.clone(), job) + .await + .map_err(|e| OsirisClientError::CommandFailed(format!("{:?}", e)))?; + + // Parse the result + let result: serde_json::Value = serde_json::from_str(&result_str) + .map_err(|e| OsirisClientError::DeserializationFailed(e.to_string()))?; + + if let Some(success) = result.get("success") { + Ok(RunJobResponse { + job_id: success.as_str().unwrap_or("unknown").to_string(), + status: "completed".to_string(), + }) + } else if let Some(error) = result.get("error") { + Err(OsirisClientError::CommandFailed(error.as_str().unwrap_or("unknown error").to_string())) + } else { + Err(OsirisClientError::DeserializationFailed("Invalid response format".to_string())) + } + } + } + + /// Execute a Rhai script template with variable substitution + pub async fn execute_template(&self, template: &str, variables: &HashMap) -> Result { + let script = substitute_variables(template, variables); + self.execute_script(&script).await + } +} + +// ========== Helper Structures ========== + +#[derive(Serialize)] +struct RunJobRequest { + runner_name: String, + script: String, + timeout: Option, + #[serde(skip_serializing_if = "Option::is_none")] + env: Option>, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct RunJobResponse { + pub job_id: String, + pub status: String, +} + +/// Helper function to substitute variables in a Rhai script template +pub fn substitute_variables(template: &str, variables: &HashMap) -> String { + let mut result = template.to_string(); + for (key, value) in variables { + let placeholder = format!("{{{{ {} }}}}", key); + result = result.replace(&placeholder, value); + } + result } #[cfg(test)] @@ -114,6 +318,32 @@ mod tests { #[test] fn test_client_creation() { let client = OsirisClient::new("http://localhost:8080"); - assert_eq!(client.base_url, "http://localhost:8080"); + assert_eq!(client.osiris_url, "http://localhost:8080"); + } + + #[test] + fn test_builder() { + let client = OsirisClient::builder() + .osiris_url("http://localhost:8081") + .supervisor_url("http://localhost:3030") + .supervisor_secret("test_secret") + .runner_name("osiris") + .build() + .unwrap(); + + assert_eq!(client.osiris_url, "http://localhost:8081"); + assert_eq!(client.supervisor_url, Some("http://localhost:3030".to_string())); + assert_eq!(client.runner_name, "osiris"); + } + + #[test] + fn test_substitute_variables() { + let template = "let x = {{ value }}; let y = {{ name }};"; + let mut vars = HashMap::new(); + vars.insert("value".to_string(), "42".to_string()); + vars.insert("name".to_string(), "\"test\"".to_string()); + + let result = substitute_variables(template, &vars); + assert_eq!(result, "let x = 42; let y = \"test\";"); } } diff --git a/client/src/scripts/kyc_verification.rhai b/client/src/scripts/kyc_verification.rhai new file mode 100644 index 0000000..2099b18 --- /dev/null +++ b/client/src/scripts/kyc_verification.rhai @@ -0,0 +1,37 @@ +// KYC verification script template +// Variables: {{resident_id}}, {{callback_url}} + +print("=== Starting KYC Verification ==="); +print("Resident ID: {{resident_id}}"); + +// Get freezone context +let freezone_pubkey = "04e58314c13ea3f9caed882001a5090797b12563d5f9bbd7f16efe020e060c780b446862311501e2e9653416527d2634ff8a8050ff3a085baccd7ddcb94185ff56"; +let freezone_ctx = get_context([freezone_pubkey]); + +// Get KYC client from context +let kyc_client = freezone_ctx.get("kyc_client"); +if kyc_client == () { + print("ERROR: KYC client not configured"); + return #{ + success: false, + error: "KYC client not configured" + }; +} + +// Create KYC session +let session = kyc_client.create_session( + "{{resident_id}}", + "{{callback_url}}" +); + +print("✓ KYC session created"); +print(" Session ID: " + session.session_id); +print(" KYC URL: " + session.kyc_url); + +// Return response +#{ + success: true, + session_id: session.session_id, + kyc_url: session.kyc_url, + expires_at: session.expires_at +} diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..1838148 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "osiris-core" +version = "0.1.0" +edition = "2021" + +[lib] +name = "osiris" +path = "src/lib.rs" + +[[bin]] +name = "runner" +path = "src/bin/runner.rs" + +[dependencies] +anyhow = "1.0" +redis = { version = "0.24", features = ["aio", "tokio-comp"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +time = { version = "0.3", features = ["serde", "formatting", "parsing", "macros"] } +tokio = { version = "1.23", features = ["full"] } +clap = { version = "4.5", features = ["derive"] } +toml = "0.8" +uuid = { version = "1.6", features = ["v4", "serde"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +osiris_derive = { path = "osiris_derive" } +lettre = "0.11" +rhai = { version = "1.21.0", features = ["std", "sync", "serde"] } +env_logger = "0.10" +reqwest = { version = "0.11", features = ["json"] } + +[dev-dependencies] +tempfile = "3.8" diff --git a/osiris_derive/Cargo.toml b/core/osiris_derive/Cargo.toml similarity index 100% rename from osiris_derive/Cargo.toml rename to core/osiris_derive/Cargo.toml diff --git a/osiris_derive/src/lib.rs b/core/osiris_derive/src/lib.rs similarity index 100% rename from osiris_derive/src/lib.rs rename to core/osiris_derive/src/lib.rs diff --git a/src/bin/runner.rs b/core/src/bin/runner.rs similarity index 100% rename from src/bin/runner.rs rename to core/src/bin/runner.rs diff --git a/src/config/mod.rs b/core/src/config/mod.rs similarity index 100% rename from src/config/mod.rs rename to core/src/config/mod.rs diff --git a/src/config/model.rs b/core/src/config/model.rs similarity index 100% rename from src/config/model.rs rename to core/src/config/model.rs diff --git a/src/context.rs b/core/src/context.rs similarity index 100% rename from src/context.rs rename to core/src/context.rs diff --git a/src/engine.rs b/core/src/engine.rs similarity index 100% rename from src/engine.rs rename to core/src/engine.rs diff --git a/src/error.rs b/core/src/error.rs similarity index 100% rename from src/error.rs rename to core/src/error.rs diff --git a/src/index/field_index.rs b/core/src/index/field_index.rs similarity index 100% rename from src/index/field_index.rs rename to core/src/index/field_index.rs diff --git a/src/index/mod.rs b/core/src/index/mod.rs similarity index 100% rename from src/index/mod.rs rename to core/src/index/mod.rs diff --git a/src/interfaces/cli.rs b/core/src/interfaces/cli.rs similarity index 100% rename from src/interfaces/cli.rs rename to core/src/interfaces/cli.rs diff --git a/src/interfaces/mod.rs b/core/src/interfaces/mod.rs similarity index 100% rename from src/interfaces/mod.rs rename to core/src/interfaces/mod.rs diff --git a/src/lib.rs b/core/src/lib.rs similarity index 100% rename from src/lib.rs rename to core/src/lib.rs diff --git a/src/main.rs b/core/src/main.rs similarity index 100% rename from src/main.rs rename to core/src/main.rs diff --git a/src/objects/accounting/expense.rs b/core/src/objects/accounting/expense.rs similarity index 100% rename from src/objects/accounting/expense.rs rename to core/src/objects/accounting/expense.rs diff --git a/src/objects/accounting/invoice.rs b/core/src/objects/accounting/invoice.rs similarity index 100% rename from src/objects/accounting/invoice.rs rename to core/src/objects/accounting/invoice.rs diff --git a/src/objects/accounting/mod.rs b/core/src/objects/accounting/mod.rs similarity index 100% rename from src/objects/accounting/mod.rs rename to core/src/objects/accounting/mod.rs diff --git a/src/objects/accounting/rhai.rs b/core/src/objects/accounting/rhai.rs similarity index 100% rename from src/objects/accounting/rhai.rs rename to core/src/objects/accounting/rhai.rs diff --git a/src/objects/communication/email.rs b/core/src/objects/communication/email.rs similarity index 100% rename from src/objects/communication/email.rs rename to core/src/objects/communication/email.rs diff --git a/src/objects/communication/mod.rs b/core/src/objects/communication/mod.rs similarity index 100% rename from src/objects/communication/mod.rs rename to core/src/objects/communication/mod.rs diff --git a/src/objects/communication/rhai.rs b/core/src/objects/communication/rhai.rs similarity index 100% rename from src/objects/communication/rhai.rs rename to core/src/objects/communication/rhai.rs diff --git a/src/objects/communication/verification.rs b/core/src/objects/communication/verification.rs similarity index 100% rename from src/objects/communication/verification.rs rename to core/src/objects/communication/verification.rs diff --git a/src/objects/communication/verification_old.rs b/core/src/objects/communication/verification_old.rs similarity index 100% rename from src/objects/communication/verification_old.rs rename to core/src/objects/communication/verification_old.rs diff --git a/src/objects/event/mod.rs b/core/src/objects/event/mod.rs similarity index 100% rename from src/objects/event/mod.rs rename to core/src/objects/event/mod.rs diff --git a/src/objects/event/rhai.rs b/core/src/objects/event/rhai.rs similarity index 100% rename from src/objects/event/rhai.rs rename to core/src/objects/event/rhai.rs diff --git a/src/objects/flow/instance.rs b/core/src/objects/flow/instance.rs similarity index 100% rename from src/objects/flow/instance.rs rename to core/src/objects/flow/instance.rs diff --git a/src/objects/flow/mod.rs b/core/src/objects/flow/mod.rs similarity index 100% rename from src/objects/flow/mod.rs rename to core/src/objects/flow/mod.rs diff --git a/src/objects/flow/rhai.rs b/core/src/objects/flow/rhai.rs similarity index 100% rename from src/objects/flow/rhai.rs rename to core/src/objects/flow/rhai.rs diff --git a/src/objects/flow/template.rs b/core/src/objects/flow/template.rs similarity index 100% rename from src/objects/flow/template.rs rename to core/src/objects/flow/template.rs diff --git a/src/objects/grid4/bid.rs b/core/src/objects/grid4/bid.rs similarity index 100% rename from src/objects/grid4/bid.rs rename to core/src/objects/grid4/bid.rs diff --git a/src/objects/grid4/common.rs b/core/src/objects/grid4/common.rs similarity index 100% rename from src/objects/grid4/common.rs rename to core/src/objects/grid4/common.rs diff --git a/src/objects/grid4/contract.rs b/core/src/objects/grid4/contract.rs similarity index 100% rename from src/objects/grid4/contract.rs rename to core/src/objects/grid4/contract.rs diff --git a/src/objects/grid4/mod.rs b/core/src/objects/grid4/mod.rs similarity index 100% rename from src/objects/grid4/mod.rs rename to core/src/objects/grid4/mod.rs diff --git a/src/objects/grid4/node.rs b/core/src/objects/grid4/node.rs similarity index 100% rename from src/objects/grid4/node.rs rename to core/src/objects/grid4/node.rs diff --git a/src/objects/grid4/nodegroup.rs b/core/src/objects/grid4/nodegroup.rs similarity index 100% rename from src/objects/grid4/nodegroup.rs rename to core/src/objects/grid4/nodegroup.rs diff --git a/src/objects/grid4/reputation.rs b/core/src/objects/grid4/reputation.rs similarity index 100% rename from src/objects/grid4/reputation.rs rename to core/src/objects/grid4/reputation.rs diff --git a/src/objects/grid4/reservation.rs b/core/src/objects/grid4/reservation.rs similarity index 100% rename from src/objects/grid4/reservation.rs rename to core/src/objects/grid4/reservation.rs diff --git a/src/objects/grid4/specs/README.md b/core/src/objects/grid4/specs/README.md similarity index 100% rename from src/objects/grid4/specs/README.md rename to core/src/objects/grid4/specs/README.md diff --git a/src/objects/grid4/specs/model_bid.v b/core/src/objects/grid4/specs/model_bid.v similarity index 100% rename from src/objects/grid4/specs/model_bid.v rename to core/src/objects/grid4/specs/model_bid.v diff --git a/src/objects/grid4/specs/model_contract.v b/core/src/objects/grid4/specs/model_contract.v similarity index 100% rename from src/objects/grid4/specs/model_contract.v rename to core/src/objects/grid4/specs/model_contract.v diff --git a/src/objects/grid4/specs/model_node.v b/core/src/objects/grid4/specs/model_node.v similarity index 100% rename from src/objects/grid4/specs/model_node.v rename to core/src/objects/grid4/specs/model_node.v diff --git a/src/objects/grid4/specs/model_nodegroup.v b/core/src/objects/grid4/specs/model_nodegroup.v similarity index 100% rename from src/objects/grid4/specs/model_nodegroup.v rename to core/src/objects/grid4/specs/model_nodegroup.v diff --git a/src/objects/grid4/specs/model_reputation.v b/core/src/objects/grid4/specs/model_reputation.v similarity index 100% rename from src/objects/grid4/specs/model_reputation.v rename to core/src/objects/grid4/specs/model_reputation.v diff --git a/src/objects/heroledger/dnsrecord.rs b/core/src/objects/heroledger/dnsrecord.rs similarity index 100% rename from src/objects/heroledger/dnsrecord.rs rename to core/src/objects/heroledger/dnsrecord.rs diff --git a/src/objects/heroledger/group.rs b/core/src/objects/heroledger/group.rs similarity index 100% rename from src/objects/heroledger/group.rs rename to core/src/objects/heroledger/group.rs diff --git a/src/objects/heroledger/membership.rs b/core/src/objects/heroledger/membership.rs similarity index 100% rename from src/objects/heroledger/membership.rs rename to core/src/objects/heroledger/membership.rs diff --git a/src/objects/heroledger/mod.rs b/core/src/objects/heroledger/mod.rs similarity index 100% rename from src/objects/heroledger/mod.rs rename to core/src/objects/heroledger/mod.rs diff --git a/src/objects/heroledger/money.rs b/core/src/objects/heroledger/money.rs similarity index 100% rename from src/objects/heroledger/money.rs rename to core/src/objects/heroledger/money.rs diff --git a/src/objects/heroledger/rhai.rs b/core/src/objects/heroledger/rhai.rs similarity index 100% rename from src/objects/heroledger/rhai.rs rename to core/src/objects/heroledger/rhai.rs diff --git a/src/objects/heroledger/secretbox.rs b/core/src/objects/heroledger/secretbox.rs similarity index 100% rename from src/objects/heroledger/secretbox.rs rename to core/src/objects/heroledger/secretbox.rs diff --git a/src/objects/heroledger/signature.rs b/core/src/objects/heroledger/signature.rs similarity index 100% rename from src/objects/heroledger/signature.rs rename to core/src/objects/heroledger/signature.rs diff --git a/src/objects/heroledger/user.rs b/core/src/objects/heroledger/user.rs similarity index 100% rename from src/objects/heroledger/user.rs rename to core/src/objects/heroledger/user.rs diff --git a/src/objects/heroledger/user_kvs.rs b/core/src/objects/heroledger/user_kvs.rs similarity index 100% rename from src/objects/heroledger/user_kvs.rs rename to core/src/objects/heroledger/user_kvs.rs diff --git a/src/objects/kyc/client.rs b/core/src/objects/kyc/client.rs similarity index 100% rename from src/objects/kyc/client.rs rename to core/src/objects/kyc/client.rs diff --git a/src/objects/kyc/info.rs b/core/src/objects/kyc/info.rs similarity index 100% rename from src/objects/kyc/info.rs rename to core/src/objects/kyc/info.rs diff --git a/src/objects/kyc/mod.rs b/core/src/objects/kyc/mod.rs similarity index 100% rename from src/objects/kyc/mod.rs rename to core/src/objects/kyc/mod.rs diff --git a/src/objects/kyc/rhai.rs b/core/src/objects/kyc/rhai.rs similarity index 100% rename from src/objects/kyc/rhai.rs rename to core/src/objects/kyc/rhai.rs diff --git a/src/objects/kyc/session.rs b/core/src/objects/kyc/session.rs similarity index 100% rename from src/objects/kyc/session.rs rename to core/src/objects/kyc/session.rs diff --git a/src/objects/legal/contract.rs b/core/src/objects/legal/contract.rs similarity index 100% rename from src/objects/legal/contract.rs rename to core/src/objects/legal/contract.rs diff --git a/src/objects/legal/mod.rs b/core/src/objects/legal/mod.rs similarity index 100% rename from src/objects/legal/mod.rs rename to core/src/objects/legal/mod.rs diff --git a/src/objects/legal/rhai.rs b/core/src/objects/legal/rhai.rs similarity index 100% rename from src/objects/legal/rhai.rs rename to core/src/objects/legal/rhai.rs diff --git a/src/objects/mod.rs b/core/src/objects/mod.rs similarity index 100% rename from src/objects/mod.rs rename to core/src/objects/mod.rs diff --git a/src/objects/money/mod.rs b/core/src/objects/money/mod.rs similarity index 100% rename from src/objects/money/mod.rs rename to core/src/objects/money/mod.rs diff --git a/src/objects/money/models.rs b/core/src/objects/money/models.rs similarity index 100% rename from src/objects/money/models.rs rename to core/src/objects/money/models.rs diff --git a/src/objects/money/payments.rs b/core/src/objects/money/payments.rs similarity index 100% rename from src/objects/money/payments.rs rename to core/src/objects/money/payments.rs diff --git a/src/objects/money/rhai.rs b/core/src/objects/money/rhai.rs similarity index 100% rename from src/objects/money/rhai.rs rename to core/src/objects/money/rhai.rs diff --git a/src/objects/note/mod.rs b/core/src/objects/note/mod.rs similarity index 100% rename from src/objects/note/mod.rs rename to core/src/objects/note/mod.rs diff --git a/src/objects/note/rhai.rs b/core/src/objects/note/rhai.rs similarity index 100% rename from src/objects/note/rhai.rs rename to core/src/objects/note/rhai.rs diff --git a/src/retrieve/mod.rs b/core/src/retrieve/mod.rs similarity index 100% rename from src/retrieve/mod.rs rename to core/src/retrieve/mod.rs diff --git a/src/retrieve/query.rs b/core/src/retrieve/query.rs similarity index 100% rename from src/retrieve/query.rs rename to core/src/retrieve/query.rs diff --git a/src/retrieve/search.rs b/core/src/retrieve/search.rs similarity index 100% rename from src/retrieve/search.rs rename to core/src/retrieve/search.rs diff --git a/src/store/base_data.rs b/core/src/store/base_data.rs similarity index 100% rename from src/store/base_data.rs rename to core/src/store/base_data.rs diff --git a/src/store/generic_store.rs b/core/src/store/generic_store.rs similarity index 100% rename from src/store/generic_store.rs rename to core/src/store/generic_store.rs diff --git a/src/store/herodb_client.rs b/core/src/store/herodb_client.rs similarity index 100% rename from src/store/herodb_client.rs rename to core/src/store/herodb_client.rs diff --git a/src/store/mod.rs b/core/src/store/mod.rs similarity index 100% rename from src/store/mod.rs rename to core/src/store/mod.rs diff --git a/src/store/object.rs b/core/src/store/object.rs similarity index 100% rename from src/store/object.rs rename to core/src/store/object.rs diff --git a/src/store/object_trait.rs b/core/src/store/object_trait.rs similarity index 100% rename from src/store/object_trait.rs rename to core/src/store/object_trait.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index ce1461e..62e7e98 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -osiris = { path = ".." } +osiris = { package = "osiris-core", path = "../core" } axum = "0.7" tokio = { version = "1.23", features = ["full"] } serde = { version = "1.0", features = ["derive"] } diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..5fa1973 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,145 @@ +//! Osiris Server - Generic OpenAPI REST server for Osiris data structures +//! +//! Provides generic CRUD operations for all Osiris structs via REST API. +//! Routes follow pattern: GET /api/:struct_name/:id + +use axum::{ + extract::{Path, Query, State}, + http::StatusCode, + response::{IntoResponse, Json}, + routing::get, + Router, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::collections::HashMap; +use std::sync::Arc; +use tower_http::cors::{Any, CorsLayer}; +use tracing::{info, warn}; + +#[derive(Clone)] +struct AppState { + // In a real implementation, this would be a Redis connection pool + // For now, we'll use an in-memory store for demonstration + store: Arc>>>, +} + +impl AppState { + fn new() -> Self { + Self { + store: Arc::new(tokio::sync::RwLock::new(HashMap::new())), + } + } +} + +#[tokio::main] +async fn main() { + // Initialize tracing + tracing_subscriber::fmt() + .with_target(false) + .compact() + .init(); + + let state = AppState::new(); + + // Build router + let app = Router::new() + .route("/health", get(health_check)) + .route("/api/:struct_name", get(list_structs)) + .route("/api/:struct_name/:id", get(get_struct)) + .layer( + CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any), + ) + .with_state(state); + + let addr = "0.0.0.0:8081"; + info!("🚀 Osiris Server starting on {}", addr); + info!("📖 API Documentation: http://localhost:8081/health"); + + let listener = tokio::net::TcpListener::bind(addr) + .await + .expect("Failed to bind address"); + + axum::serve(listener, app) + .await + .expect("Server failed"); +} + +/// Health check endpoint +async fn health_check() -> impl IntoResponse { + Json(json!({ + "status": "healthy", + "service": "osiris-server", + "version": "0.1.0" + })) +} + +/// Generic GET endpoint for a single struct by ID +/// GET /api/:struct_name/:id +async fn get_struct( + State(state): State, + Path((struct_name, id)): Path<(String, String)>, +) -> Result, (StatusCode, String)> { + info!("GET /api/{}/{}", struct_name, id); + + let store = state.store.read().await; + + if let Some(struct_store) = store.get(&struct_name) { + if let Some(data) = struct_store.get(&id) { + return Ok(Json(data.clone())); + } + } + + warn!("Not found: {}/{}", struct_name, id); + Err(( + StatusCode::NOT_FOUND, + format!("{}/{} not found", struct_name, id), + )) +} + +/// Generic LIST endpoint for all instances of a struct +/// GET /api/:struct_name?field=value +async fn list_structs( + State(state): State, + Path(struct_name): Path, + Query(params): Query>, +) -> Result>, (StatusCode, String)> { + info!("GET /api/{} with params: {:?}", struct_name, params); + + let store = state.store.read().await; + + if let Some(struct_store) = store.get(&struct_name) { + let mut results: Vec = struct_store.values().cloned().collect(); + + // Apply filters if any + if !params.is_empty() { + results.retain(|item| { + params.iter().all(|(key, value)| { + item.get(key) + .and_then(|v| v.as_str()) + .map(|v| v == value) + .unwrap_or(false) + }) + }); + } + + return Ok(Json(results)); + } + + // Return empty array if struct type doesn't exist yet + Ok(Json(vec![])) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_health_check() { + let response = health_check().await.into_response(); + assert_eq!(response.status(), StatusCode::OK); + } +}