api: derive status from episode season numbers

with_status drops its seasons slice for the same reason as
derive_series_status; tv_by_series no longer loads seasons at all.
Offline query data regenerated.
This commit is contained in:
Miguel Palhas
2026-08-23 18:18:26 +01:00
parent 69263da4f3
commit 50b68e24b6
7 changed files with 99 additions and 198 deletions
@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE e.id = ?", "query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", se.number AS \"season_number!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE e.id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -19,43 +19,48 @@
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "number!: i64", "name": "season_number!: i64",
"ordinal": 3, "ordinal": 3,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "title!: String", "name": "number!: i64",
"ordinal": 4, "ordinal": 4,
"type_info": "Text" "type_info": "Integer"
}, },
{ {
"name": "air_date", "name": "title!: String",
"ordinal": 5, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "wanted!: bool", "name": "air_date",
"ordinal": 6, "ordinal": 6,
"type_info": "Text"
},
{
"name": "wanted!: bool",
"ordinal": 7,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "state!: String", "name": "state!: String",
"ordinal": 7, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "vanished!: bool", "name": "vanished!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "search_attempts!: i64",
"ordinal": 9, "ordinal": 9,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "last_searched_at", "name": "search_attempts!: i64",
"ordinal": 10, "ordinal": 10,
"type_info": "Integer"
},
{
"name": "last_searched_at",
"ordinal": 11,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -68,6 +73,7 @@
false, false,
false, false,
false, false,
false,
true, true,
false, false,
false, false,
@@ -76,5 +82,5 @@
true true
] ]
}, },
"hash": "633d5ff860d9c677eae5d06324d3098a0bbad30b46b23287131de72e64751256" "hash": "3c4fdf30427dc972e695940a7a98cbc7c1da617502c818e5dbe0f35c87b175d5"
} }
@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: i64\", series_id AS \"series_id!: i64\", number AS \"number!: i64\" FROM seasons",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "series_id!: i64",
"ordinal": 1,
"type_info": "Integer"
},
{
"name": "number!: i64",
"ordinal": 2,
"type_info": "Integer"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false
]
},
"hash": "3e3a42524f3e481eef0b896c20aac3cf11d578ee864e36412a6f70477fff8325"
}
@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ? ORDER BY se.number, e.number", "query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", se.number AS \"season_number!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -19,43 +19,48 @@
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "number!: i64", "name": "season_number!: i64",
"ordinal": 3, "ordinal": 3,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "title!: String", "name": "number!: i64",
"ordinal": 4, "ordinal": 4,
"type_info": "Text" "type_info": "Integer"
}, },
{ {
"name": "air_date", "name": "title!: String",
"ordinal": 5, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "wanted!: bool", "name": "air_date",
"ordinal": 6, "ordinal": 6,
"type_info": "Text"
},
{
"name": "wanted!: bool",
"ordinal": 7,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "state!: String", "name": "state!: String",
"ordinal": 7, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "vanished!: bool", "name": "vanished!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "search_attempts!: i64",
"ordinal": 9, "ordinal": 9,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "last_searched_at", "name": "search_attempts!: i64",
"ordinal": 10, "ordinal": 10,
"type_info": "Integer"
},
{
"name": "last_searched_at",
"ordinal": 11,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -68,6 +73,7 @@
false, false,
false, false,
false, false,
false,
true, true,
false, false,
false, false,
@@ -76,5 +82,5 @@
true true
] ]
}, },
"hash": "3ab4e736a4fc73625fd66e38dc3f510193b9c97788503f06c5c23f859780349a" "hash": "3ee558a18edee423eab6fe572f2a6f5bad8db69e0827e620264700f4affb3652"
} }
@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id", "query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", se.number AS \"season_number!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -19,43 +19,48 @@
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "number!: i64", "name": "season_number!: i64",
"ordinal": 3, "ordinal": 3,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "title!: String", "name": "number!: i64",
"ordinal": 4, "ordinal": 4,
"type_info": "Text" "type_info": "Integer"
}, },
{ {
"name": "air_date", "name": "title!: String",
"ordinal": 5, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "wanted!: bool", "name": "air_date",
"ordinal": 6, "ordinal": 6,
"type_info": "Text"
},
{
"name": "wanted!: bool",
"ordinal": 7,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "state!: String", "name": "state!: String",
"ordinal": 7, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "vanished!: bool", "name": "vanished!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "search_attempts!: i64",
"ordinal": 9, "ordinal": 9,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "last_searched_at", "name": "search_attempts!: i64",
"ordinal": 10, "ordinal": 10,
"type_info": "Integer"
},
{
"name": "last_searched_at",
"ordinal": 11,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -68,6 +73,7 @@
false, false,
false, false,
false, false,
false,
true, true,
false, false,
false, false,
@@ -76,5 +82,5 @@
true true
] ]
}, },
"hash": "c77c9cac0129fb4da57bef8ef2fb149c57293c76bad04f01603f11635789222d" "hash": "6bda2cef202e94b64f7bbcf88bbed823cca790c1439d94fba1c7dcea024d9467"
} }
@@ -1,32 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: i64\", series_id AS \"series_id!: i64\", number AS \"number!: i64\" FROM seasons WHERE series_id = ?",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "series_id!: i64",
"ordinal": 1,
"type_info": "Integer"
},
{
"name": "number!: i64",
"ordinal": 2,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false
]
},
"hash": "808c20cad25bdb0d81ac502379dcf72c77bb581bc0fdff7b2728cfa13712eb10"
}
@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ?", "query": "SELECT se.series_id AS \"series_id!: i64\", e.id AS \"id!: i64\", e.season_id AS \"season_id!: i64\", se.number AS \"season_number!: i64\", e.number AS \"number!: i64\", e.title AS \"title!: String\", e.air_date, e.wanted AS \"wanted!: bool\", e.state AS \"state!: String\", e.vanished AS \"vanished!: bool\", e.search_attempts AS \"search_attempts!: i64\", e.last_searched_at\n FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ? ORDER BY se.number, e.number",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -19,43 +19,48 @@
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "number!: i64", "name": "season_number!: i64",
"ordinal": 3, "ordinal": 3,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "title!: String", "name": "number!: i64",
"ordinal": 4, "ordinal": 4,
"type_info": "Text" "type_info": "Integer"
}, },
{ {
"name": "air_date", "name": "title!: String",
"ordinal": 5, "ordinal": 5,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "wanted!: bool", "name": "air_date",
"ordinal": 6, "ordinal": 6,
"type_info": "Text"
},
{
"name": "wanted!: bool",
"ordinal": 7,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "state!: String", "name": "state!: String",
"ordinal": 7, "ordinal": 8,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "vanished!: bool", "name": "vanished!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "search_attempts!: i64",
"ordinal": 9, "ordinal": 9,
"type_info": "Integer" "type_info": "Integer"
}, },
{ {
"name": "last_searched_at", "name": "search_attempts!: i64",
"ordinal": 10, "ordinal": 10,
"type_info": "Integer"
},
{
"name": "last_searched_at",
"ordinal": 11,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -68,6 +73,7 @@
false, false,
false, false,
false, false,
false,
true, true,
false, false,
false, false,
@@ -76,5 +82,5 @@
true true
] ]
}, },
"hash": "448bdb6b59938fe6c8221092fd30c0e9b278dd4df85288aa165753c0efb1d2a8" "hash": "e6f5f116e453756eb57d58da32dd32a53984f9884b179346b1faf07032b08187"
} }
+19 -78
View File
@@ -196,6 +196,7 @@ struct EpisodeRow {
series_id: i64, series_id: i64,
id: i64, id: i64,
season_id: i64, season_id: i64,
season_number: i64,
number: i64, number: i64,
title: String, title: String,
air_date: Option<String>, air_date: Option<String>,
@@ -257,6 +258,7 @@ fn core_episode(row: &EpisodeRow) -> arr_core::Episode {
arr_core::Episode { arr_core::Episode {
id: EpisodeId(row.id), id: EpisodeId(row.id),
season_id: SeasonId(row.season_id), season_id: SeasonId(row.season_id),
season_number: u16::try_from(row.season_number).unwrap_or_default(),
number: u16::try_from(row.number).unwrap_or_default(), number: u16::try_from(row.number).unwrap_or_default(),
title: row.title.clone(), title: row.title.clone(),
air_date: air_date(row.air_date.as_deref()), air_date: air_date(row.air_date.as_deref()),
@@ -277,31 +279,14 @@ fn status_name(status: SeriesStatus) -> &'static str {
} }
} }
fn core_season(id: i64, series_id: i64, number: i64) -> arr_core::Season { fn with_status(row: &SeriesRow, episodes: &[arr_core::Episode], now: SystemTime) -> Series {
arr_core::Season {
id: SeasonId(id),
series_id: SeriesId(series_id),
number: u16::try_from(number).unwrap_or_default(),
tracked: false,
}
}
fn with_status(
row: &SeriesRow,
seasons: &[arr_core::Season],
episodes: &[arr_core::Episode],
now: SystemTime,
) -> Series {
// §4.2: season 0 is invisible to status, so it stays out of the counters // §4.2: season 0 is invisible to status, so it stays out of the counters
// too. A series reading `complete` next to `42/52 eps` is the confusion // too. A series reading `complete` next to `42/52 eps` is the confusion
// this avoids. `UNIQUE (series_id, number)` means there is at most one. // this avoids. The season number rides on each episode (#131), so no
let specials = seasons // parallel slice can be forgotten.
.iter()
.find(|season| season.number == 0)
.map(|season| season.id);
let wanted = episodes let wanted = episodes
.iter() .iter()
.filter(|episode| episode.wanted && specials != Some(episode.season_id)); .filter(|episode| episode.wanted && episode.season_number != 0);
let available = wanted let available = wanted
.clone() .clone()
.filter(|episode| episode.state == MediaState::Available); .filter(|episode| episode.state == MediaState::Available);
@@ -317,53 +302,26 @@ fn with_status(
overrides: row.overrides.clone(), overrides: row.overrides.clone(),
upstream_ended: row.upstream_ended, upstream_ended: row.upstream_ended,
blocked: row.blocked, blocked: row.blocked,
status: status_name(derive_series_status( status: status_name(derive_series_status(&core_series(row), episodes, now)).to_owned(),
&core_series(row),
seasons,
episodes,
now,
))
.to_owned(),
wanted_episodes: i64::try_from(wanted.count()).unwrap_or(i64::MAX), wanted_episodes: i64::try_from(wanted.count()).unwrap_or(i64::MAX),
available_episodes: i64::try_from(available.count()).unwrap_or(i64::MAX), available_episodes: i64::try_from(available.count()).unwrap_or(i64::MAX),
} }
} }
/// Every season and episode in the library, keyed by the series they belong to. /// Every episode in the library, keyed by the series it belongs to.
/// ///
/// One query each rather than one per series: the whole table is a few thousand /// One query rather than one per series: the whole table is a few thousand
/// rows for a single household (§10), and the status of every listed series /// rows for a single household (§10), and the status of every listed series
/// needs all of them anyway. /// needs all of them anyway.
async fn tv_by_series( async fn tv_by_series(state: &AppState) -> Result<HashMap<i64, Vec<arr_core::Episode>>, ApiError> {
state: &AppState,
) -> Result<
(
HashMap<i64, Vec<arr_core::Season>>,
HashMap<i64, Vec<arr_core::Episode>>,
),
ApiError,
> {
let season_rows = sqlx::query!(
r#"SELECT id AS "id!: i64", series_id AS "series_id!: i64", number AS "number!: i64" FROM seasons"#
)
.fetch_all(pool(state)?)
.await?;
let rows = sqlx::query_as!( let rows = sqlx::query_as!(
EpisodeRow, EpisodeRow,
r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", se.number AS "season_number!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at
FROM episodes e JOIN seasons se ON se.id = e.season_id"# FROM episodes e JOIN seasons se ON se.id = e.season_id"#
) )
.fetch_all(pool(state)?) .fetch_all(pool(state)?)
.await?; .await?;
let mut seasons: HashMap<i64, Vec<arr_core::Season>> = HashMap::new();
for row in &season_rows {
seasons.entry(row.series_id).or_default().push(core_season(
row.id,
row.series_id,
row.number,
));
}
let mut episodes: HashMap<i64, Vec<arr_core::Episode>> = HashMap::new(); let mut episodes: HashMap<i64, Vec<arr_core::Episode>> = HashMap::new();
for row in &rows { for row in &rows {
episodes episodes
@@ -371,7 +329,7 @@ async fn tv_by_series(
.or_default() .or_default()
.push(core_episode(row)); .push(core_episode(row));
} }
Ok((seasons, episodes)) Ok(episodes)
} }
async fn load_series_row(state: &AppState, id: i64) -> Result<SeriesRow, ApiError> { async fn load_series_row(state: &AppState, id: i64) -> Result<SeriesRow, ApiError> {
@@ -383,26 +341,16 @@ async fn load_series_row(state: &AppState, id: i64) -> Result<SeriesRow, ApiErro
async fn load_series(state: &AppState, id: i64) -> Result<Series, ApiError> { async fn load_series(state: &AppState, id: i64) -> Result<Series, ApiError> {
let row = load_series_row(state, id).await?; let row = load_series_row(state, id).await?;
let season_rows = sqlx::query!(
r#"SELECT id AS "id!: i64", series_id AS "series_id!: i64", number AS "number!: i64" FROM seasons WHERE series_id = ?"#,
id
)
.fetch_all(pool(state)?)
.await?;
let seasons: Vec<_> = season_rows
.iter()
.map(|row| core_season(row.id, row.series_id, row.number))
.collect();
let episodes = sqlx::query_as!( let episodes = sqlx::query_as!(
EpisodeRow, EpisodeRow,
r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", se.number AS "season_number!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at
FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ?"#, FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ?"#,
id id
) )
.fetch_all(pool(state)?) .fetch_all(pool(state)?)
.await?; .await?;
let episodes: Vec<_> = episodes.iter().map(core_episode).collect(); let episodes: Vec<_> = episodes.iter().map(core_episode).collect();
Ok(with_status(&row, &seasons, &episodes, SystemTime::now())) Ok(with_status(&row, &episodes, SystemTime::now()))
} }
async fn require_tv_root(state: &AppState, root_id: i64) -> Result<(), ApiError> { async fn require_tv_root(state: &AppState, root_id: i64) -> Result<(), ApiError> {
@@ -448,20 +396,12 @@ pub async fn list(
.await? .await?
}; };
let (seasons, episodes) = tv_by_series(&state).await?; let episodes = tv_by_series(&state).await?;
let now = SystemTime::now(); let now = SystemTime::now();
let no_seasons: Vec<arr_core::Season> = Vec::new();
let no_episodes: Vec<arr_core::Episode> = Vec::new(); let no_episodes: Vec<arr_core::Episode> = Vec::new();
Ok(Json( Ok(Json(
rows.iter() rows.iter()
.map(|row| { .map(|row| with_status(row, episodes.get(&row.id).unwrap_or(&no_episodes), now))
with_status(
row,
seasons.get(&row.id).unwrap_or(&no_seasons),
episodes.get(&row.id).unwrap_or(&no_episodes),
now,
)
})
.collect(), .collect(),
)) ))
} }
@@ -607,7 +547,7 @@ async fn load_seasons(state: &AppState, series_id: i64) -> Result<Vec<Season>, A
.await?; .await?;
let episodes = sqlx::query_as!( let episodes = sqlx::query_as!(
EpisodeRow, EpisodeRow,
r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", se.number AS "season_number!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at
FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ? ORDER BY se.number, e.number"#, FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE se.series_id = ? ORDER BY se.number, e.number"#,
series_id series_id
) )
@@ -714,6 +654,7 @@ pub async fn create_season(
.map(|episode| arr_core::Episode { .map(|episode| arr_core::Episode {
id: EpisodeId(0), id: EpisodeId(0),
season_id: SeasonId(0), season_id: SeasonId(0),
season_number: u16::try_from(input.number).unwrap_or_default(),
number: u16::try_from(episode.number).unwrap_or_default(), number: u16::try_from(episode.number).unwrap_or_default(),
title: episode.title.clone(), title: episode.title.clone(),
air_date: air_date(episode.air_date.as_deref()), air_date: air_date(episode.air_date.as_deref()),
@@ -837,7 +778,7 @@ pub async fn update_season(
async fn load_episode(state: &AppState, id: i64) -> Result<Episode, ApiError> { async fn load_episode(state: &AppState, id: i64) -> Result<Episode, ApiError> {
let row = sqlx::query_as!( let row = sqlx::query_as!(
EpisodeRow, EpisodeRow,
r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at r#"SELECT se.series_id AS "series_id!: i64", e.id AS "id!: i64", e.season_id AS "season_id!: i64", se.number AS "season_number!: i64", e.number AS "number!: i64", e.title AS "title!: String", e.air_date, e.wanted AS "wanted!: bool", e.state AS "state!: String", e.vanished AS "vanished!: bool", e.search_attempts AS "search_attempts!: i64", e.last_searched_at
FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE e.id = ?"#, FROM episodes e JOIN seasons se ON se.id = e.season_id WHERE e.id = ?"#,
id id
) )