From 571ec42e92608d3808ab6f132d997cf0a351eee6 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sun, 23 Aug 2026 20:27:34 +0100 Subject: [PATCH] feat(api): expose seasons.vanished (#141) Season objects from the series endpoints now carry the conflict flag #137 writes, the way episodes already do. --- ...8a4b3a42000c96343ff9a5490bb518700f81.json} | 10 +++- crates/arr-api/src/series.rs | 51 ++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) rename .sqlx/{query-ecbf65c10aa20bc04c841981e59919485518877b49e9702fc92512f39cc2a72e.json => query-583b48cc8a0c618294361f606a9c8a4b3a42000c96343ff9a5490bb518700f81.json} (72%) diff --git a/.sqlx/query-ecbf65c10aa20bc04c841981e59919485518877b49e9702fc92512f39cc2a72e.json b/.sqlx/query-583b48cc8a0c618294361f606a9c8a4b3a42000c96343ff9a5490bb518700f81.json similarity index 72% rename from .sqlx/query-ecbf65c10aa20bc04c841981e59919485518877b49e9702fc92512f39cc2a72e.json rename to .sqlx/query-583b48cc8a0c618294361f606a9c8a4b3a42000c96343ff9a5490bb518700f81.json index f7e58b3..52f33f0 100644 --- a/.sqlx/query-ecbf65c10aa20bc04c841981e59919485518877b49e9702fc92512f39cc2a72e.json +++ b/.sqlx/query-583b48cc8a0c618294361f606a9c8a4b3a42000c96343ff9a5490bb518700f81.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id AS \"id!: i64\", series_id AS \"series_id!: i64\", number AS \"number!: i64\", tracked AS \"tracked!: bool\" FROM seasons WHERE series_id = ? ORDER BY number", + "query": "SELECT id AS \"id!: i64\", series_id AS \"series_id!: i64\", number AS \"number!: i64\", tracked AS \"tracked!: bool\", vanished AS \"vanished!: bool\" FROM seasons WHERE series_id = ? ORDER BY number", "describe": { "columns": [ { @@ -22,6 +22,11 @@ "name": "tracked!: bool", "ordinal": 3, "type_info": "Integer" + }, + { + "name": "vanished!: bool", + "ordinal": 4, + "type_info": "Integer" } ], "parameters": { @@ -31,8 +36,9 @@ true, false, false, + false, false ] }, - "hash": "ecbf65c10aa20bc04c841981e59919485518877b49e9702fc92512f39cc2a72e" + "hash": "583b48cc8a0c618294361f606a9c8a4b3a42000c96343ff9a5490bb518700f81" } diff --git a/crates/arr-api/src/series.rs b/crates/arr-api/src/series.rs index 7c231d8..38bf1a1 100644 --- a/crates/arr-api/src/series.rs +++ b/crates/arr-api/src/series.rs @@ -97,6 +97,10 @@ pub struct Season { pub number: i64, /// ยง4.1. Whether new episodes of this season arrive wanted. pub tracked: bool, + /// #141. The season vanished upstream while files remained under it, + /// which is why the row still exists. A conflict for the operator to + /// resolve; nothing was deleted from disk. + pub vanished: bool, pub episodes: Vec, } @@ -629,7 +633,7 @@ async fn remove_library_files(state: &AppState, id: i64) -> Result<(), ApiError> async fn load_seasons(state: &AppState, series_id: i64) -> Result, ApiError> { let seasons = sqlx::query!( - r#"SELECT id AS "id!: i64", series_id AS "series_id!: i64", number AS "number!: i64", tracked AS "tracked!: bool" FROM seasons WHERE series_id = ? ORDER BY number"#, + r#"SELECT id AS "id!: i64", series_id AS "series_id!: i64", number AS "number!: i64", tracked AS "tracked!: bool", vanished AS "vanished!: bool" FROM seasons WHERE series_id = ? ORDER BY number"#, series_id ) .fetch_all(pool(state)?) @@ -650,6 +654,7 @@ async fn load_seasons(state: &AppState, series_id: i64) -> Result, A series_id: season.series_id, number: season.number, tracked: season.tracked, + vanished: season.vanished, episodes: episodes .iter() .filter(|episode| episode.season_id == season.id) @@ -1390,6 +1395,50 @@ mod tests { assert_eq!(season["number"], 1); } + #[tokio::test] + async fn a_vanished_season_reaches_the_seasons_endpoint() { + let (_dir, state, base) = application().await; + let root_id = tv_root(&state, "main").await; + let series = add_series(&base, root_id, false).await; + let series_id = series["id"].as_i64().expect("id"); + let clean = add_season( + &base, + series_id, + 1, + serde_json::json!([{"number": 1, "title": "One"}]), + ) + .await; + assert_eq!(clean["vanished"], false); + add_season( + &base, + series_id, + 2, + serde_json::json!([{"number": 1, "title": "One"}]), + ) + .await; + + sqlx::query("UPDATE seasons SET vanished = 1 WHERE series_id = ? AND number = 2") + .bind(series_id) + .execute(state.database().expect("database").pool()) + .await + .expect("flag season vanished"); + + let seasons: serde_json::Value = reqwest::Client::new() + .get(format!("{base}/api/series/{series_id}/seasons")) + .send() + .await + .expect("list seasons") + .json() + .await + .expect("seasons json"); + let seasons = seasons.as_array().expect("seasons array"); + assert_eq!(seasons[0]["vanished"], false); + assert_eq!( + seasons[1]["vanished"], true, + "#141: the conflict flag is not stopped at the database" + ); + } + #[tokio::test] async fn intent_is_set_on_seasons_and_on_single_episodes() { let (_dir, state, base) = application().await;