mod auth; mod config; mod db; mod errors; mod models; mod routes; use axum::routing::{get, post, put}; use axum::Router; use tower_http::cors::{Any, CorsLayer}; use tower_http::services::{ServeDir, ServeFile}; #[tokio::main] async fn main() { // Load .env file (silently ignore if missing). let _ = dotenvy::dotenv(); let database_url = config::database_url(); let pool = db::init_pool(&database_url).await; db::run_migrations(&pool).await; let state = config::AppState::from_env(pool); let port = config::port(); let cors = CorsLayer::new() .allow_origin(Any) .allow_methods([ axum::http::Method::GET, axum::http::Method::POST, axum::http::Method::PUT, axum::http::Method::DELETE, ]) .allow_headers([ axum::http::header::AUTHORIZATION, axum::http::header::CONTENT_TYPE, ]); let mut app = Router::new() .route("/api/register", post(routes::auth::register)) .route("/api/login", post(routes::auth::login)) .route("/api/me", get(routes::auth::me)) .route( "/api/progress", get(routes::progress::get_progress).put(routes::progress::update_progress), ) .route( "/api/lesson-state", put(routes::lesson_state::save_lesson_state), ) .route( "/api/lesson-state/{topic_id}/{lesson_id}", get(routes::lesson_state::get_lesson_state) .delete(routes::lesson_state::delete_lesson_state), ) .route("/api/tts", get(routes::tts::synthesize)) .layer(cors) .with_state(state); // In production, serve the frontend build as static files with SPA fallback if let Some(static_dir) = config::static_dir() { let index = format!("{}/index.html", static_dir); app = app.fallback_service(ServeDir::new(&static_dir).fallback(ServeFile::new(index))); println!("Serving static files from {static_dir}"); } let addr = format!("0.0.0.0:{port}"); let listener = tokio::net::TcpListener::bind(&addr) .await .expect("Failed to bind"); println!("Ayos API listening on http://{addr}"); axum::serve(listener, app).await.expect("Server error"); }