mod api; mod auth; mod config; mod error; mod services; mod state; mod worker; use crate::config::Config; use crate::error::{AppError, ErrorCode}; use crate::services::mail::Mailer; use crate::state::AppState; use sqlx::postgres::PgPoolOptions; #[tokio::main] async fn main() -> Result<(), AppError> { dotenvy::dotenv().ok(); init_tracing(); let config = Config::from_env()?; let mailer = Mailer::new(&config)?; let db = PgPoolOptions::new() .max_connections(config.database_max_connections) .connect(&config.database_url) .await .map_err(|err| AppError::new(ErrorCode::Internal, "数据库连接失败").with_source(err))?; let redis = redis::Client::open(config.redis_url.clone()) .map_err(|err| AppError::new(ErrorCode::Internal, "Redis 配置错误").with_source(err))? .get_connection_manager() .await .map_err(|err| AppError::new(ErrorCode::Internal, "Redis 连接失败").with_source(err))?; let image_processing_semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new( config.image_processing_concurrency as usize, )); let state = AppState { config, db, redis, mailer: std::sync::Arc::new(mailer), image_processing_semaphore, }; match state.config.role.as_str() { "api" => api::run(state).await, "worker" => worker::run(state).await, other => Err(AppError::new( ErrorCode::InvalidRequest, format!("未知 IMAGEFORGE_ROLE: {other}(仅支持 api/worker)"), )), } } fn init_tracing() { let env_filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { tracing_subscriber::EnvFilter::new("info,tower_http=info,imageforge=info") }); tracing_subscriber::fmt().with_env_filter(env_filter).init(); }