use axum::{ routing::{get, post}, Router, Json, extract::State, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::RwLock; use tracing::{info, error}; mod analytics; mod reports; mod ipc; use analytics::AnalyticsEngine; use reports::ReportGenerator; #[derive(Clone)] struct AppState { analytics: Arc>, reports: Arc>, } #[derive(Serialize)] struct HealthResponse { status: String, service: String, version: String, } #[derive(Deserialize)] struct ReportRequest { tenant_id: String, report_type: String, parameters: serde_json::Value, } #[derive(Serialize)] struct ReportResponse { report_id: String, status: String, data: Option, } #[derive(Deserialize)] struct AnalyticsRequest { tenant_id: String, metric: String, period: String, } #[derive(Serialize)] struct AnalyticsResponse { metric: String, value: f64, trend: f64, breakdown: Vec, } #[derive(Serialize)] struct BreakdownItem { label: String, value: f64, } #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter("boc_rust_service=info") .init(); info!("BOC Rust Service starting..."); let state = AppState { analytics: Arc::new(RwLock::new(AnalyticsEngine::new())), reports: Arc::new(RwLock::new(ReportGenerator::new())), }; let app = Router::new() .route("/health", get(health_handler)) .route("/api/v1/reports/generate", post(generate_report)) .route("/api/v1/analytics/query", post(query_analytics)) .route("/api/v1/analytics/batch", post(batch_analytics)) .with_state(state); let listener = tokio::net::TcpListener::bind("0.0.0.0:9093") .await .expect("Failed to bind port 9093"); info!("BOC Rust Service listening on 0.0.0.0:9093"); axum::serve(listener, app) .await .expect("Server failed"); } async fn health_handler() -> Json { Json(HealthResponse { status: "ok".to_string(), service: "boc-rust-service".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), }) } async fn generate_report( State(state): State, Json(req): Json, ) -> Json { info!("Generating report: {} for tenant: {}", req.report_type, req.tenant_id); let reports = state.reports.read().await; match reports.generate(&req.tenant_id, &req.report_type, &req.parameters).await { Ok(data) => Json(ReportResponse { report_id: uuid::Uuid::new_v4().to_string(), status: "completed".to_string(), data: Some(data), }), Err(e) => { error!("Report generation failed: {}", e); Json(ReportResponse { report_id: uuid::Uuid::new_v4().to_string(), status: "failed".to_string(), data: None, }) } } } async fn query_analytics( State(state): State, Json(req): Json, ) -> Json { let analytics = state.analytics.read().await; let result = analytics.query(&req.tenant_id, &req.metric, &req.period).await; Json(AnalyticsResponse { metric: req.metric, value: result.value, trend: result.trend, breakdown: result.breakdown.into_iter() .map(|(label, value)| BreakdownItem { label, value }) .collect(), }) } async fn batch_analytics( State(state): State, Json(reqs): Json>, ) -> Json> { let analytics = state.analytics.read().await; let mut responses = Vec::with_capacity(reqs.len()); for req in reqs { let result = analytics.query(&req.tenant_id, &req.metric, &req.period).await; responses.push(AnalyticsResponse { metric: req.metric.clone(), value: result.value, trend: result.trend, breakdown: result.breakdown.into_iter() .map(|(label, value)| BreakdownItem { label, value }) .collect(), }); } Json(responses) }