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?); Self::with_identity(config, database, None).await } pub async fn with_identity( config: AppConfig, database: Arc, identity: Option>, ) -> Result { 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())?); if config.oauth.enabled && identity.is_none() { return Err(ExportError::InvalidConfig( "oauth requires the in-process identity service".to_owned(), )); } let kenauth = identity; if let Some(client) = &kenauth { oauth_client::install(client.clone()); } let email_rpc = kenauth.clone(); Ok(Self { database, redis, storage, config, kenauth, email_rpc, }) } }