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; use std::fs::OpenOptions; use std::io::Write; // Helper function to log to both file and stdout fn log(msg: &str) { let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"); let full_msg = format!("[{}] {}\n", timestamp, msg); // Try to write to file if let Ok(mut file) = OpenOptions::new() .create(true) .append(true) .open("/tmp/normogen-startup.log") { let _ = file.write_all(full_msg.as_bytes()); let _ = file.flush(); } // Also print to stdout print!("{}", full_msg); let _ = std::io::stdout().flush(); } #[tokio::main] async fn main() -> anyhow::Result<()> { log("=== NORMOGEN STARTING ==="); log("[1/7] Loading environment variables..."); match dotenv::dotenv() { Ok(path) => { log(&format!("[1/7] Loaded .env from: {:?}", path)); } Err(_) => { log("[1/7] No .env file found (OK in Docker)"); } } log("[2/7] 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(); log("[3/7] Loading configuration..."); let config = match Config::from_env() { Ok(cfg) => { log(&format!("[3/7] Config loaded: DB={}, Port={}", cfg.database.database, cfg.server.port)); cfg } Err(e) => { log(&format!("[FATAL] Failed to load configuration: {}", e)); return Err(e); } }; log(&format!("[4/7] Connecting to MongoDB at {}...", config.database.uri)); let db = match db::MongoDb::new(&config.database.uri, &config.database.database).await { Ok(db) => { log("[4/7] MongoDB connection successful!"); db } Err(e) => { log(&format!("[FATAL] Failed to connect to MongoDB: {}", e)); return Err(e); } }; log("[5/7] Performing health check..."); match db.health_check().await { Ok(_) => { log("[5/7] MongoDB health check: OK"); } Err(e) => { log(&format!("[FATAL] MongoDB health check failed: {}", e)); return Err(e); } } log("[6/7] Building application..."); let jwt_service = auth::JwtService::new(config.jwt.clone()); let app_state = config::AppState { db, jwt_service, config: config.clone(), }; 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() .route("/api/users/me", get(handlers::get_profile)) .route("/api/users/me", put(handlers::update_profile)) .route("/api/users/me", delete(handlers::delete_account)) .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)) .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)) .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); log(&format!("[7/7] Starting server on {}:{}...", config.server.host, config.server.port)); let listener = tokio::net::TcpListener::bind(&format!("{}:{}", config.server.host, config.server.port)) .await?; log("=== SERVER READY ==="); log(&format!("Listening on http://{}:{}", config.server.host, config.server.port)); tracing::info!("Server listening on {}:{}", config.server.host, config.server.port); axum::serve(listener, app).await?; Ok(()) }