clean(presentation): strip comments, kill dead code, extract constants

- remove all doc/inline comments
- delete unused ApiError variants (AuthRequired, NotImplemented) and helpers (internal, not_implemented)
- prefix lifetime-only AppState fields with _ to suppress dead_code warning
- extract magic numbers: TICK_INTERVAL_SECS, EXPIRY_THRESHOLD_HOURS, POLL_INTERVAL_SECS, EVENT_BUS_CAPACITY, DEFAULT_ACTIVITY_LIMIT, DEFAULT_SEARCH_LIMIT, etc
- replace inline serde_json::json! in sync_status with typed SyncStatusEntry
- fix mid-file import in schedule handler
- fix extractors: strip_prefix instead of magic offset 6
- fix unreachable pattern in wire_repositories with cfg guard
- use eq_ignore_ascii_case for content-type header check
This commit is contained in:
2026-07-12 04:35:54 +02:00
parent 25b33b6a0e
commit ff5f299a84
21 changed files with 91 additions and 296 deletions

View File

@@ -1,5 +1,3 @@
//! Admin handlers.
use axum::Json;
use axum::extract::{Query, State};
use serde::Deserialize;
@@ -12,7 +10,8 @@ use crate::errors::ApiError;
use crate::extractors::AdminUser;
use crate::state::AppState;
/// GET /admin/settings
const DEFAULT_ACTIVITY_LIMIT: u32 = 50;
pub async fn get_settings(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
@@ -23,7 +22,6 @@ pub async fn get_settings(
Ok(Json(SettingsResponse { settings }))
}
/// PUT /admin/settings
pub async fn update_settings(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
@@ -35,7 +33,6 @@ pub async fn update_settings(
};
application::admin::update_settings::execute(&state.admin_deps, cmd).await?;
// Re-read after update
let pairs =
application::admin::get_settings::execute(&state.admin_deps, GetSettingsQuery).await?;
let settings: HashMap<String, String> = pairs.into_iter().collect();
@@ -47,14 +44,13 @@ pub struct ActivityLogParams {
pub limit: Option<u32>,
}
/// GET /admin/activity
pub async fn get_activity_log(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
Query(params): Query<ActivityLogParams>,
) -> Result<Json<Vec<ActivityEventResponse>>, ApiError> {
let query = GetActivityLogQuery {
limit: params.limit.unwrap_or(50),
limit: params.limit.unwrap_or(DEFAULT_ACTIVITY_LIMIT),
};
let events = application::admin::activity_log::execute(&state.admin_deps, query).await?;
Ok(Json(

View File

@@ -1,5 +1,3 @@
//! Authentication handlers.
use axum::Json;
use axum::extract::State;
@@ -10,7 +8,9 @@ use crate::errors::ApiError;
use crate::extractors::CurrentUser;
use crate::state::AppState;
/// POST /auth/register
const TOKEN_TYPE_BEARER: &str = "Bearer";
const SECS_PER_HOUR: u64 = 3600;
pub async fn register(
State(state): State<AppState>,
Json(req): Json<RegisterRequest>,
@@ -23,7 +23,6 @@ pub async fn register(
Ok(Json(UserResponse::from(user)))
}
/// POST /auth/login
pub async fn login(
State(state): State<AppState>,
Json(req): Json<LoginRequest>,
@@ -36,25 +35,22 @@ pub async fn login(
let (access_token, refresh_token) = create_tokens(&user, &state, req.remember_me)?;
Ok(Json(TokenResponse {
access_token,
token_type: "Bearer".to_string(),
expires_in: state.config.jwt_expiry_hours * 3600,
token_type: TOKEN_TYPE_BEARER.to_string(),
expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR,
refresh_token,
}))
}
/// POST /auth/logout — no-op for JWT (stateless)
pub async fn logout() -> Result<Json<serde_json::Value>, ApiError> {
Ok(Json(serde_json::json!({"message": "logged out"})))
}
/// GET /auth/me
pub async fn me(
CurrentUser(user): CurrentUser,
) -> Result<Json<UserResponse>, ApiError> {
Ok(Json(UserResponse::from(user)))
}
/// POST /auth/token — exchange credentials for tokens
#[cfg(feature = "auth-jwt")]
pub async fn get_token(
State(state): State<AppState>,
@@ -68,13 +64,12 @@ pub async fn get_token(
let (access_token, refresh_token) = create_tokens(&user, &state, req.remember_me)?;
Ok(Json(TokenResponse {
access_token,
token_type: "Bearer".to_string(),
expires_in: state.config.jwt_expiry_hours * 3600,
token_type: TOKEN_TYPE_BEARER.to_string(),
expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR,
refresh_token,
}))
}
/// POST /auth/refresh — refresh an access token
#[cfg(feature = "auth-jwt")]
pub async fn refresh_token(
State(state): State<AppState>,
@@ -106,8 +101,8 @@ pub async fn refresh_token(
let (access_token, refresh_token) = create_tokens(&user, &state, true)?;
Ok(Json(TokenResponse {
access_token,
token_type: "Bearer".to_string(),
expires_in: state.config.jwt_expiry_hours * 3600,
token_type: TOKEN_TYPE_BEARER.to_string(),
expires_in: state.config.jwt_expiry_hours * SECS_PER_HOUR,
refresh_token,
}))
}

View File

@@ -1,5 +1,3 @@
//! Channel CRUD handlers.
use axum::Json;
use axum::extract::{Path, State};
@@ -20,7 +18,6 @@ use crate::errors::ApiError;
use crate::extractors::CurrentUser;
use crate::state::AppState;
/// GET /channels
pub async fn list_channels(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -30,7 +27,6 @@ pub async fn list_channels(
Ok(Json(channels.into_iter().map(ChannelResponse::from).collect()))
}
/// GET /channels/mine
pub async fn list_my_channels(
State(state): State<AppState>,
CurrentUser(user): CurrentUser,
@@ -43,7 +39,6 @@ pub async fn list_my_channels(
Ok(Json(channels.into_iter().map(ChannelResponse::from).collect()))
}
/// POST /channels
pub async fn create_channel(
State(state): State<AppState>,
CurrentUser(user): CurrentUser,
@@ -58,7 +53,6 @@ pub async fn create_channel(
Ok(Json(ChannelResponse::from(channel)))
}
/// GET /channels/:id
pub async fn get_channel(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -71,7 +65,6 @@ pub async fn get_channel(
Ok(Json(ChannelResponse::from(channel)))
}
/// PUT /channels/:id
pub async fn update_channel(
State(state): State<AppState>,
CurrentUser(user): CurrentUser,
@@ -108,7 +101,6 @@ pub async fn update_channel(
Ok(Json(ChannelResponse::from(channel)))
}
/// DELETE /channels/:id
pub async fn delete_channel(
State(state): State<AppState>,
CurrentUser(user): CurrentUser,
@@ -122,9 +114,6 @@ pub async fn delete_channel(
Ok(axum::http::StatusCode::NO_CONTENT)
}
// ── Config snapshots ─────────────────────────────────────────────────────
/// POST /channels/:id/snapshots
pub async fn save_snapshot(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -138,7 +127,6 @@ pub async fn save_snapshot(
Ok(Json(ConfigSnapshotResponse::from(snap)))
}
/// GET /channels/:id/snapshots
pub async fn list_snapshots(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -149,7 +137,6 @@ pub async fn list_snapshots(
Ok(Json(snaps.into_iter().map(ConfigSnapshotResponse::from).collect()))
}
/// GET /channels/:id/snapshots/:snapshot_id
pub async fn get_snapshot(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -165,7 +152,6 @@ pub async fn get_snapshot(
Ok(Json(ConfigSnapshotResponse::from(snap)))
}
/// PATCH /channels/:id/snapshots/:snapshot_id
pub async fn patch_snapshot(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -184,7 +170,6 @@ pub async fn patch_snapshot(
Ok(Json(ConfigSnapshotResponse::from(snap)))
}
/// POST /channels/:id/snapshots/:snapshot_id/restore
pub async fn restore_snapshot(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,

View File

@@ -1,5 +1,3 @@
//! System configuration handler.
use axum::Json;
use axum::extract::State;
@@ -8,7 +6,8 @@ use api_types::{ConfigResponse, ProviderCapabilitiesResponse, ProviderInfo};
use crate::errors::ApiError;
use crate::state::AppState;
/// GET /config — public system configuration
const FALLBACK_STREAMING_PROTOCOL: &str = "direct_file";
pub async fn get_config(
State(state): State<AppState>,
) -> Result<Json<ConfigResponse>, ApiError> {
@@ -36,7 +35,7 @@ pub async fn get_config(
tags: false,
decade: false,
search: false,
streaming_protocol: "direct_file".to_string(),
streaming_protocol: FALLBACK_STREAMING_PROTOCOL.to_string(),
rescan: false,
transcode: false,
});

View File

@@ -1,9 +1,3 @@
//! Local file streaming handlers (feature-gated).
//!
//! Placeholder — the actual streaming logic requires the local-files adapter
//! which provides file index and transcoding. This will be fleshed out once
//! the local-files adapter integration is complete.
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
@@ -11,22 +5,19 @@ use axum::response::IntoResponse;
use crate::errors::ApiError;
use crate::state::AppState;
/// GET /files/stream/:id — stream a local file
pub async fn stream_file(
State(_state): State<AppState>,
Path(_id): Path<String>,
) -> Result<impl IntoResponse, ApiError> {
// TODO: integrate with adapter-local-files for actual streaming
Err::<StatusCode, _>(ApiError::not_implemented(
"Local file streaming not yet wired in presentation crate",
Err::<StatusCode, _>(ApiError::NotFound(
"Local file streaming not yet wired in presentation crate".to_string(),
))
}
/// POST /files/rescan — rescan local files
pub async fn rescan(
State(_state): State<AppState>,
) -> Result<impl IntoResponse, ApiError> {
Err::<StatusCode, _>(ApiError::not_implemented(
"Local file rescan not yet wired in presentation crate",
Err::<StatusCode, _>(ApiError::NotFound(
"Local file rescan not yet wired in presentation crate".to_string(),
))
}

View File

@@ -1,5 +1,3 @@
//! IPTV export handlers (M3U, XMLTV).
use axum::extract::{Query, State};
use axum::http::header;
use axum::response::IntoResponse;
@@ -11,12 +9,14 @@ use crate::errors::ApiError;
use crate::extractors::OptionalCurrentUser;
use crate::state::AppState;
const M3U_CONTENT_TYPE: &str = "audio/x-mpegurl; charset=utf-8";
const XML_CONTENT_TYPE: &str = "application/xml; charset=utf-8";
#[derive(Debug, Deserialize)]
pub struct IptvParams {
pub token: Option<String>,
}
/// GET /iptv/playlist.m3u — M3U playlist
pub async fn m3u_playlist(
State(state): State<AppState>,
OptionalCurrentUser(_user): OptionalCurrentUser,
@@ -27,20 +27,13 @@ pub async fn m3u_playlist(
token: params.token,
};
let content = application::iptv::m3u::execute(&state.iptv_deps, query).await?;
Ok((
[(header::CONTENT_TYPE, "audio/x-mpegurl; charset=utf-8")],
content,
))
Ok(([(header::CONTENT_TYPE, M3U_CONTENT_TYPE)], content))
}
/// GET /iptv/epg.xml — XMLTV electronic program guide
pub async fn xmltv_epg(
State(state): State<AppState>,
OptionalCurrentUser(_user): OptionalCurrentUser,
) -> Result<impl IntoResponse, ApiError> {
let content = application::iptv::xmltv::execute(&state.iptv_deps, GetXmltvQuery).await?;
Ok((
[(header::CONTENT_TYPE, "application/xml; charset=utf-8")],
content,
))
Ok(([(header::CONTENT_TYPE, XML_CONTENT_TYPE)], content))
}

View File

@@ -1,8 +1,6 @@
//! Library browsing handlers.
use axum::Json;
use axum::extract::{Path, Query, State};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use api_types::{CollectionResponse, LibraryItemResponse, PaginatedResponse, SeasonResponse, ShowResponse};
use application::library::{
@@ -14,6 +12,8 @@ use crate::errors::ApiError;
use crate::extractors::{AdminUser, CurrentUser};
use crate::state::AppState;
const DEFAULT_SEARCH_LIMIT: u32 = 50;
#[derive(Debug, Deserialize)]
pub struct LibrarySearchParams {
pub provider: Option<String>,
@@ -30,7 +30,6 @@ pub struct LibrarySearchParams {
pub limit: Option<u32>,
}
/// GET /library/items
pub async fn search_items(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -46,7 +45,7 @@ pub async fn search_items(
season_number: params.season_number,
decade: params.decade,
offset: params.offset.unwrap_or(0),
limit: params.limit.unwrap_or(50),
limit: params.limit.unwrap_or(DEFAULT_SEARCH_LIMIT),
};
let (items, total) = application::library::search::execute(&state.library_query_deps, query).await?;
Ok(Json(PaginatedResponse::new(
@@ -55,7 +54,6 @@ pub async fn search_items(
)))
}
/// GET /library/items/:id
pub async fn get_item(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -68,7 +66,6 @@ pub async fn get_item(
Ok(Json(LibraryItemResponse::from(item)))
}
/// GET /library/collections
pub async fn list_collections(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -100,7 +97,6 @@ pub struct ShowsParams {
pub genres: Vec<String>,
}
/// GET /library/shows
pub async fn list_shows(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -121,7 +117,6 @@ pub struct SeasonsParams {
pub provider: Option<String>,
}
/// GET /library/seasons
pub async fn list_seasons(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -144,7 +139,6 @@ pub struct GenresParams {
pub provider: Option<String>,
}
/// GET /library/genres
pub async fn list_genres(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -159,51 +153,52 @@ pub async fn list_genres(
Ok(Json(genres))
}
/// GET /library/sync/status
#[derive(Debug, Serialize)]
pub(crate) struct SyncStatusEntry {
provider_id: String,
started_at: String,
finished_at: String,
items_found: u32,
status: String,
error_msg: String,
}
pub async fn sync_status(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
) -> Result<Json<serde_json::Value>, ApiError> {
) -> Result<Json<Vec<SyncStatusEntry>>, ApiError> {
let entries =
application::library::get_sync_status::execute(&state.library_query_deps, GetSyncStatusQuery)
.await?;
let result: Vec<serde_json::Value> = entries
let result: Vec<SyncStatusEntry> = entries
.into_iter()
.map(|e| {
serde_json::json!({
"provider_id": e.provider_id(),
"started_at": e.started_at(),
"finished_at": e.finished_at().unwrap_or(""),
"items_found": e.items_found(),
"status": e.status(),
"error_msg": e.error_msg().unwrap_or(""),
})
.map(|e| SyncStatusEntry {
provider_id: e.provider_id().to_string(),
started_at: e.started_at().to_string(),
finished_at: e.finished_at().unwrap_or("").to_string(),
items_found: e.items_found(),
status: e.status().to_string(),
error_msg: e.error_msg().unwrap_or("").to_string(),
})
.collect();
Ok(Json(serde_json::Value::Array(result)))
Ok(Json(result))
}
/// POST /library/sync — trigger sync (admin only)
///
/// Validates that no sync is already running, then sends a signal to the
/// background sync task to start a sync cycle immediately.
pub async fn trigger_sync(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
) -> Result<axum::http::StatusCode, ApiError> {
let cmd = TriggerSyncCommand { provider_id: None };
let _provider_ids =
application::library::sync::execute(&state.library_command_deps, cmd)
.await
.map_err(|e| {
if e.to_string().contains("already running") {
ApiError::conflict(e.to_string())
} else {
ApiError::from(e)
}
})?;
application::library::sync::execute(&state.library_command_deps, cmd)
.await
.map_err(|e| {
if e.to_string().contains("already running") {
ApiError::conflict(e.to_string())
} else {
ApiError::from(e)
}
})?;
// Signal the background sync task to run immediately
let _ = state.sync_trigger.send(());
Ok(axum::http::StatusCode::ACCEPTED)

View File

@@ -1,5 +1,3 @@
//! Provider configuration CRUD handlers.
use axum::Json;
use axum::extract::{Path, State};
@@ -12,7 +10,6 @@ use crate::errors::ApiError;
use crate::extractors::AdminUser;
use crate::state::AppState;
/// GET /admin/providers
pub async fn list_providers(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
@@ -27,7 +24,6 @@ pub async fn list_providers(
))
}
/// GET /admin/providers/:id
pub async fn get_provider(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
@@ -40,7 +36,6 @@ pub async fn get_provider(
Ok(Json(ProviderConfigResponse::from(provider)))
}
/// PUT /admin/providers/:id
pub async fn upsert_provider(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
@@ -60,7 +55,6 @@ pub async fn upsert_provider(
Ok(Json(serde_json::json!({"status": "ok"})))
}
/// DELETE /admin/providers/:id
pub async fn delete_provider(
State(state): State<AppState>,
AdminUser(_user): AdminUser,

View File

@@ -1,8 +1,7 @@
//! Schedule, broadcast, and stream handlers.
use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use api_types::{
CurrentBroadcastResponse, ScheduleHistoryEntry, ScheduleResponse, SlotResponse,
@@ -16,7 +15,6 @@ use crate::errors::ApiError;
use crate::extractors::CurrentUser;
use crate::state::AppState;
/// POST /channels/:id/schedule — generate a new schedule
pub async fn generate_schedule(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -27,7 +25,6 @@ pub async fn generate_schedule(
Ok(Json(ScheduleResponse::from(schedule)))
}
/// GET /channels/:id/schedule — get the active schedule
pub async fn get_active_schedule(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,
@@ -40,9 +37,6 @@ pub async fn get_active_schedule(
}
}
use axum::response::IntoResponse;
/// GET /channels/:id/now — what's currently playing
pub async fn get_current_broadcast(
State(state): State<AppState>,
Path(id): Path<uuid::Uuid>,
@@ -51,7 +45,6 @@ pub async fn get_current_broadcast(
match application::schedule::get_current_broadcast::execute(&state.schedule_deps, query).await?
{
Some(broadcast) => {
// Look up the channel to resolve block access mode
let channel_query = application::channels::GetChannelQuery { channel_id: id };
let channel = application::channels::get::execute(&state.channel_query_deps, channel_query).await?;
@@ -73,7 +66,6 @@ pub async fn get_current_broadcast(
}
}
/// GET /channels/:id/epg — electronic program guide
pub async fn get_epg(
State(state): State<AppState>,
Path(id): Path<uuid::Uuid>,
@@ -83,12 +75,10 @@ pub async fn get_epg(
Ok(Json(slots.into_iter().map(SlotResponse::from).collect()))
}
/// GET /channels/:id/stream — redirect to stream URL (307)
pub async fn get_stream(
State(state): State<AppState>,
Path(id): Path<uuid::Uuid>,
) -> Result<axum::response::Response, ApiError> {
// Find the current broadcast first to get the item ID
let broadcast_query = GetCurrentBroadcastQuery { channel_id: id };
let broadcast =
application::schedule::get_current_broadcast::execute(&state.schedule_deps, broadcast_query)
@@ -113,7 +103,6 @@ pub async fn get_stream(
}
}
/// GET /channels/:id/schedule/history — list schedule generations
pub async fn list_schedule_history(
State(state): State<AppState>,
CurrentUser(_user): CurrentUser,