//! Application state shared across request handlers use sqlx::PgPool; use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; use crate::auth::jwt::JwtConfig; use attune_common::{config::Config, mq::Publisher}; /// Shared application state #[derive(Clone)] pub struct AppState { /// Database connection pool pub db: PgPool, /// JWT configuration pub jwt_config: Arc, /// CORS allowed origins pub cors_origins: Vec, /// Application configuration pub config: Arc, /// Optional message queue publisher (shared, swappable after reconnection) pub publisher: Arc>>>, /// Broadcast channel for SSE notifications pub broadcast_tx: broadcast::Sender, } impl AppState { /// Create new application state pub fn new(db: PgPool, config: Config) -> Self { let jwt_secret = config.security.jwt_secret.clone().unwrap_or_else(|| { tracing::warn!( "JWT_SECRET not set in config, using default (INSECURE for production!)" ); "insecure_default_secret_change_in_production".to_string() }); let jwt_config = JwtConfig { secret: jwt_secret, access_token_expiration: config.security.jwt_access_expiration as i64, refresh_token_expiration: config.security.jwt_refresh_expiration as i64, }; let cors_origins = config.server.cors_origins.clone(); // Create broadcast channel for SSE notifications (capacity 1000) let (broadcast_tx, _) = broadcast::channel(1000); Self { db, jwt_config: Arc::new(jwt_config), cors_origins, config: Arc::new(config), publisher: Arc::new(RwLock::new(None)), broadcast_tx, } } /// Set the message queue publisher (called once at startup or after reconnection) pub async fn set_publisher(&self, publisher: Arc) { let mut guard = self.publisher.write().await; *guard = Some(publisher); } /// Get a clone of the current publisher, if available pub async fn get_publisher(&self) -> Option> { self.publisher.read().await.clone() } } /// Type alias for Arc-wrapped application state /// Used by Axum handlers pub type SharedState = Arc;