Skip to main content

tuitbot_server/
state.rs

1//! Shared application state for the tuitbot server.
2
3use std::collections::HashMap;
4use std::net::IpAddr;
5use std::path::PathBuf;
6use std::sync::Arc;
7use std::time::{Instant, SystemTime};
8
9use tokio::sync::{broadcast, Mutex, RwLock};
10use tokio_util::sync::CancellationToken;
11use tuitbot_core::automation::circuit_breaker::CircuitBreaker;
12use tuitbot_core::automation::Runtime;
13use tuitbot_core::config::{ConnectorConfig, ContentSourcesConfig, DeploymentMode};
14use tuitbot_core::content::ContentGenerator;
15use tuitbot_core::storage::DbPool;
16use tuitbot_core::x_api::auth::TokenManager;
17
18use tuitbot_core::error::XApiError;
19use tuitbot_core::x_api::auth;
20
21use crate::ws::WsEvent;
22
23/// Pending OAuth PKCE state for connector link flows.
24pub struct PendingOAuth {
25    /// The PKCE code verifier needed to complete the token exchange.
26    pub code_verifier: String,
27    /// When this entry was created (for 10-minute expiry).
28    pub created_at: Instant,
29}
30
31/// Shared application state accessible by all route handlers.
32pub struct AppState {
33    /// SQLite connection pool.
34    pub db: DbPool,
35    /// Path to the configuration file.
36    pub config_path: PathBuf,
37    /// Data directory for media storage (parent of config file).
38    pub data_dir: PathBuf,
39    /// Broadcast channel sender for real-time WebSocket events.
40    pub event_tx: broadcast::Sender<WsEvent>,
41    /// Local bearer token for API authentication.
42    pub api_token: String,
43    /// Bcrypt hash of the web login passphrase (None if not configured).
44    pub passphrase_hash: RwLock<Option<String>>,
45    /// Last-observed mtime of the `passphrase_hash` file (for detecting out-of-band resets).
46    pub passphrase_hash_mtime: RwLock<Option<SystemTime>>,
47    /// Host address the server is bound to.
48    pub bind_host: String,
49    /// Port the server is listening on.
50    pub bind_port: u16,
51    /// Per-IP login attempt tracking for rate limiting: (count, window_start).
52    pub login_attempts: Mutex<HashMap<IpAddr, (u32, Instant)>>,
53    /// Per-account automation runtimes (keyed by account_id).
54    pub runtimes: Mutex<HashMap<String, Runtime>>,
55    /// Per-account content generators for AI assist endpoints.
56    pub content_generators: Mutex<HashMap<String, Arc<ContentGenerator>>>,
57    /// Optional circuit breaker for X API rate-limit protection.
58    pub circuit_breaker: Option<Arc<CircuitBreaker>>,
59    /// Cancellation token for the Watchtower filesystem watcher (None if not running).
60    pub watchtower_cancel: Option<CancellationToken>,
61    /// Content sources configuration for the Watchtower.
62    pub content_sources: ContentSourcesConfig,
63    /// Connector configuration for remote source OAuth flows.
64    pub connector_config: ConnectorConfig,
65    /// Deployment mode (desktop, self_host, or cloud).
66    pub deployment_mode: DeploymentMode,
67    /// Provider backend ("", "x_api", or "scraper").
68    pub provider_backend: String,
69    /// Pending OAuth PKCE challenges keyed by state parameter.
70    pub pending_oauth: Mutex<HashMap<String, PendingOAuth>>,
71    /// Per-account X API token managers for automatic token refresh.
72    pub token_managers: Mutex<HashMap<String, Arc<TokenManager>>>,
73    /// X API client ID from config (needed to create token managers).
74    pub x_client_id: String,
75}
76
77impl AppState {
78    /// Get a fresh X API access token for the given account.
79    ///
80    /// Lazily creates a `TokenManager` on first use (loading tokens from disk),
81    /// then returns a token that is automatically refreshed before expiry.
82    pub async fn get_x_access_token(
83        &self,
84        token_path: &std::path::Path,
85        account_id: &str,
86    ) -> Result<String, XApiError> {
87        // Fast path: token manager already exists.
88        {
89            let managers = self.token_managers.lock().await;
90            if let Some(tm) = managers.get(account_id) {
91                return tm.get_access_token().await;
92            }
93        }
94
95        // Load tokens from disk and create a new manager.
96        let tokens = auth::load_tokens(token_path)?.ok_or(XApiError::AuthExpired)?;
97
98        let tm = Arc::new(TokenManager::new(
99            tokens,
100            self.x_client_id.clone(),
101            token_path.to_path_buf(),
102        ));
103
104        let access_token = tm.get_access_token().await?;
105
106        self.token_managers
107            .lock()
108            .await
109            .insert(account_id.to_string(), tm);
110
111        Ok(access_token)
112    }
113}