mod api_error; pub mod authz; pub mod database; pub mod error; pub mod health; pub mod imaging; pub mod jwt; pub mod middleware; pub mod notify; pub mod pagination; pub mod push; pub mod redis; pub mod rest; pub mod serde_datetime; pub mod serde_patch; pub mod space_token; pub mod storage; pub mod telemetry; pub mod urls; 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}; pub use email::EmailService; #[derive(Clone)] pub struct AppState { pub database: Arc, pub redis: AppRedis, pub storage: Arc, pub email: EmailService, pub config: AppConfig, } 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 redis_url = redis.config().url().to_string(); let builder = email::EmailServiceBuilder::new(config.email.clone()) .with_queue(redis_url, config.email_queue.clone()) .register_default_templates() .await; let email = builder.build().await?; Ok(Self { database, redis, storage, email, config, }) } }