120 lines
5.4 KiB
SQL
120 lines
5.4 KiB
SQL
-- 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';
|