move repos into monorepo
This commit is contained in:
31
lib/clients/osiris/Cargo.toml
Normal file
31
lib/clients/osiris/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "osiris-client"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Osiris client library"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
# Core dependencies
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
thiserror.workspace = true
|
||||
chrono.workspace = true
|
||||
|
||||
# HTTP client
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json"] }
|
||||
|
||||
# Hero dependencies
|
||||
hero-supervisor-openrpc-client = { path = "../supervisor" }
|
||||
hero-job = { path = "../../models/job" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
uuid.workspace = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
uuid = { workspace = true, features = ["js"] }
|
||||
getrandom.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio.workspace = true
|
||||
170
lib/clients/osiris/examples/complete.rs
Normal file
170
lib/clients/osiris/examples/complete.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! Complete Osiris Client Example
|
||||
//!
|
||||
//! This example demonstrates the full CQRS pattern with Osiris:
|
||||
//! - Commands (writes) via Rhai scripts through Supervisor
|
||||
//! - Queries (reads) via REST API from Osiris server
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - Redis running on localhost:6379
|
||||
//! - Supervisor running on localhost:3030
|
||||
//! - Osiris server running on localhost:8080
|
||||
//! - Osiris runner connected to Redis
|
||||
|
||||
use osiris_client::OsirisClient;
|
||||
use hero_supervisor_openrpc_client::SupervisorClient;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🚀 Osiris Client - Complete Example\n");
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
|
||||
// Configuration
|
||||
let admin_secret = "807470fd1e1ccc3fb997a1d4177cceb31a68cb355a4412c8fd6e66e517e902be";
|
||||
let supervisor_url = "http://localhost:3030";
|
||||
let osiris_url = "http://localhost:8080";
|
||||
let runner_name = "osiris-queue";
|
||||
|
||||
// ========== Part 1: Setup Runner ==========
|
||||
println!("📋 Part 1: Runner Setup\n");
|
||||
|
||||
let supervisor = SupervisorClient::builder()
|
||||
.url(supervisor_url)
|
||||
.secret(admin_secret)
|
||||
.build()?;
|
||||
|
||||
// Register the runner
|
||||
println!("1. Registering runner '{}'...", runner_name);
|
||||
match supervisor.register_runner(runner_name).await {
|
||||
Ok(result) => println!(" ✅ Runner registered: {}\n", result),
|
||||
Err(e) => println!(" ⚠️ Registration failed (may already exist): {:?}\n", e),
|
||||
}
|
||||
|
||||
// List all runners
|
||||
println!("2. Listing all runners...");
|
||||
match supervisor.list_runners().await {
|
||||
Ok(runners) => {
|
||||
println!(" ✅ Found {} runner(s):", runners.len());
|
||||
for runner in runners {
|
||||
println!(" - {}", runner);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!(" ❌ Failed: {:?}\n", e),
|
||||
}
|
||||
|
||||
// ========== Part 2: Initialize Osiris Client ==========
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
println!("📋 Part 2: Osiris Client (CQRS Pattern)\n");
|
||||
|
||||
let client = OsirisClient::builder()
|
||||
.osiris_url(osiris_url)
|
||||
.supervisor_url(supervisor_url)
|
||||
.supervisor_secret(admin_secret)
|
||||
.runner_name(runner_name)
|
||||
.build()?;
|
||||
|
||||
println!("✅ Osiris client initialized");
|
||||
println!(" - Osiris URL: {}", osiris_url);
|
||||
println!(" - Supervisor URL: {}", supervisor_url);
|
||||
println!(" - Runner: {}\n", runner_name);
|
||||
|
||||
// ========== Part 3: Execute Simple Script ==========
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
println!("📋 Part 3: Execute Rhai Script\n");
|
||||
|
||||
let script = r#"
|
||||
print("Hello from Osiris!");
|
||||
let result = 40 + 2;
|
||||
print("The answer is: " + result);
|
||||
result
|
||||
"#;
|
||||
|
||||
println!("Executing script...");
|
||||
match client.execute_script(script).await {
|
||||
Ok(response) => {
|
||||
println!(" ✅ Script executed successfully!");
|
||||
println!(" Job ID: {}", response.job_id);
|
||||
println!(" Status: {}\n", response.status);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ❌ Script execution failed: {}", e);
|
||||
println!(" Make sure the runner is connected to Redis!\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Part 4: CQRS Operations (if runner is connected) ==========
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
println!("📋 Part 4: CQRS Operations (Commands + Queries)\n");
|
||||
|
||||
// Create an API Key (Command via Rhai)
|
||||
println!("1. Creating API Key (Command via Rhai)...");
|
||||
let api_key = format!("test-key-{}", chrono::Utc::now().timestamp());
|
||||
match client.create_api_key(
|
||||
api_key.clone(),
|
||||
"Test Key".to_string(),
|
||||
"admin".to_string()
|
||||
).await {
|
||||
Ok(response) => {
|
||||
println!(" ✅ API Key created!");
|
||||
println!(" Job ID: {}", response.job_id);
|
||||
println!(" Status: {}\n", response.status);
|
||||
}
|
||||
Err(e) => println!(" ⚠️ Failed: {}\n", e),
|
||||
}
|
||||
|
||||
// Query the API Key (Query via REST)
|
||||
println!("2. Querying API Key (Query via REST)...");
|
||||
match client.get_api_key(&api_key).await {
|
||||
Ok(key) => {
|
||||
println!(" ✅ API Key retrieved!");
|
||||
println!(" Key: {:?}\n", key);
|
||||
}
|
||||
Err(e) => println!(" ⚠️ Not found yet: {}\n", e),
|
||||
}
|
||||
|
||||
// List all API Keys (Query via REST)
|
||||
println!("3. Listing all API Keys (Query via REST)...");
|
||||
match client.list_api_keys().await {
|
||||
Ok(keys) => {
|
||||
println!(" ✅ Found {} API key(s)", keys.len());
|
||||
for key in keys.iter().take(3) {
|
||||
println!(" - {:?}", key);
|
||||
}
|
||||
if keys.len() > 3 {
|
||||
println!(" ... and {} more", keys.len() - 3);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Err(e) => println!(" ⚠️ Failed: {}\n", e),
|
||||
}
|
||||
|
||||
// ========== Part 5: Cleanup ==========
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
println!("📋 Part 5: Cleanup\n");
|
||||
|
||||
println!("Deleting test API Key (Command via Rhai)...");
|
||||
match client.delete_api_key(api_key.clone()).await {
|
||||
Ok(response) => {
|
||||
println!(" ✅ API Key deleted!");
|
||||
println!(" Job ID: {}", response.job_id);
|
||||
println!(" Status: {}\n", response.status);
|
||||
}
|
||||
Err(e) => println!(" ⚠️ Failed: {}\n", e),
|
||||
}
|
||||
|
||||
// ========== Summary ==========
|
||||
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
println!("✅ Example Complete!\n");
|
||||
println!("Summary:");
|
||||
println!(" ✅ Runner registration working");
|
||||
println!(" ✅ Osiris client initialized");
|
||||
println!(" ✅ CQRS pattern demonstrated");
|
||||
println!(" - Commands via Rhai scripts");
|
||||
println!(" - Queries via REST API");
|
||||
println!("\nNext steps:");
|
||||
println!(" - Explore other CQRS methods (runners, jobs, etc.)");
|
||||
println!(" - Use template-based script generation");
|
||||
println!(" - Build your own Osiris-backed applications!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
100
lib/clients/osiris/src/communication.rs
Normal file
100
lib/clients/osiris/src/communication.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
//! Communication methods (queries and commands)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{OsirisClient, OsirisClientError};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Verification {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub code: String,
|
||||
pub transport: String,
|
||||
pub status: VerificationStatus,
|
||||
pub created_at: i64,
|
||||
pub expires_at: i64,
|
||||
pub verified_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum VerificationStatus {
|
||||
Pending,
|
||||
Verified,
|
||||
Expired,
|
||||
Failed,
|
||||
}
|
||||
|
||||
// ========== Request/Response Models ==========
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SendVerificationRequest {
|
||||
pub email: String,
|
||||
pub verification_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SendVerificationResponse {
|
||||
pub verification_id: String,
|
||||
pub email: String,
|
||||
pub expires_at: i64,
|
||||
}
|
||||
|
||||
// ========== Client Methods ==========
|
||||
|
||||
impl OsirisClient {
|
||||
// ========== Query Methods ==========
|
||||
|
||||
/// Get verification by ID (query)
|
||||
pub async fn get_verification(&self, verification_id: &str) -> Result<Verification, OsirisClientError> {
|
||||
self.get("verification", verification_id).await
|
||||
}
|
||||
|
||||
/// Get verification by email (query)
|
||||
pub async fn get_verification_by_email(&self, email: &str) -> Result<Vec<Verification>, 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<Verification, OsirisClientError> {
|
||||
self.get_verification(verification_id).await
|
||||
}
|
||||
|
||||
// ========== Command Methods ==========
|
||||
|
||||
/// Send verification email (command)
|
||||
pub async fn send_verification_email(
|
||||
&self,
|
||||
request: SendVerificationRequest,
|
||||
) -> Result<SendVerificationResponse, OsirisClientError> {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
102
lib/clients/osiris/src/kyc.rs
Normal file
102
lib/clients/osiris/src/kyc.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! KYC methods (queries and commands)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{OsirisClient, OsirisClientError};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KycSession {
|
||||
pub id: String,
|
||||
pub resident_id: String,
|
||||
pub status: KycSessionStatus,
|
||||
pub kyc_url: Option<String>,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub expires_at: i64,
|
||||
pub verified_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum KycSessionStatus {
|
||||
Pending,
|
||||
InProgress,
|
||||
Completed,
|
||||
Failed,
|
||||
Expired,
|
||||
}
|
||||
|
||||
// ========== Request/Response Models ==========
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KycVerificationRequest {
|
||||
pub resident_id: String,
|
||||
pub callback_url: Option<String>,
|
||||
}
|
||||
|
||||
#[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<KycSession, OsirisClientError> {
|
||||
self.get("kyc_session", session_id).await
|
||||
}
|
||||
|
||||
/// List all KYC sessions for a resident
|
||||
pub async fn list_kyc_sessions_by_resident(&self, resident_id: &str) -> Result<Vec<KycSession>, 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<KycVerificationResponse, OsirisClientError> {
|
||||
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<KycSession, OsirisClientError> {
|
||||
self.get_kyc_session(&session_id).await
|
||||
}
|
||||
}
|
||||
439
lib/clients/osiris/src/lib.rs
Normal file
439
lib/clients/osiris/src/lib.rs
Normal file
@@ -0,0 +1,439 @@
|
||||
//! Osiris Client - Unified CQRS Client
|
||||
//!
|
||||
//! 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;
|
||||
pub mod payment;
|
||||
pub mod communication;
|
||||
|
||||
pub use kyc::*;
|
||||
pub use payment::*;
|
||||
pub use communication::*;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OsirisClientError {
|
||||
#[error("HTTP request failed: {0}")]
|
||||
RequestFailed(#[from] reqwest::Error),
|
||||
|
||||
#[error("Resource not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Deserialization failed: {0}")]
|
||||
DeserializationFailed(String),
|
||||
|
||||
#[error("Configuration error: {0}")]
|
||||
ConfigError(String),
|
||||
|
||||
#[error("Command execution failed: {0}")]
|
||||
CommandFailed(String),
|
||||
}
|
||||
|
||||
/// Osiris client with CQRS support
|
||||
#[derive(Clone)]
|
||||
pub struct OsirisClient {
|
||||
// Query side (Osiris REST API)
|
||||
osiris_url: String,
|
||||
|
||||
// Command side (Supervisor + Rhai)
|
||||
supervisor_client: Option<hero_supervisor_openrpc_client::SupervisorClient>,
|
||||
runner_name: String,
|
||||
timeout: u64,
|
||||
|
||||
// HTTP client
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
/// Builder for OsirisClient
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OsirisClientBuilder {
|
||||
osiris_url: Option<String>,
|
||||
supervisor_url: Option<String>,
|
||||
runner_name: Option<String>,
|
||||
supervisor_secret: Option<String>,
|
||||
timeout: u64,
|
||||
}
|
||||
|
||||
impl OsirisClientBuilder {
|
||||
/// Create a new builder
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
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<String>) -> Self {
|
||||
self.osiris_url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the Supervisor URL (for commands)
|
||||
pub fn supervisor_url(mut self, url: impl Into<String>) -> Self {
|
||||
self.supervisor_url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the runner name (default: "osiris")
|
||||
pub fn runner_name(mut self, name: impl Into<String>) -> Self {
|
||||
self.runner_name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the supervisor authentication secret
|
||||
pub fn supervisor_secret(mut self, secret: impl Into<String>) -> 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<OsirisClient, OsirisClientError> {
|
||||
let osiris_url = self.osiris_url
|
||||
.ok_or_else(|| OsirisClientError::ConfigError("osiris_url is required".to_string()))?;
|
||||
|
||||
// Build supervisor client if URL and secret are provided
|
||||
let supervisor_client = if let (Some(url), Some(secret)) = (self.supervisor_url, self.supervisor_secret) {
|
||||
Some(
|
||||
hero_supervisor_openrpc_client::SupervisorClient::builder()
|
||||
.url(url)
|
||||
.secret(secret)
|
||||
.build()
|
||||
.map_err(|e| OsirisClientError::ConfigError(format!("Failed to create supervisor client: {:?}", e)))?
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(OsirisClient {
|
||||
osiris_url,
|
||||
supervisor_client,
|
||||
runner_name: self.runner_name.unwrap_or_else(|| "osiris".to_string()),
|
||||
timeout: self.timeout,
|
||||
client: reqwest::Client::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OsirisClient {
|
||||
/// Create a new Osiris client (query-only)
|
||||
pub fn new(osiris_url: impl Into<String>) -> Self {
|
||||
Self {
|
||||
osiris_url: osiris_url.into(),
|
||||
supervisor_client: None,
|
||||
runner_name: "osiris".to_string(),
|
||||
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<T>(&self, struct_name: &str, id: &str) -> Result<T, OsirisClientError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let url = format!("{}/api/{}/{}", self.osiris_url, struct_name, id);
|
||||
|
||||
let response = self.client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status() == 404 {
|
||||
return Err(OsirisClientError::NotFound(format!("{}/{}", struct_name, id)));
|
||||
}
|
||||
|
||||
let data = response
|
||||
.json::<T>()
|
||||
.await
|
||||
.map_err(|e| OsirisClientError::DeserializationFailed(e.to_string()))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Generic LIST request for all instances of a struct
|
||||
pub async fn list<T>(&self, struct_name: &str) -> Result<Vec<T>, OsirisClientError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let url = format!("{}/api/{}", self.osiris_url, struct_name);
|
||||
|
||||
let response = self.client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let data = response
|
||||
.json::<Vec<T>>()
|
||||
.await
|
||||
.map_err(|e| OsirisClientError::DeserializationFailed(e.to_string()))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Generic QUERY request with filters
|
||||
pub async fn query<T>(&self, struct_name: &str, query: &str) -> Result<Vec<T>, OsirisClientError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let url = format!("{}/api/{}?{}", self.osiris_url, struct_name, query);
|
||||
|
||||
let response = self.client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let data = response
|
||||
.json::<Vec<T>>()
|
||||
.await
|
||||
.map_err(|e| OsirisClientError::DeserializationFailed(e.to_string()))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
// ========== Command Methods (Supervisor + Rhai) ==========
|
||||
// Commands are write operations that execute Rhai scripts via the supervisor
|
||||
// to modify state in Osiris
|
||||
|
||||
/// Execute a Rhai script via the Supervisor
|
||||
pub async fn execute_script(&self, script: &str) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let supervisor_client = self.supervisor_client.as_ref()
|
||||
.ok_or_else(|| OsirisClientError::ConfigError("supervisor_client not configured for commands".to_string()))?;
|
||||
|
||||
// Use JobBuilder from supervisor client (which re-exports from hero-job)
|
||||
use hero_supervisor_openrpc_client::JobBuilder;
|
||||
|
||||
let job = JobBuilder::new()
|
||||
.caller_id("osiris-client")
|
||||
.context_id("command-execution")
|
||||
.runner(&self.runner_name)
|
||||
.payload(script)
|
||||
.executor("rhai")
|
||||
.timeout(self.timeout)
|
||||
.build()
|
||||
.map_err(|e| OsirisClientError::CommandFailed(format!("Failed to build job: {}", e)))?;
|
||||
|
||||
// Use job_run method which returns JobRunResponse
|
||||
// Secret is sent via Authorization header (configured during client creation)
|
||||
let result = supervisor_client.job_run(job, Some(self.timeout))
|
||||
.await
|
||||
.map_err(|e| OsirisClientError::CommandFailed(format!("{:?}", e)))?;
|
||||
|
||||
// Convert JobRunResponse to our RunJobResponse
|
||||
Ok(RunJobResponse {
|
||||
job_id: result.job_id,
|
||||
status: result.status,
|
||||
})
|
||||
}
|
||||
|
||||
/// Execute a Rhai script template with variable substitution
|
||||
pub async fn execute_template(&self, template: &str, variables: &HashMap<String, String>) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = substitute_variables(template, variables);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
// ========== Supervisor-specific CQRS Methods ==========
|
||||
|
||||
/// Create an API key (Command - via Rhai)
|
||||
pub async fn create_api_key(&self, key: String, name: String, scope: String) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
let api_key = new_api_key("{}", "{}", "{}", "{}");
|
||||
save_api_key(api_key);
|
||||
"#,
|
||||
self.get_namespace(),
|
||||
key,
|
||||
name,
|
||||
scope
|
||||
);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
/// Get an API key by key value (Query - via REST)
|
||||
pub async fn get_api_key(&self, key: &str) -> Result<Option<serde_json::Value>, OsirisClientError> {
|
||||
// Query by indexed field
|
||||
let results: Vec<serde_json::Value> = self.query("ApiKey", &format!("key={}", key)).await?;
|
||||
Ok(results.into_iter().next())
|
||||
}
|
||||
|
||||
/// List all API keys (Query - via REST)
|
||||
pub async fn list_api_keys(&self) -> Result<Vec<serde_json::Value>, OsirisClientError> {
|
||||
self.list("ApiKey").await
|
||||
}
|
||||
|
||||
/// Delete an API key (Command - via Rhai)
|
||||
pub async fn delete_api_key(&self, key: String) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
delete_api_key("{}");
|
||||
"#,
|
||||
key
|
||||
);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
/// Create a runner (Command - via Rhai)
|
||||
pub async fn create_runner(&self, runner_id: String, name: String, queue: String, registered_by: String) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
let runner = new_runner("{}", "{}", "{}", "{}", "{}");
|
||||
save_runner(runner);
|
||||
"#,
|
||||
self.get_namespace(),
|
||||
runner_id,
|
||||
name,
|
||||
queue,
|
||||
registered_by
|
||||
);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
/// Get a runner by ID (Query - via REST)
|
||||
pub async fn get_runner(&self, runner_id: &str) -> Result<Option<serde_json::Value>, OsirisClientError> {
|
||||
let results: Vec<serde_json::Value> = self.query("Runner", &format!("runner_id={}", runner_id)).await?;
|
||||
Ok(results.into_iter().next())
|
||||
}
|
||||
|
||||
/// List all runners (Query - via REST)
|
||||
pub async fn list_runners(&self) -> Result<Vec<serde_json::Value>, OsirisClientError> {
|
||||
self.list("Runner").await
|
||||
}
|
||||
|
||||
/// Delete a runner (Command - via Rhai)
|
||||
pub async fn delete_runner(&self, runner_id: String) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
delete_runner("{}");
|
||||
"#,
|
||||
runner_id
|
||||
);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
/// Create job metadata (Command - via Rhai)
|
||||
pub async fn create_job_metadata(&self, job_id: String, runner: String, created_by: String, payload: String) -> Result<RunJobResponse, OsirisClientError> {
|
||||
let script = format!(
|
||||
r#"
|
||||
let job = new_job_metadata("{}", "{}", "{}", "{}", "{}");
|
||||
save_job_metadata(job);
|
||||
"#,
|
||||
self.get_namespace(),
|
||||
job_id,
|
||||
runner,
|
||||
created_by,
|
||||
payload
|
||||
);
|
||||
self.execute_script(&script).await
|
||||
}
|
||||
|
||||
/// Get job metadata by ID (Query - via REST)
|
||||
pub async fn get_job_metadata(&self, job_id: &str) -> Result<Option<serde_json::Value>, OsirisClientError> {
|
||||
let results: Vec<serde_json::Value> = self.query("JobMetadata", &format!("job_id={}", job_id)).await?;
|
||||
Ok(results.into_iter().next())
|
||||
}
|
||||
|
||||
/// List all job metadata (Query - via REST)
|
||||
pub async fn list_job_metadata(&self) -> Result<Vec<serde_json::Value>, OsirisClientError> {
|
||||
self.list("JobMetadata").await
|
||||
}
|
||||
|
||||
/// List jobs by runner (Query - via REST)
|
||||
pub async fn list_jobs_by_runner(&self, runner: &str) -> Result<Vec<serde_json::Value>, OsirisClientError> {
|
||||
self.query("JobMetadata", &format!("runner={}", runner)).await
|
||||
}
|
||||
|
||||
/// List jobs by creator (Query - via REST)
|
||||
pub async fn list_jobs_by_creator(&self, creator: &str) -> Result<Vec<serde_json::Value>, OsirisClientError> {
|
||||
self.query("JobMetadata", &format!("created_by={}", creator)).await
|
||||
}
|
||||
|
||||
// Helper method to get namespace
|
||||
fn get_namespace(&self) -> &str {
|
||||
"supervisor"
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helper Structures ==========
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RunJobRequest {
|
||||
runner_name: String,
|
||||
script: String,
|
||||
timeout: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[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, String>) -> String {
|
||||
let mut result = template.to_string();
|
||||
for (key, value) in variables {
|
||||
let placeholder = format!("{{{{ {} }}}}", key);
|
||||
result = result.replace(&placeholder, value);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_client_creation() {
|
||||
let client = OsirisClient::new("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\";");
|
||||
}
|
||||
}
|
||||
39
lib/clients/osiris/src/payment.rs
Normal file
39
lib/clients/osiris/src/payment.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! Payment query methods
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{OsirisClient, OsirisClientError};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Payment {
|
||||
pub id: String,
|
||||
pub amount: f64,
|
||||
pub currency: String,
|
||||
pub status: PaymentStatus,
|
||||
pub description: String,
|
||||
pub payment_url: Option<String>,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub completed_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaymentStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Completed,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl OsirisClient {
|
||||
/// Get payment by ID
|
||||
pub async fn get_payment(&self, payment_id: &str) -> Result<Payment, OsirisClientError> {
|
||||
self.get("payment", payment_id).await
|
||||
}
|
||||
|
||||
/// List all payments
|
||||
pub async fn list_payments(&self) -> Result<Vec<Payment>, OsirisClientError> {
|
||||
self.list("payment").await
|
||||
}
|
||||
}
|
||||
37
lib/clients/osiris/src/scripts/kyc_verification.rhai
Normal file
37
lib/clients/osiris/src/scripts/kyc_verification.rhai
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user