diff --git a/containers/database/music-agregator/002_schema.sql b/containers/database/music-agregator/002_schema.sql new file mode 100644 index 0000000..fd09b65 --- /dev/null +++ b/containers/database/music-agregator/002_schema.sql @@ -0,0 +1,112 @@ +CREATE TYPE monitor_state AS ENUM ('unmonitored', 'monitored', 'excluded'); +CREATE TYPE download_state AS ENUM ('pending', 'downloading', 'completed', 'failed', 'seeding'); + +CREATE TABLE artists ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + external_id VARCHAR(255) NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + artist_type VARCHAR(50) NOT NULL, + country VARCHAR(10), + genres TEXT[] NOT NULL DEFAULT '{}', + image_url TEXT, + monitor_state monitor_state NOT NULL DEFAULT 'unmonitored', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_artists_monitor_state ON artists(monitor_state); + +CREATE TABLE albums ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + external_id VARCHAR(255) NOT NULL UNIQUE, + artist_id UUID NOT NULL REFERENCES artists(id) ON DELETE CASCADE, + title VARCHAR(255) NOT NULL, + album_type VARCHAR(50) NOT NULL, + release_date DATE, + total_tracks INTEGER NOT NULL DEFAULT 0, + total_discs INTEGER NOT NULL DEFAULT 1, + label VARCHAR(255), + genres TEXT[] NOT NULL DEFAULT '{}', + cover_url TEXT, + monitor_state monitor_state NOT NULL DEFAULT 'unmonitored', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_albums_artist_id ON albums(artist_id); +CREATE INDEX idx_albums_monitor_state ON albums(monitor_state); + +CREATE TABLE tracks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + external_id VARCHAR(255) NOT NULL UNIQUE, + album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, + title VARCHAR(255) NOT NULL, + duration_ms INTEGER NOT NULL DEFAULT 0, + isrc VARCHAR(20), + disc_number INTEGER NOT NULL DEFAULT 1, + track_number INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_tracks_album_id ON tracks(album_id); + +CREATE TABLE torrents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, + info_hash VARCHAR(64) NOT NULL UNIQUE, + tracker VARCHAR(100) NOT NULL, + title TEXT NOT NULL, + format VARCHAR(20) NOT NULL, + quality VARCHAR(20) NOT NULL, + source VARCHAR(50), + bit_depth INTEGER, + sample_rate INTEGER, + seeders INTEGER NOT NULL DEFAULT 0, + peers INTEGER NOT NULL DEFAULT 0, + size BIGINT NOT NULL DEFAULT 0, + track_count INTEGER NOT NULL DEFAULT 0, + has_cover_art BOOLEAN NOT NULL DEFAULT false, + has_cue_sheet BOOLEAN NOT NULL DEFAULT false, + has_rip_log BOOLEAN NOT NULL DEFAULT false, + download_link TEXT, + torrent_file BYTEA, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_torrents_album_id ON torrents(album_id); + +CREATE TABLE downloads ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + torrent_id UUID NOT NULL REFERENCES torrents(id) ON DELETE CASCADE, + album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, + format VARCHAR(20) NOT NULL, + quality VARCHAR(20) NOT NULL, + state download_state NOT NULL DEFAULT 'pending', + qbit_hash VARCHAR(64), + save_path TEXT, + error_message TEXT, + queued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_downloads_album_id ON downloads(album_id); +CREATE INDEX idx_downloads_torrent_id ON downloads(torrent_id); +CREATE INDEX idx_downloads_state ON downloads(state); + +CREATE TABLE download_files ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + download_id UUID NOT NULL REFERENCES downloads(id) ON DELETE CASCADE, + track_id UUID REFERENCES tracks(id) ON DELETE SET NULL, + file_path TEXT NOT NULL, + file_size BIGINT NOT NULL DEFAULT 0, + file_type VARCHAR(50) NOT NULL, + sha256_hash VARCHAR(64), + verified_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_download_files_download_id ON download_files(download_id); diff --git a/containers/database/music-agregator/003_event_bus.sql b/containers/database/music-agregator/003_event_bus.sql new file mode 100644 index 0000000..6e8ec7c --- /dev/null +++ b/containers/database/music-agregator/003_event_bus.sql @@ -0,0 +1,33 @@ +CREATE TABLE workflow_runs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + album_id UUID NOT NULL REFERENCES albums(id) ON DELETE CASCADE, + quality VARCHAR(20) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'running', + error_message TEXT, + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + running_lock BOOLEAN GENERATED ALWAYS AS (CASE WHEN status = 'running' THEN TRUE ELSE NULL END) STORED, + CONSTRAINT idx_workflow_runs_active UNIQUE (album_id, quality, running_lock) +); + +CREATE INDEX idx_workflow_runs_status ON workflow_runs(status); + +CREATE TABLE album_events ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + seq BIGSERIAL NOT NULL, + workflow_run_id UUID NOT NULL REFERENCES workflow_runs(id) ON DELETE CASCADE, + album_id UUID NOT NULL, + event_type VARCHAR(20) NOT NULL, + step VARCHAR(50) NOT NULL, + message TEXT NOT NULL, + data_json JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_album_events_workflow ON album_events(workflow_run_id); +CREATE INDEX idx_album_events_album ON album_events(album_id); +CREATE INDEX idx_album_events_seq ON album_events(seq); + +ALTER TYPE download_state ADD VALUE IF NOT EXISTS 'cancelled';