use deployer_types::NodeInfo; use std::collections::HashMap; use tokio::sync::{Mutex, RwLock}; use tracing::{info, warn}; use crate::config::Config; use crate::storage::NodeStorage; /// Application state shared across handlers pub struct AppState { /// Server configuration pub config: Config, /// ChainFire-backed storage (when available) pub storage: Option>, /// Fallback in-memory node registry /// Key: node_id, Value: NodeInfo pub nodes: RwLock>, /// Fallback in-memory machine_id → (node_id, NodeConfig) mapping pub machine_configs: RwLock>, } impl AppState { /// Create new application state with default config pub fn new() -> Self { Self::with_config(Config::default()) } /// Create application state with custom config pub fn with_config(config: Config) -> Self { Self { config, storage: None, nodes: RwLock::new(HashMap::new()), machine_configs: RwLock::new(HashMap::new()), } } /// Initialize ChainFire storage connection pub async fn init_storage(&mut self) -> anyhow::Result<()> { if self.config.chainfire.endpoints.is_empty() { warn!("No ChainFire endpoints configured, using in-memory storage"); return Ok(()); } let endpoint = &self.config.chainfire.endpoints[0]; let namespace = &self.config.chainfire.namespace; match NodeStorage::connect(endpoint, namespace).await { Ok(storage) => { info!( endpoint = %endpoint, namespace = %namespace, "Connected to ChainFire storage" ); self.storage = Some(Mutex::new(storage)); Ok(()) } Err(e) => { warn!( error = %e, "Failed to connect to ChainFire, using in-memory storage" ); // Continue with in-memory storage as fallback Ok(()) } } } /// Check if ChainFire storage is available pub fn has_storage(&self) -> bool { self.storage.is_some() } } impl Default for AppState { fn default() -> Self { Self::new() } }