feat(db): add series, season and episode model (#71)
This commit was merged in pull request #71.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
-- 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,
|
||||
title TEXT NOT NULL,
|
||||
year INTEGER,
|
||||
original_language TEXT,
|
||||
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'))
|
||||
) STRICT;
|
||||
|
||||
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')
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'movies require a movie root');
|
||||
END;
|
||||
|
||||
CREATE TRIGGER movies_require_movie_root_on_update
|
||||
BEFORE UPDATE OF root_id ON movies
|
||||
WHEN EXISTS (SELECT 1 FROM roots WHERE id = NEW.root_id AND kind != 'movie')
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'movies require a movie root');
|
||||
END;
|
||||
|
||||
CREATE TRIGGER series_require_tv_root
|
||||
BEFORE INSERT ON series
|
||||
WHEN EXISTS (SELECT 1 FROM roots WHERE id = NEW.root_id AND kind != 'tv')
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'series require a TV root');
|
||||
END;
|
||||
|
||||
CREATE TRIGGER series_require_tv_root_on_update
|
||||
BEFORE UPDATE OF root_id ON series
|
||||
WHEN EXISTS (SELECT 1 FROM roots WHERE id = NEW.root_id AND kind != 'tv')
|
||||
BEGIN
|
||||
SELECT RAISE(ABORT, 'series require a TV root');
|
||||
END;
|
||||
|
||||
CREATE TABLE seasons (
|
||||
id INTEGER PRIMARY KEY,
|
||||
series_id INTEGER NOT NULL REFERENCES series (id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL CHECK (number >= 0),
|
||||
tracked INTEGER NOT NULL DEFAULT 0 CHECK (tracked 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')),
|
||||
UNIQUE (series_id, number)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE episodes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
season_id INTEGER NOT NULL REFERENCES seasons (id) ON DELETE CASCADE,
|
||||
number INTEGER NOT NULL CHECK (number >= 0),
|
||||
title TEXT NOT NULL,
|
||||
air_date TEXT,
|
||||
wanted INTEGER NOT NULL DEFAULT 0 CHECK (wanted IN (0, 1)),
|
||||
-- Mirrors arr-core's canonical MediaState vocabulary. The older movies
|
||||
-- table uses legacy names; its additive correction is tracked in #73.
|
||||
state TEXT NOT NULL DEFAULT 'missing'
|
||||
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')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
UNIQUE (season_id, number)
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX episodes_pending_search
|
||||
ON episodes (last_searched_at)
|
||||
WHERE wanted = 1 AND state = 'missing';
|
||||
|
||||
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
|
||||
) 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"]'),
|
||||
-- §5.5 deliberately keeps source tier as Remux > BluRay > WEB-DL.
|
||||
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
|
||||
) 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';
|
||||
|
||||
INSERT INTO roots (kind, audience, path, policy_id)
|
||||
SELECT 'tv', 'kids', '/mnt/media/tv/kids', id FROM policies WHERE name = 'TV — kids';
|
||||
Reference in New Issue
Block a user