//! AI Weekly Synth — Rust/Axum backend entry point. //! //! Loads configuration, connects to Postgres, runs migrations, //! and starts the HTTP server. Also supports the `create-admin` CLI subcommand. mod cli; use anyhow::Context; use clap::Parser; use sqlx::postgres::PgPoolOptions; use tracing_subscriber::{fmt, EnvFilter}; use ai_synth_backend::app_state; use ai_synth_backend::config::AppConfig; use ai_synth_backend::db; use ai_synth_backend::models::user::UserRole; use ai_synth_backend::router; use crate::cli::{Cli, Commands}; #[tokio::main] async fn main() -> anyhow::Result<()> { // Load .env file if present (not an error if missing) dotenvy::dotenv().ok(); // Initialize tracing let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("info,ai_synth_backend=debug")); fmt().with_env_filter(filter).init(); let cli = Cli::parse(); // Load and validate configuration let config = AppConfig::from_env().map_err(|e| anyhow::anyhow!(e))?; config.validate().map_err(|e| anyhow::anyhow!(e))?; tracing::info!("Configuration loaded successfully"); // Create database connection pool let pool = PgPoolOptions::new() .max_connections(10) .connect(&config.database_url) .await .context("Failed to connect to Postgres")?; tracing::info!("Connected to Postgres"); // Run migrations run_migrations(&pool).await?; match cli.command.unwrap_or(Commands::Serve) { Commands::Serve => { let http_client = reqwest::Client::new(); let state = app_state::AppState::new(config.clone(), pool, http_client); // Load provider rate limits from DB into in-memory limiter if let Err(e) = state.provider_rate_limiter.reload_from_db(&state.pool).await { tracing::warn!("Failed to load provider rate limits from DB: {:?}. Using defaults.", e); } let app = router::build_router(state, &config); let addr = format!("0.0.0.0:{}", config.port); tracing::info!("Starting server on {}", addr); let listener = tokio::net::TcpListener::bind(&addr) .await .context("Failed to bind to address")?; axum::serve(listener, app) .await .context("Server error")?; } Commands::CreateAdmin { email } => { tracing::info!("Creating admin user: {}", email); let existing = db::users::find_by_email(&pool, &email).await?; match existing { Some(user) => { db::users::update_role(&pool, user.id, UserRole::Admin).await?; tracing::info!("User {} promoted to admin.", email); println!("User {} promoted to admin.", email); } None => { db::users::create(&pool, &email, None, UserRole::Admin).await?; tracing::info!("Admin user {} created.", email); println!("Admin user {} created.", email); } } } } Ok(()) } /// Run database migrations at startup. async fn run_migrations(pool: &sqlx::PgPool) -> anyhow::Result<()> { tracing::info!("Running database migrations..."); sqlx::migrate!("./migrations") .run(pool) .await .context("Failed to run database migrations")?; tracing::info!("Migrations complete."); Ok(()) }