You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

105 lines
3.4 KiB
Rust

//! 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(())
}