use anyhow::Result; use axum::routing::{delete, get, patch, post}; use axum::Router; use tower_http::services::ServeDir; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod auth; mod config; mod db; mod error; mod handlers; mod models; mod services; mod state; use config::AppConfig; use state::AppState; #[tokio::main] async fn main() -> Result<()> { dotenvy::dotenv().ok(); tracing_subscriber::registry() .with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { "eventsnap_backend=debug,tower_http=debug".into() })) .with(tracing_subscriber::fmt::layer()) .init(); let config = AppConfig::from_env()?; let pool = db::create_pool(&config.database_url).await?; let state = AppState::new(pool, config.clone()); // Ensure media directories exist tokio::fs::create_dir_all(&config.media_path).await.ok(); let api = Router::new() // Auth .route("/api/v1/join", post(auth::handlers::join)) .route("/api/v1/recover", post(auth::handlers::recover)) .route("/api/v1/admin/login", post(auth::handlers::admin_login)) .route("/api/v1/session", delete(auth::handlers::logout)) // Upload .route("/api/v1/upload", post(handlers::upload::upload)) .route( "/api/v1/upload/{id}", patch(handlers::upload::edit_upload).delete(handlers::upload::delete_upload), ) // Feed .route("/api/v1/feed", get(handlers::feed::feed)) .route("/api/v1/feed/delta", get(handlers::feed::feed_delta)) .route("/api/v1/hashtags", get(handlers::feed::hashtags)) // Social .route("/api/v1/upload/{id}/like", post(handlers::social::toggle_like)) .route( "/api/v1/upload/{id}/comments", get(handlers::social::list_comments).post(handlers::social::add_comment), ) .route("/api/v1/comment/{id}", delete(handlers::social::delete_comment)) // SSE .route("/api/v1/stream", get(handlers::sse::stream)) // Host Dashboard .route("/api/v1/host/event", get(handlers::host::get_event_status)) .route("/api/v1/host/event/close", post(handlers::host::close_event)) .route("/api/v1/host/event/open", post(handlers::host::open_event)) .route("/api/v1/host/gallery/release", post(handlers::host::release_gallery)) .route("/api/v1/host/users", get(handlers::host::list_users)) .route("/api/v1/host/users/{id}/ban", post(handlers::host::ban_user)) .route("/api/v1/host/users/{id}/unban", post(handlers::host::unban_user)) .route("/api/v1/host/users/{id}/role", patch(handlers::host::set_role)) .route("/api/v1/host/upload/{id}", delete(handlers::host::host_delete_upload)) .route("/api/v1/host/comment/{id}", delete(handlers::host::host_delete_comment)); // Serve media files from disk let media_service = ServeDir::new(&config.media_path); let router = Router::new() .route("/health", get(|| async { "ok" })) .merge(api) .nest_service("/media", media_service) .with_state(state); let listener = tokio::net::TcpListener::bind(("0.0.0.0", config.app_port)).await?; tracing::info!("listening on {}", listener.local_addr()?); axum::serve(listener, router).await?; Ok(()) }