MediaRole auto-detection + manual role API + sync wiring (#7)

role_detector: classify items as Interstitial by collection name/tag patterns.
Wire chapter extraction + role detection into sync adapter (worker + presentation).
SQLite: persist/read role column, migration.
PUT /library/items/{id}/role endpoint for manual override.
This commit is contained in:
2026-07-12 14:00:28 +02:00
parent 607d311375
commit f21d70c559
15 changed files with 304 additions and 21 deletions

View File

@@ -4,9 +4,10 @@ use axum::extract::{Path, Query, State};
use api_types::{
CollectionResponse, GenresParams, LibraryItemResponse, LibrarySearchParams, PaginatedResponse,
ProviderParam, SeasonResponse, SeasonsParams, ShowResponse, ShowsParams, SyncStatusEntry,
UpdateRoleRequest,
};
use application::library::{SearchItemsQuery, TriggerSyncCommand};
use domain::DomainError;
use domain::{DomainError, MediaRole};
use crate::errors::AppError;
use crate::extractors::{AdminUser, CurrentUser};
@@ -131,3 +132,32 @@ pub async fn trigger_sync(
application::library::sync::execute(&state.library_command_deps, cmd).await?;
Ok(axum::http::StatusCode::ACCEPTED)
}
pub async fn update_role(
State(state): State<AppState>,
AdminUser(_user): AdminUser,
Path(id): Path<String>,
Json(body): Json<UpdateRoleRequest>,
) -> Result<Json<LibraryItemResponse>, AppError> {
let role: MediaRole = serde_json::from_value(serde_json::Value::String(body.role.clone()))
.map_err(|_| {
AppError(DomainError::ValidationError(format!(
"Invalid role '{}'. Must be 'program' or 'interstitial'",
body.role
)))
})?;
state
.library_command_deps
.library_command
.update_role(&id, role)
.await?;
let item = state
.library_query
.get_by_id(&id)
.await?
.ok_or_else(|| AppError(DomainError::NotFound(format!("Library item {id} not found"))))?;
Ok(Json(LibraryItemResponse::from(item)))
}