fix(db): address TV model review
This commit is contained in:
@@ -158,11 +158,13 @@ pub struct Policy {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct MovieOverrides {
|
||||
pub struct TitleOverrides {
|
||||
pub only_4k: bool,
|
||||
pub allow_english_audio: bool,
|
||||
}
|
||||
|
||||
pub type MovieOverrides = TitleOverrides;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MovieState {
|
||||
Missing,
|
||||
@@ -186,12 +188,6 @@ pub struct Movie {
|
||||
pub last_searched_at: Option<SystemTime>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SeriesOverrides {
|
||||
pub only_4k: bool,
|
||||
pub allow_english_audio: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Series {
|
||||
pub id: SeriesId,
|
||||
@@ -201,7 +197,8 @@ pub struct Series {
|
||||
pub original_language: Language,
|
||||
pub root_id: RootId,
|
||||
pub auto_track: bool,
|
||||
pub overrides: SeriesOverrides,
|
||||
pub overrides: TitleOverrides,
|
||||
pub upstream_ended: bool,
|
||||
pub blocked: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
-- TV side of the separate aggregates in DESIGN.md §4. This migration is
|
||||
-- additive: the movie tables stay concrete and unchanged.
|
||||
CREATE TABLE series (
|
||||
id INTEGER PRIMARY KEY,
|
||||
tmdb_id INTEGER NOT NULL UNIQUE,
|
||||
@@ -7,6 +9,8 @@ CREATE TABLE series (
|
||||
root_id INTEGER NOT NULL REFERENCES roots (id),
|
||||
auto_track INTEGER NOT NULL DEFAULT 0 CHECK (auto_track IN (0, 1)),
|
||||
overrides TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(overrides)),
|
||||
-- §4.2 needs upstream completion to derive `ended` rather than store it.
|
||||
upstream_ended INTEGER NOT NULL DEFAULT 0 CHECK (upstream_ended IN (0, 1)),
|
||||
blocked INTEGER NOT NULL DEFAULT 0 CHECK (blocked IN (0, 1)),
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||
@@ -14,6 +18,8 @@ CREATE TABLE series (
|
||||
|
||||
CREATE INDEX series_root ON series (root_id);
|
||||
|
||||
-- Before TV roots existed, the roots foreign key was sufficient to keep a
|
||||
-- movie on a movie root. Preserve that invariant now that both kinds exist.
|
||||
CREATE TRIGGER movies_require_movie_root
|
||||
BEFORE INSERT ON movies
|
||||
WHEN EXISTS (SELECT 1 FROM roots WHERE id = NEW.root_id AND kind != 'movie')
|
||||
@@ -59,8 +65,9 @@ CREATE TABLE episodes (
|
||||
title TEXT NOT NULL,
|
||||
air_date TEXT,
|
||||
wanted INTEGER NOT NULL DEFAULT 0 CHECK (wanted IN (0, 1)),
|
||||
-- Mirrors arr-core's canonical EpisodeState vocabulary.
|
||||
state TEXT NOT NULL DEFAULT 'missing'
|
||||
CHECK (state IN ('missing', 'grabbed', 'imported')),
|
||||
CHECK (state IN ('missing', 'downloading', 'available')),
|
||||
search_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
last_searched_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
@@ -77,18 +84,31 @@ CREATE INDEX episodes_state ON episodes (state);
|
||||
INSERT INTO policies (
|
||||
name, required_audio, dub_blacklist, hdr_rules, size_bands,
|
||||
resolution_pref, source_weights, score_weights
|
||||
) SELECT
|
||||
'TV — main', required_audio, dub_blacklist, hdr_rules, size_bands,
|
||||
resolution_pref, source_weights, score_weights
|
||||
FROM policies WHERE name = 'Movies — main';
|
||||
) VALUES (
|
||||
'TV — main',
|
||||
json('{"require":"original_language","allow_english_fallback":false}'),
|
||||
json('["pt-BR"]'),
|
||||
json('{"dv_profile_allow":["8.1"],"dv_profile_reject":["5","7"],"allow_hdr10":true,"allow_hdr10plus":true,"allow_sdr":true}'),
|
||||
json('{"2160p":{"floor_gib":8,"target_gib":22,"penalty_points_per_gib_over":60},"1080p":{"floor_gib":3,"target_gib":8,"penalty_points_per_gib_over":60}}'),
|
||||
json('["2160p","1080p"]'),
|
||||
json('{"Remux":4,"BluRay":3,"WEB-DL":2,"WEBRip":1,"HDTV":0}'),
|
||||
json('{"size_at_target":1000,"source_tier":25,"seeder_doubling":8}')
|
||||
);
|
||||
|
||||
INSERT INTO policies (
|
||||
name, required_audio, dub_blacklist, hdr_rules, size_bands,
|
||||
resolution_pref, source_weights, score_weights
|
||||
) SELECT
|
||||
'TV — kids', required_audio, dub_blacklist, hdr_rules, size_bands,
|
||||
resolution_pref, source_weights, score_weights
|
||||
FROM policies WHERE name = 'Movies — kids';
|
||||
) VALUES (
|
||||
'TV — kids',
|
||||
json('{"require":"any_of","langs":["pt-PT"],"or_original_when_portuguese":true,"allow_english_fallback":false}'),
|
||||
json('["pt-BR"]'),
|
||||
json('{"dv_profile_allow":["8.1"],"dv_profile_reject":["5","7"],"allow_hdr10":true,"allow_hdr10plus":true,"allow_sdr":true}'),
|
||||
json('{"2160p":{"floor_gib":8,"target_gib":22,"penalty_points_per_gib_over":60},"1080p":{"floor_gib":3,"target_gib":8,"penalty_points_per_gib_over":60}}'),
|
||||
json('["2160p","1080p"]'),
|
||||
-- §5.2: pt-PT dubs are concentrated in streaming releases.
|
||||
json('{"WEB-DL":4,"WEBRip":2,"BluRay":1,"Remux":1,"HDTV":0}'),
|
||||
json('{"size_at_target":1000,"source_tier":25,"seeder_doubling":8}')
|
||||
);
|
||||
|
||||
INSERT INTO roots (kind, audience, path, policy_id)
|
||||
SELECT 'tv', 'main', '/mnt/media/tv/main', id FROM policies WHERE name = 'TV — main';
|
||||
|
||||
@@ -298,11 +298,10 @@ mod tests {
|
||||
"auto_track and tracked are rules, not episode intent"
|
||||
);
|
||||
|
||||
let movie_count: i64 = sqlx::query_scalar("SELECT count(*) FROM movies")
|
||||
.fetch_one(db.pool())
|
||||
sqlx::query("INSERT INTO movies (tmdb_id, title, root_id) SELECT 693134, 'Dune Part Two', id FROM roots WHERE kind = 'movie' AND audience = 'main'")
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.expect("movie schema remains available");
|
||||
assert_eq!(movie_count, 0);
|
||||
.expect("existing movie schema remains writable");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -329,4 +328,43 @@ mod tests {
|
||||
.await
|
||||
.expect_err("series cannot use movie root");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn episode_state_and_upstream_completion_are_constrained() {
|
||||
let (_dir, db) = fresh().await;
|
||||
let root_id: i64 =
|
||||
sqlx::query_scalar("SELECT id FROM roots WHERE kind = 'tv' AND audience = 'main'")
|
||||
.fetch_one(db.pool())
|
||||
.await
|
||||
.expect("TV root");
|
||||
let series_id = sqlx::query(
|
||||
"INSERT INTO series (tmdb_id, title, root_id, upstream_ended)
|
||||
VALUES (82728, 'Bluey', ?, 1)",
|
||||
)
|
||||
.bind(root_id)
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.expect("ended series")
|
||||
.last_insert_rowid();
|
||||
let season_id = sqlx::query("INSERT INTO seasons (series_id, number) VALUES (?, 1)")
|
||||
.bind(series_id)
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.expect("season")
|
||||
.last_insert_rowid();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO episodes (season_id, number, title, state)
|
||||
VALUES (?, 1, 'Magic Xylophone', 'grabbed')",
|
||||
)
|
||||
.bind(season_id)
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.expect_err("database mirrors EpisodeState");
|
||||
sqlx::query("UPDATE series SET upstream_ended = 2 WHERE id = ?")
|
||||
.bind(series_id)
|
||||
.execute(db.pool())
|
||||
.await
|
||||
.expect_err("upstream completion is boolean");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user