use std::sync::Arc; use anyhow::Result; use poem::middleware::Cors; use poem::{EndpointExt, Route}; use poem_openapi::OpenApiService; use std::net::SocketAddr; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod api; mod auth; mod atproto; mod config; mod cost; mod crypto; mod db; mod lexicon; mod publisher; mod storage; use crate::auth::AuthService; use crate::storage::StorageProvider; #[tokio::main] async fn main() -> Result<()> { let app_config = config::AppConfig::load()?; tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::new( app_config.log_level.clone(), )) .with(tracing_subscriber::fmt::layer()) .init(); // Initialize database let db_pool = db::init_database(std::path::Path::new(&app_config.database.path)).await?; // Initialize storage let storage = StorageProvider::new(&app_config.storage.provider, &app_config.storage).await?; // Initialize auth service wrapped in Arc for sharing across endpoints let auth_service = Arc::new(AuthService::new(app_config.auth.clone(), db_pool.clone())); // Load age master key from environment variable path let master_key = match crypto::load_master_key() { Ok(key) => { tracing::info!("Loaded age master key from SCRATCHBACK_AGE_KEY_PATH"); key } Err(e) => { tracing::warn!("Failed to load age master key: {:?}. Using generated key for development.", e); // Create a generated key for development - in production this should fail let identity = age::x25519::Identity::generate(); crypto::MasterKey::from_identity(identity) } }; // Create API - tuple of endpoints let api = ( api::games::GamesEndpoint::with_auth(db_pool.clone(), auth_service.clone()), api::saves::SavesEndpoint::new(db_pool.clone(), storage, auth_service.clone()), api::AuthEndpoint::new(auth_service.clone(), app_config.auth.jwt_secret.clone()), api::cost::CostEndpoint::new(db_pool.clone()), api::keys::KeyEndpoint::new(db_pool.clone(), master_key, auth_service), api::atproto_api::AtprotoEndpoint::new(&app_config.atproto, db_pool.clone()), ); // Initialize OpenAPI service let api_service = OpenApiService::new(api, "Scratchback API", "1.0") .server(format!("http://{}/api", app_config.server_addr())); let spec_endpoint = api_service.spec_endpoint(); let swagger_ui = api_service.swagger_ui(); let mut api_route = Route::new(); api_route = api_route.nest("/api", api_service); api_route = api_route.at("/openapi.json", spec_endpoint); api_route = api_route.at("/docs", swagger_ui); let app = api_route .with(Cors::new()); let addr: SocketAddr = app_config.server_addr(); tracing::info!("Starting server on {}", addr); poem::Server::new(poem::listener::TcpListener::bind(addr)) .run(app) .await?; Ok(()) }