- Disable Axum's 2 MB default body limit on the upload route so large
photos/videos are accepted without HTTP 400
- Serialize UserRole as lowercase in JWT so the frontend role checks
('guest'/'host'/'admin') match correctly
- Drain multipart body before returning early upload errors (rate-limit,
ban, event-lock) to keep the HTTP keep-alive connection clean and
prevent cascading Netzwerkfehler / empty-500 responses
- Add TraceLayer for request logging and Vite dev proxy config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.1 KiB
Rust
104 lines
4.1 KiB
Rust
use anyhow::Result;
|
|
use axum::extract::DefaultBodyLimit;
|
|
use axum::routing::{delete, get, patch, post};
|
|
use axum::Router;
|
|
use tower_http::services::ServeDir;
|
|
use tower_http::trace::TraceLayer;
|
|
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 — body limit disabled; size validation is done inside the handler
|
|
.route("/api/v1/upload", post(handlers::upload::upload)
|
|
.route_layer(DefaultBodyLimit::disable()))
|
|
.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))
|
|
// Export (all authenticated users)
|
|
.route("/api/v1/export/status", get(handlers::admin::export_status))
|
|
.route("/api/v1/export/zip", get(handlers::admin::download_zip))
|
|
.route("/api/v1/export/html", get(handlers::admin::download_html))
|
|
// Admin Dashboard
|
|
.route("/api/v1/admin/stats", get(handlers::admin::get_stats))
|
|
.route(
|
|
"/api/v1/admin/config",
|
|
get(handlers::admin::get_config).patch(handlers::admin::patch_config),
|
|
)
|
|
.route("/api/v1/admin/export/jobs", get(handlers::admin::get_export_jobs));
|
|
|
|
// 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)
|
|
.layer(TraceLayer::new_for_http())
|
|
.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(())
|
|
}
|