mod config; mod db; mod models; mod auth; mod handlers; mod middleware; use axum::{ routing::{get, post, put, delete}, Router, middleware as axum_middleware, }; use tower::ServiceBuilder; use tower_http::{ cors::CorsLayer, trace::TraceLayer, }; use config::Config; #[tokio::main] async fn main() -> anyhow::Result<()> { eprintln!("NORMOGEN BACKEND STARTING..."); eprintln!("Loading environment variables..."); match dotenv::dotenv() { Ok(path) => eprintln!("Loaded .env from: {:?}", path), Err(e) => eprintln!("No .env file found (this is OK in Docker): {}", e), } eprintln!("Initializing logging..."); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "normogen_backend=debug,tower_http=debug,axum=debug".into()) ) .init(); eprintln!("Loading configuration..."); let config = match Config::from_env() { Ok(cfg) => { tracing::info!("Configuration loaded successfully"); eprintln!("Config loaded: DB={}, Port={}", cfg.database.database, cfg.server.port); cfg } Err(e) => { eprintln!("FATAL: Failed to load configuration: {}", e); return Err(e); } }; tracing::info!("Connecting to MongoDB at {}", config.database.uri); eprintln!("Connecting to MongoDB..."); let db = match db::MongoDb::new(&config.database.uri, &config.database.database).await { Ok(db) => { tracing::info!("Connected to MongoDB database: {}", config.database.database); eprintln!("MongoDB connection successful"); db } Err(e) => { eprintln!("FATAL: Failed to connect to MongoDB: {}", e); return Err(e); } }; tracing::info!("MongoDB health check: {}", db.health_check().await?); let jwt_service = auth::JwtService::new(config.jwt.clone()); let app_state = config::AppState { db, jwt_service, config: config.clone(), }; eprintln!("Building router..."); let public_routes = Router::new() .route("/health", get(handlers::health_check)) .route("/ready", get(handlers::ready_check)) .route("/api/auth/register", post(handlers::register)) .route("/api/auth/login", post(handlers::login)) .route("/api/auth/recover-password", post(handlers::recover_password)) .layer( ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(CorsLayer::new()) ); let protected_routes = Router::new() // Profile management .route("/api/users/me", get(handlers::get_profile)) .route("/api/users/me", put(handlers::update_profile)) .route("/api/users/me", delete(handlers::delete_account)) // Account settings .route("/api/users/me/settings", get(handlers::get_settings)) .route("/api/users/me/settings", put(handlers::update_settings)) .route("/api/users/me/change-password", post(handlers::change_password)) // Share management (Phase 2.5) .route("/api/shares", post(handlers::create_share)) .route("/api/shares", get(handlers::list_shares)) .route("/api/shares/:id", get(handlers::get_share)) .route("/api/shares/:id", put(handlers::update_share)) .route("/api/shares/:id", delete(handlers::delete_share)) // Permissions (Phase 2.5) .route("/api/permissions/check", post(handlers::check_permission)) .layer( ServiceBuilder::new() .layer(TraceLayer::new_for_http()) .layer(CorsLayer::new()) ) .route_layer(axum_middleware::from_fn_with_state( app_state.clone(), crate::middleware::auth::jwt_auth_middleware )); let app = public_routes.merge(protected_routes).with_state(app_state); eprintln!("Binding to {}:{}...", config.server.host, config.server.port); let listener = tokio::net::TcpListener::bind(&format!("{}:{}", config.server.host, config.server.port)) .await?; tracing::info!("Server listening on {}:{}", config.server.host, config.server.port); eprintln!("Server is running on http://{}:{}", config.server.host, config.server.port); axum::serve(listener, app).await?; Ok(()) }