feat(web): tell pending seasons from settled empty
This commit is contained in:
@@ -57,6 +57,10 @@ pub struct Series {
|
||||
pub poster_path: Option<String>,
|
||||
/// TMDB's rating, out of 10; `null` when TMDB has no votes for it.
|
||||
pub vote_average: Option<f64>,
|
||||
/// NULL until a metadata refresh has stamped it (#160). Issue #177: this
|
||||
/// is what tells the SPA a series with no seasons yet is still seeding
|
||||
/// its first refresh, rather than a title upstream genuinely lists none.
|
||||
pub metadata_refreshed_at: Option<String>,
|
||||
/// `airing`, `incomplete`, `waiting`, `complete` or `ended` (§4.2).
|
||||
pub status: String,
|
||||
/// Episodes currently marked wanted (§4.1 — the only intent).
|
||||
@@ -327,6 +331,7 @@ fn with_status(row: &SeriesRow, episodes: &[arr_core::Episode], now: SystemTime)
|
||||
blocked: row.blocked,
|
||||
poster_path: row.poster_path.clone(),
|
||||
vote_average: row.vote_average,
|
||||
metadata_refreshed_at: row.metadata_refreshed_at.clone(),
|
||||
status: status_name(derive_series_status(&core_series(row), episodes, now)).to_owned(),
|
||||
wanted_episodes: i64::try_from(wanted.count()).unwrap_or(i64::MAX),
|
||||
available_episodes: i64::try_from(available.count()).unwrap_or(i64::MAX),
|
||||
@@ -509,6 +514,31 @@ pub async fn get(
|
||||
Ok(Json(load_series(&state, id).await?))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/series/{series_id}/refresh-metadata", tag = "series",
|
||||
params(("series_id" = i64, Path, description = "Series row id")),
|
||||
responses(
|
||||
(status = 202, body = Accepted),
|
||||
(status = 404, body = ErrorBody),
|
||||
(status = 500, body = ErrorBody),
|
||||
(status = 503, body = ErrorBody)
|
||||
)
|
||||
)]
|
||||
/// Issue #177: the same on-demand command `create` sends on add, resent by
|
||||
/// hand when a page gave up polling for it. Same terms — asynchronous, best
|
||||
/// effort, and a failure leaves `metadata_refreshed_at` NULL for the daily
|
||||
/// sweep to pick up.
|
||||
pub async fn refresh_metadata(
|
||||
State(state): State<AppState>,
|
||||
Path(series_id): Path<i64>,
|
||||
) -> Result<(StatusCode, Json<Accepted>), ApiError> {
|
||||
load_series_row(&state, series_id).await?;
|
||||
state
|
||||
.send_metadata_command(MetadataCommand::Series { series_id })
|
||||
.map_err(|_| ApiError::Unavailable)?;
|
||||
Ok((StatusCode::ACCEPTED, Json(Accepted { accepted: true })))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
patch, path = "/api/series/{series_id}", tag = "series", request_body = UpdateSeries,
|
||||
params(("series_id" = i64, Path, description = "Series row id")),
|
||||
@@ -1805,6 +1835,62 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Issue #177: the series response carries `metadata_refreshed_at` so
|
||||
/// the SPA can tell a never-refreshed series from a settled empty one.
|
||||
#[tokio::test]
|
||||
async fn series_response_exposes_metadata_refreshed_at() {
|
||||
let (_dir, state, base) = application().await;
|
||||
let root_id = tv_root(&state, "main").await;
|
||||
let series = add_series(&base, root_id, true).await;
|
||||
assert!(series["metadata_refreshed_at"].is_null());
|
||||
}
|
||||
|
||||
/// Issue #177: the retry control a gave-up poll offers resends the same
|
||||
/// on-demand command `create` sends, so a second refresh attempt does
|
||||
/// not need the scheduled sweep to come around.
|
||||
#[tokio::test]
|
||||
async fn refreshing_metadata_by_hand_queues_the_same_command() {
|
||||
let (_dir, state, base) = application().await;
|
||||
let root_id = tv_root(&state, "main").await;
|
||||
let series = add_series(&base, root_id, true).await;
|
||||
let series_id = series["id"].as_i64().expect("series id");
|
||||
assert_eq!(
|
||||
state
|
||||
.next_metadata_command()
|
||||
.await
|
||||
.expect("create's command"),
|
||||
MetadataCommand::Series { series_id }
|
||||
);
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{base}/api/series/{series_id}/refresh-metadata"))
|
||||
.send()
|
||||
.await
|
||||
.expect("refresh metadata");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::ACCEPTED);
|
||||
assert_eq!(
|
||||
state
|
||||
.next_metadata_command()
|
||||
.await
|
||||
.expect("retry's command"),
|
||||
MetadataCommand::Series { series_id }
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refreshing_metadata_for_an_unknown_series_is_a_404() {
|
||||
let (_dir, _state, base) = application().await;
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{base}/api/series/404/refresh-metadata"))
|
||||
.send()
|
||||
.await
|
||||
.expect("refresh metadata");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn series_must_sit_on_a_tv_root() {
|
||||
let (_dir, state, base) = application().await;
|
||||
|
||||
Reference in New Issue
Block a user