-- Music Aggregator Database Schema -- Based on docs/erd.puml CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- ══════════════════════════════════════════════════════════════ -- CONFIGURATION -- ══════════════════════════════════════════════════════════════ CREATE TABLE quality_profiles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL UNIQUE, cutoff INT NOT NULL DEFAULT 0, items JSONB NOT NULL DEFAULT '[]', upgrade_allowed BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE metadata_profiles ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL UNIQUE, primary_album_types JSONB NOT NULL DEFAULT '["Album", "EP"]', secondary_album_types JSONB NOT NULL DEFAULT '[]', release_statuses JSONB NOT NULL DEFAULT '["Official"]' ); CREATE TABLE root_folders ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, path TEXT NOT NULL UNIQUE, default_quality_profile_id UUID REFERENCES quality_profiles(id), default_metadata_profile_id UUID REFERENCES metadata_profiles(id) ); CREATE TABLE indexers ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, implementation TEXT NOT NULL, settings JSONB NOT NULL DEFAULT '{}', enable_rss BOOLEAN NOT NULL DEFAULT true, enable_search BOOLEAN NOT NULL DEFAULT true, priority INT NOT NULL DEFAULT 25 ); CREATE TABLE download_clients ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), name TEXT NOT NULL, implementation TEXT NOT NULL, settings JSONB NOT NULL DEFAULT '{}', protocol TEXT NOT NULL DEFAULT 'torrent', priority INT NOT NULL DEFAULT 1, enabled BOOLEAN NOT NULL DEFAULT true ); -- ══════════════════════════════════════════════════════════════ -- CORE MUSIC ENTITIES -- ══════════════════════════════════════════════════════════════ CREATE TABLE artist_metadata ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), foreign_artist_id TEXT UNIQUE, name TEXT NOT NULL, sort_name TEXT, disambiguation TEXT, artist_type TEXT, status TEXT, overview TEXT, images JSONB NOT NULL DEFAULT '[]', links JSONB NOT NULL DEFAULT '[]', genres JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE artists ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), metadata_id UUID NOT NULL REFERENCES artist_metadata(id) ON DELETE CASCADE, quality_profile_id UUID REFERENCES quality_profiles(id), metadata_profile_id UUID REFERENCES metadata_profiles(id), root_folder_id UUID REFERENCES root_folders(id), path TEXT, monitored BOOLEAN NOT NULL DEFAULT true, monitor_new_items TEXT NOT NULL DEFAULT 'all', last_info_sync TIMESTAMPTZ, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE albums ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artist_metadata_id UUID NOT NULL REFERENCES artist_metadata(id) ON DELETE CASCADE, foreign_album_id TEXT UNIQUE, title TEXT NOT NULL, clean_title TEXT, disambiguation TEXT, overview TEXT, album_type TEXT, release_date DATE, images JSONB NOT NULL DEFAULT '[]', genres JSONB NOT NULL DEFAULT '[]', monitored BOOLEAN NOT NULL DEFAULT true, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE album_releases ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, foreign_release_id TEXT UNIQUE, title TEXT NOT NULL, status TEXT, duration_ms INT, release_date DATE, country TEXT[], label TEXT[], format TEXT, track_count INT, monitored BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE track_files ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, path TEXT NOT NULL, relative_path TEXT NOT NULL, size BIGINT NOT NULL DEFAULT 0, file_hash TEXT, audio_hash TEXT, quality JSONB NOT NULL DEFAULT '{}', media_info JSONB NOT NULL DEFAULT '{}', scene_name TEXT, release_group TEXT, date_added TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE tracks ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), album_release_id UUID NOT NULL REFERENCES album_releases(id) ON DELETE CASCADE, artist_metadata_id UUID NOT NULL REFERENCES artist_metadata(id) ON DELETE CASCADE, track_file_id UUID REFERENCES track_files(id) ON DELETE SET NULL, foreign_track_id TEXT UNIQUE, title TEXT NOT NULL, track_number INT NOT NULL DEFAULT 1, disc_number INT NOT NULL DEFAULT 1, duration_ms INT, explicit BOOLEAN NOT NULL DEFAULT false ); -- ══════════════════════════════════════════════════════════════ -- DOWNLOAD TRACKING -- ══════════════════════════════════════════════════════════════ CREATE TABLE wanted_albums ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), album_id UUID NOT NULL UNIQUE REFERENCES albums(id) ON DELETE CASCADE, priority INT NOT NULL DEFAULT 0, search_count INT NOT NULL DEFAULT 0, last_searched_at TIMESTAMPTZ, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE download_queue ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artist_id UUID REFERENCES artists(id) ON DELETE SET NULL, album_id UUID REFERENCES albums(id) ON DELETE SET NULL, download_id TEXT, title TEXT NOT NULL, size BIGINT NOT NULL DEFAULT 0, size_left BIGINT NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'queued', progress REAL NOT NULL DEFAULT 0.0, error_message TEXT, protocol TEXT NOT NULL DEFAULT 'torrent', indexer TEXT, download_client TEXT, torrent_hash TEXT, output_path TEXT, added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), completed_at TIMESTAMPTZ ); CREATE TABLE blocklist ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), artist_id UUID NOT NULL REFERENCES artists(id) ON DELETE CASCADE, album_id UUID REFERENCES albums(id) ON DELETE CASCADE, source_title TEXT NOT NULL, quality JSONB NOT NULL DEFAULT '{}', size BIGINT NOT NULL DEFAULT 0, protocol TEXT, indexer TEXT, message TEXT, torrent_hash TEXT, date TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ══════════════════════════════════════════════════════════════ -- INDEXES -- ══════════════════════════════════════════════════════════════ CREATE INDEX idx_artist_metadata_name ON artist_metadata(name); CREATE INDEX idx_artist_metadata_foreign_id ON artist_metadata(foreign_artist_id); CREATE INDEX idx_albums_artist ON albums(artist_metadata_id); CREATE INDEX idx_albums_foreign_id ON albums(foreign_album_id); CREATE INDEX idx_albums_release_date ON albums(release_date); CREATE INDEX idx_album_releases_album ON album_releases(album_id); CREATE INDEX idx_tracks_release ON tracks(album_release_id); CREATE INDEX idx_tracks_artist ON tracks(artist_metadata_id); CREATE INDEX idx_track_files_album ON track_files(album_id); CREATE INDEX idx_track_files_hash ON track_files(file_hash); CREATE INDEX idx_track_files_audio_hash ON track_files(audio_hash); CREATE INDEX idx_wanted_albums_priority ON wanted_albums(priority DESC); CREATE INDEX idx_download_queue_status ON download_queue(status); CREATE INDEX idx_download_queue_album ON download_queue(album_id); CREATE INDEX idx_blocklist_artist ON blocklist(artist_id); CREATE INDEX idx_blocklist_torrent ON blocklist(torrent_hash); -- ══════════════════════════════════════════════════════════════ -- DEFAULT DATA -- ══════════════════════════════════════════════════════════════ INSERT INTO quality_profiles (name, cutoff, items, upgrade_allowed) VALUES ('Any', 0, '[]', true), ('Lossless', 1, '[{"quality": "FLAC", "allowed": true}, {"quality": "ALAC", "allowed": true}]', true), ('Standard', 2, '[{"quality": "MP3-320", "allowed": true}, {"quality": "MP3-VBR-V0", "allowed": true}]', true); INSERT INTO metadata_profiles (name, primary_album_types, secondary_album_types, release_statuses) VALUES ('Standard', '["Album", "EP"]', '[]', '["Official"]'), ('All', '["Album", "EP", "Single", "Broadcast", "Other"]', '["Compilation", "Soundtrack", "Spokenword", "Interview", "Audiobook", "Live", "Remix", "DJ-mix", "Mixtape/Street", "Demo"]', '["Official", "Promotional", "Bootleg"]');