//! Shared application state passed to all handlers via Axum's `State` extractor. use dashmap::DashMap; use sqlx::PgPool; use uuid::Uuid; use crate::config::AppConfig; use crate::services::rate_limiter::{ProviderRateLimiter, RateLimiter}; use crate::services::synthesis::JobStore; /// Application state shared across all request handlers. /// /// Cloned cheaply (all inner types are `Arc`-based or `Clone`-cheap). #[derive(Clone)] pub struct AppState { pub config: AppConfig, pub pool: PgPool, pub http_client: reqwest::Client, /// Rate limiter for auth endpoints (magic link requests). pub auth_rate_limiter: RateLimiter, /// Per-provider rate limiter for LLM API calls. /// Loaded from DB at startup, hot-reloaded when admin updates config. pub provider_rate_limiter: ProviderRateLimiter, /// Per-user rate limiters for generation jobs. /// Keyed by user_id. Created on first generation, replaced if settings change. pub user_rate_limiters: DashMap, /// In-memory store for active generation jobs. /// Maps job_id -> progress watch channel. pub job_store: JobStore, } /// Cached per-user rate limiter with the settings it was created from. #[derive(Clone)] pub struct UserRateLimitEntry { pub max_requests: i32, pub window_seconds: i32, pub limiter: RateLimiter, } impl UserRateLimitEntry { /// Create a new entry, building the underlying rate limiter from the given settings. pub fn new(max_requests: i32, window_seconds: i32) -> Self { Self { max_requests, window_seconds, limiter: RateLimiter::new( max_requests as usize, std::time::Duration::from_secs(window_seconds as u64), ), } } /// Returns `true` if the cached settings differ from the given values. pub fn settings_changed(&self, max_requests: i32, window_seconds: i32) -> bool { self.max_requests != max_requests || self.window_seconds != window_seconds } } impl AppState { /// Create a new `AppState` instance. pub fn new(config: AppConfig, pool: PgPool, http_client: reqwest::Client) -> Self { // Auth rate limiter: 10 requests per 60 seconds per key (email or IP) let auth_rate_limiter = RateLimiter::new(10, std::time::Duration::from_secs(60)); // Provider rate limiter: loaded from DB after creation via `reload_from_db` let provider_rate_limiter = ProviderRateLimiter::new(); // In-memory job store for generation progress tracking let job_store = JobStore::new(); Self { config, pool, http_client, auth_rate_limiter, provider_rate_limiter, user_rate_limiters: DashMap::new(), job_store, } } }