-- Custom types CREATE TYPE MEDIA_TYPE AS ENUM ( 'movie', 'series', 'episode', 'music', 'album', 'audiobook', 'podcast', 'photo', 'other' ); CREATE TYPE MEDIA_STATUS AS ENUM ( 'unavailable', 'searching', 'downloading', 'importing', 'available', 'upgrading', 'failed' ); CREATE TYPE QUEUE_STATUS AS ENUM ( 'pending', 'downloading', 'imported', 'failed', 'blacklisted', 'cancelled' ); -- Quality profiles CREATE TABLE quality_profiles ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, media_types MEDIA_TYPE[] NOT NULL, cutoff_quality JSONB NOT NULL, allowed_qualities JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Root folders CREATE TABLE root_folders ( id SERIAL PRIMARY KEY, path TEXT NOT NULL UNIQUE, media_type MEDIA_TYPE NOT NULL, free_space BIGINT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Tags CREATE TABLE tags ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, color TEXT DEFAULT '#6366f1' ); -- Scheduled tasks CREATE TABLE scheduled_tasks ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, cron_expr TEXT NOT NULL, last_run_at TIMESTAMPTZ, next_run_at TIMESTAMPTZ, enabled BOOLEAN DEFAULT true, retention_days INTEGER DEFAULT 7 ); -- Indexers CREATE TABLE indexers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, implementation TEXT NOT NULL, url TEXT NOT NULL, api_key TEXT, categories JSONB DEFAULT '[]', settings JSONB DEFAULT '{}', enabled BOOLEAN DEFAULT true, priority INTEGER DEFAULT 0, last_success_at TIMESTAMPTZ, failure_count INTEGER DEFAULT 0, disabled_until TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Download clients CREATE TABLE download_clients ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, implementation TEXT NOT NULL, host TEXT NOT NULL, port INTEGER NOT NULL, username TEXT, password TEXT, settings JSONB DEFAULT '{}', enabled BOOLEAN DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Unified media table (partitioned by type) CREATE TABLE media ( id BIGSERIAL, media_type MEDIA_TYPE NOT NULL, title TEXT NOT NULL, sort_title TEXT NOT NULL, original_title TEXT, overview TEXT, year INTEGER, status MEDIA_STATUS NOT NULL DEFAULT 'unavailable', monitored BOOLEAN NOT NULL DEFAULT false, external_ids JSONB NOT NULL DEFAULT '{}', metadata JSONB NOT NULL DEFAULT '{}', images JSONB NOT NULL DEFAULT '[]', quality_profile_id INTEGER REFERENCES quality_profiles(id), root_folder_id INTEGER REFERENCES root_folders(id), current_quality JSONB, desired_quality JSONB, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_search_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, PRIMARY KEY (id, media_type) ) PARTITION BY LIST (media_type); CREATE TABLE media_movie PARTITION OF media FOR VALUES IN ('movie'); CREATE TABLE media_series PARTITION OF media FOR VALUES IN ('series'); CREATE TABLE media_episode PARTITION OF media FOR VALUES IN ('episode'); CREATE TABLE media_music PARTITION OF media FOR VALUES IN ('music'); CREATE TABLE media_album PARTITION OF media FOR VALUES IN ('album'); CREATE TABLE media_audiobook PARTITION OF media FOR VALUES IN ('audiobook'); CREATE TABLE media_podcast PARTITION OF media FOR VALUES IN ('podcast'); CREATE TABLE media_photo PARTITION OF media FOR VALUES IN ('photo'); CREATE TABLE media_other PARTITION OF media FOR VALUES IN ('other'); -- Media indexes CREATE INDEX idx_media_title ON media USING gin (to_tsvector('english', coalesce(title, ''))); CREATE INDEX idx_media_monitored ON media (monitored) WHERE monitored = true; CREATE INDEX idx_media_status ON media (status, media_type); CREATE INDEX idx_media_external_ids ON media USING gin (external_ids); -- Media relations (series->episodes, album->tracks) CREATE TABLE media_relations ( id BIGSERIAL PRIMARY KEY, parent_id BIGINT NOT NULL, child_id BIGINT NOT NULL, relation TEXT NOT NULL, position INTEGER, season INTEGER, UNIQUE(parent_id, child_id, relation) ); -- Media tags CREATE TABLE media_tags ( media_id BIGINT NOT NULL, media_type MEDIA_TYPE NOT NULL, tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE, PRIMARY KEY (media_id, media_type, tag_id) ); -- Unified file tracking CREATE TABLE media_files ( id BIGSERIAL PRIMARY KEY, media_id BIGINT NOT NULL, media_type MEDIA_TYPE NOT NULL, path TEXT NOT NULL, original_path TEXT, file_name TEXT NOT NULL, file_size BIGINT NOT NULL DEFAULT 0, quality JSONB NOT NULL DEFAULT '{}', codec TEXT, resolution TEXT, source TEXT, is_hardlinked BOOLEAN DEFAULT false, checksum TEXT, transcode_status TEXT DEFAULT 'none', transcode_preset TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ, UNIQUE(media_id, media_type, path) ); CREATE INDEX idx_media_files_media ON media_files (media_id, media_type); CREATE INDEX idx_media_files_transcode ON media_files (transcode_status) WHERE transcode_status != 'done'; -- Download queue CREATE TABLE download_queue ( id BIGSERIAL PRIMARY KEY, media_id BIGINT NOT NULL, media_type MEDIA_TYPE NOT NULL, release_title TEXT NOT NULL, release_url TEXT, indexer TEXT NOT NULL, download_client TEXT NOT NULL, quality JSONB NOT NULL DEFAULT '{}', size BIGINT, protocol TEXT NOT NULL DEFAULT 'torrent', status QUEUE_STATUS NOT NULL DEFAULT 'pending', progress REAL DEFAULT 0, error_message TEXT, batch_id UUID, priority INTEGER DEFAULT 0, retry_count INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 3, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_queue_status ON download_queue (status, priority DESC); CREATE INDEX idx_queue_batch ON download_queue (batch_id) WHERE batch_id IS NOT NULL; CREATE INDEX idx_queue_media ON download_queue (media_id, media_type); -- Blocklist CREATE TABLE blocklist ( id BIGSERIAL PRIMARY KEY, release_title TEXT NOT NULL, source_title TEXT, quality JSONB DEFAULT '{}', indexer TEXT, protocol TEXT DEFAULT 'torrent', torrent_hash TEXT, size BIGINT, message TEXT, media_id BIGINT, media_type MEDIA_TYPE, block_reason TEXT DEFAULT 'manual', auto_expires_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_blocklist_media ON blocklist (media_id, media_type); CREATE INDEX idx_blocklist_expires ON blocklist (auto_expires_at) WHERE auto_expires_at IS NOT NULL; -- Task execution log CREATE TABLE task_executions ( id BIGSERIAL PRIMARY KEY, task_id INTEGER NOT NULL REFERENCES scheduled_tasks(id) ON DELETE CASCADE, status TEXT NOT NULL DEFAULT 'running', started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), ended_at TIMESTAMPTZ, duration_ms INTEGER, result JSONB, error TEXT ); CREATE INDEX idx_task_exec_task ON task_executions (task_id, started_at DESC); -- Download history (partitioned for auto-cleanup) CREATE TABLE download_history ( id BIGSERIAL, media_id BIGINT NOT NULL, media_type MEDIA_TYPE NOT NULL, action TEXT NOT NULL, release_title TEXT, quality JSONB DEFAULT '{}', indexer TEXT, client TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (created_at); CREATE TABLE download_history_current PARTITION OF download_history FOR VALUES FROM (CURRENT_DATE - INTERVAL '90 days') TO (MAXVALUE);