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