various: simplify code; work on security and performance

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9a5114addcab5fbff430ab2b919b83466a6a6964
This commit is contained in:
raf 2026-02-02 17:32:11 +03:00
commit c4adc4e3e0
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
75 changed files with 12921 additions and 358 deletions

View file

@ -40,6 +40,9 @@ pub fn create_router(state: AppState) -> Router {
config: login_governor,
});
// Public routes (no auth required)
let public_routes = Router::new().route("/s/{token}", get(routes::social::access_shared_media));
// Read-only routes: any authenticated user (Viewer+)
let viewer_routes = Router::new()
.route("/health", get(routes::health::health))
@ -87,7 +90,82 @@ pub fn create_router(state: AppState) -> Router {
.route("/webhooks", get(routes::webhooks::list_webhooks))
// Auth endpoints (self-service) — login handled separately with stricter rate limit
.route("/auth/logout", post(routes::auth::logout))
.route("/auth/me", get(routes::auth::me));
.route("/auth/me", get(routes::auth::me))
// Social: ratings & comments (read)
.route(
"/media/{id}/ratings",
get(routes::social::get_media_ratings),
)
.route(
"/media/{id}/comments",
get(routes::social::get_media_comments),
)
// Favorites (read)
.route("/favorites", get(routes::social::list_favorites))
// Playlists (read)
.route("/playlists", get(routes::playlists::list_playlists))
.route("/playlists/{id}", get(routes::playlists::get_playlist))
.route("/playlists/{id}/items", get(routes::playlists::list_items))
.route(
"/playlists/{id}/shuffle",
post(routes::playlists::shuffle_playlist),
)
// Analytics (read)
.route(
"/analytics/most-viewed",
get(routes::analytics::get_most_viewed),
)
.route(
"/analytics/recently-viewed",
get(routes::analytics::get_recently_viewed),
)
.route("/analytics/events", post(routes::analytics::record_event))
.route(
"/media/{id}/progress",
get(routes::analytics::get_watch_progress),
)
.route(
"/media/{id}/progress",
post(routes::analytics::update_watch_progress),
)
// Subtitles (read)
.route(
"/media/{id}/subtitles",
get(routes::subtitles::list_subtitles),
)
.route(
"/media/{media_id}/subtitles/{subtitle_id}/content",
get(routes::subtitles::get_subtitle_content),
)
// Enrichment (read)
.route(
"/media/{id}/external-metadata",
get(routes::enrichment::get_external_metadata),
)
// Transcode (read)
.route("/transcode/{id}", get(routes::transcode::get_session))
.route("/transcode", get(routes::transcode::list_sessions))
// Streaming
.route(
"/media/{id}/stream/hls/master.m3u8",
get(routes::streaming::hls_master_playlist),
)
.route(
"/media/{id}/stream/hls/{profile}/playlist.m3u8",
get(routes::streaming::hls_variant_playlist),
)
.route(
"/media/{id}/stream/hls/{profile}/{segment}",
get(routes::streaming::hls_segment),
)
.route(
"/media/{id}/stream/dash/manifest.mpd",
get(routes::streaming::dash_manifest),
)
.route(
"/media/{id}/stream/dash/{profile}/{segment}",
get(routes::streaming::dash_segment),
);
// Write routes: Editor+ required
let editor_routes = Router::new()
@ -190,6 +268,58 @@ pub fn create_router(state: AppState) -> Router {
)
// Webhooks
.route("/webhooks/test", post(routes::webhooks::test_webhook))
// Social: ratings & comments (write)
.route("/media/{id}/ratings", post(routes::social::rate_media))
.route("/media/{id}/comments", post(routes::social::add_comment))
// Favorites (write)
.route("/favorites", post(routes::social::add_favorite))
.route(
"/favorites/{media_id}",
delete(routes::social::remove_favorite),
)
// Share links
.route("/share", post(routes::social::create_share_link))
// Playlists (write)
.route("/playlists", post(routes::playlists::create_playlist))
.route("/playlists/{id}", patch(routes::playlists::update_playlist))
.route(
"/playlists/{id}",
delete(routes::playlists::delete_playlist),
)
.route("/playlists/{id}/items", post(routes::playlists::add_item))
.route(
"/playlists/{id}/items/{media_id}",
delete(routes::playlists::remove_item),
)
.route(
"/playlists/{id}/reorder",
post(routes::playlists::reorder_item),
)
// Subtitles (write)
.route(
"/media/{id}/subtitles",
post(routes::subtitles::add_subtitle),
)
.route(
"/subtitles/{id}",
delete(routes::subtitles::delete_subtitle),
)
.route(
"/subtitles/{id}/offset",
patch(routes::subtitles::update_offset),
)
// Enrichment (write)
.route(
"/media/{id}/enrich",
post(routes::enrichment::trigger_enrichment),
)
.route("/jobs/enrich", post(routes::enrichment::batch_enrich))
// Transcode (write)
.route(
"/media/{id}/transcode",
post(routes::transcode::start_transcode),
)
.route("/transcode/{id}", delete(routes::transcode::cancel_session))
.layer(middleware::from_fn(auth::require_editor));
// Admin-only routes: destructive/config operations
@ -203,14 +333,33 @@ pub fn create_router(state: AppState) -> Router {
.route("/config/ui", put(routes::config::update_ui_config))
.route("/database/vacuum", post(routes::database::vacuum_database))
.route("/database/clear", post(routes::database::clear_database))
// Plugin management
.route("/plugins", get(routes::plugins::list_plugins))
.route("/plugins/{id}", get(routes::plugins::get_plugin))
.route("/plugins/install", post(routes::plugins::install_plugin))
.route("/plugins/{id}", delete(routes::plugins::uninstall_plugin))
.route("/plugins/{id}/toggle", post(routes::plugins::toggle_plugin))
.route("/plugins/{id}/reload", post(routes::plugins::reload_plugin))
// User management
.route("/users", get(routes::users::list_users))
.route("/users", post(routes::users::create_user))
.route("/users/{id}", get(routes::users::get_user))
.route("/users/{id}", patch(routes::users::update_user))
.route("/users/{id}", delete(routes::users::delete_user))
.route(
"/users/{id}/libraries",
get(routes::users::get_user_libraries),
)
.route(
"/users/{id}/libraries",
post(routes::users::grant_library_access),
)
.route(
"/users/{id}/libraries",
delete(routes::users::revoke_library_access),
)
.layer(middleware::from_fn(auth::require_admin));
let api = Router::new()
.merge(login_route)
.merge(viewer_routes)
.merge(editor_routes)
.merge(admin_routes);
// CORS: allow same-origin by default, plus the desktop UI origin
let cors = CorsLayer::new()
.allow_origin([
@ -228,13 +377,25 @@ pub fn create_router(state: AppState) -> Router {
.allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION])
.allow_credentials(true);
Router::new()
.nest("/api/v1", api)
.layer(DefaultBodyLimit::max(10 * 1024 * 1024))
// Create protected routes with auth middleware
let protected_api = Router::new()
.merge(viewer_routes)
.merge(editor_routes)
.merge(admin_routes)
.layer(middleware::from_fn_with_state(
state.clone(),
auth::require_auth,
))
));
// Combine protected and public routes
let full_api = Router::new()
.merge(login_route)
.merge(public_routes)
.merge(protected_api);
Router::new()
.nest("/api/v1", full_api)
.layer(DefaultBodyLimit::max(10 * 1024 * 1024))
.layer(GovernorLayer {
config: global_governor,
})