mod api_error; pub mod database; pub mod error; pub mod events; pub mod health; pub mod imaging; pub mod metrics; pub mod middleware; pub mod notify; pub mod oauth_client; pub mod pagination; pub mod push; pub mod redis; pub mod rest; pub mod serde_datetime; pub mod serde_patch; pub mod storage; pub mod telemetry; pub mod urls; pub mod users; use std::sync::Arc; pub use api_error::is_unique_violation; use config::AppConfig; pub use database::{AppDatabase, DatabaseRole}; pub use error::ExportError; pub use redis::{AppRedis, RedisConnection}; pub use storage::AppStorage; pub use urls::{resolve_space_image_url, resolve_user_avatar_url}; #[derive(Clone)] pub struct AppState { pub database: Arc, pub redis: AppRedis, pub storage: Arc, pub config: AppConfig, pub kenauth: Option>, pub email_rpc: Option>, } impl AppState { pub async fn load() -> Result { let config = config::AppConfig::load().map_err(|e| ExportError::InvalidConfig(e.to_string()))?; Self::with_config(config).await } pub async fn with_config(config: AppConfig) -> Result { let database = Arc::new(AppDatabase::connect(config.database.clone()).await?); let mut redis = AppRedis::connect(config.redis.clone()).await?; // 入队点遍布各业务 crate,但只有这里有配置:把开关写进句柄,让 // `export::push::enqueue` 能自己判断该不该产生任务。 redis.set_push_enabled(config.push.enabled); let storage = Arc::new(AppStorage::connect(config.s3.clone())?); let kenauth = if config.oauth.enabled { let client = Arc::new( kenauth::KenAuthClient::connect(&config.oauth) .map_err(|err| ExportError::InvalidConfig(err.to_string()))?, ); oauth_client::install(client.clone()); Some(client) } else { None }; let email_rpc = if config.email.rpc.enabled() { Some(Arc::new( kenauth::KenAuthClient::connect_rpc( &config.email.rpc.endpoint, &config.email.rpc.rpc_key, config.email.rpc.timeout_ms(), ) .map_err(|err| ExportError::InvalidConfig(err.to_string()))?, )) } else { kenauth.clone() }; Ok(Self { database, redis, storage, config, kenauth, email_rpc, }) } }