chore: initial public commit

This commit is contained in:
Gabe Farrell 2025-06-11 19:45:39 -04:00
commit fc9054b78c
250 changed files with 32809 additions and 0 deletions

View file

@ -0,0 +1,106 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';
-- +goose StatementEnd
CREATE TABLE IF NOT EXISTS artists (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY(id),
musicbrainz_id UUID UNIQUE,
name TEXT NOT NULL,
image UUID,
image_source TEXT
);
CREATE TABLE IF NOT EXISTS artist_aliases (
artist_id INT NOT NULL REFERENCES artists(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
PRIMARY KEY (artist_id, alias),
source TEXT NOT NULL
);
-- CREATE TABLE IF NOT EXISTS release_groups (
-- id INT NOT NULL GENERATED ALWAYS AS IDENTITY,
-- PRIMARY KEY(id),
-- musicbrainz_id UUID UNIQUE,
-- title TEXT NOT NULL,
-- various_artists BOOLEAN NOT NULL DEFAULT FALSE,
-- image TEXT
-- );
CREATE TABLE IF NOT EXISTS releases (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY(id),
musicbrainz_id UUID UNIQUE,
-- release_group_id INT REFERENCES release_groups(id) ON DELETE SET NULL,
image UUID,
image_source TEXT,
various_artists BOOLEAN NOT NULL DEFAULT FALSE,
title TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS release_aliases (
release_id INT NOT NULL REFERENCES releases(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
PRIMARY KEY (release_id, alias),
source TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS artist_releases (
artist_id INT REFERENCES artists(id) ON DELETE CASCADE,
release_id INT REFERENCES releases(id) ON DELETE CASCADE,
PRIMARY KEY (artist_id, release_id)
);
CREATE TABLE IF NOT EXISTS tracks (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY(id),
musicbrainz_id UUID UNIQUE,
title TEXT NOT NULL,
duration INT NOT NULL DEFAULT 0,
release_id INT NOT NULL REFERENCES releases(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS artist_tracks (
artist_id INT REFERENCES artists(id) ON DELETE CASCADE,
track_id INT REFERENCES tracks(id) ON DELETE CASCADE,
PRIMARY KEY (artist_id, track_id)
);
CREATE TABLE IF NOT EXISTS listens (
track_id INT NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
listened_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY(track_id, listened_at)
);
-- Indexes
CREATE INDEX idx_artist_aliases_artist_id ON artist_aliases(artist_id);
CREATE INDEX idx_artist_releases ON artist_releases(artist_id, release_id);
CREATE INDEX idx_tracks_release_id ON tracks(release_id);
CREATE INDEX listens_listened_at_idx ON listens(listened_at);
CREATE INDEX listens_track_id_listened_at_idx ON listens(track_id, listened_at);
-- Trigram search support
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_tracks_title_trgm ON tracks USING gin (title gin_trgm_ops);
-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
-- +goose StatementEnd
DROP INDEX idx_artist_aliases_artist_id;
DROP INDEX idx_artist_releases;
DROP INDEX idx_tracks_release_id;
DROP INDEX listens_listened_at_idx;
DROP INDEX listens_track_id_listened_at_idx;
DROP INDEX idx_tracks_title_trgm;
DROP TABLE listens;
DROP TABLE artist_aliases;
DROP TABLE artist_releases;
DROP TABLE artist_tracks;
DROP TABLE tracks;
DROP TABLE releases;
DROP TABLE release_groups;
DROP TABLE artists;

View file

@ -0,0 +1,87 @@
-- +goose Up
-- +goose StatementBegin
-- Step 1: Create new releases table with surrogate ID
DROP TABLE releases;
CREATE TABLE releases (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
musicbrainz_id UUID UNIQUE,
release_group_id INT REFERENCES release_groups(id) ON DELETE SET NULL,
title TEXT NOT NULL
);
-- Step 2: Create artist_releases (replaces artist_release_groups)
CREATE TABLE artist_releases (
artist_id INT REFERENCES artists(id) ON DELETE CASCADE,
release_id INT REFERENCES releases(id) ON DELETE CASCADE,
PRIMARY KEY (artist_id, release_id)
);
-- Step 3: Populate releases with one release per release_group
INSERT INTO releases (musicbrainz_id, release_group_id, title)
SELECT musicbrainz_id, id AS release_group_id, title
FROM release_groups;
-- Step 4: Add release_id to tracks temporarily
ALTER TABLE tracks ADD COLUMN release_id INT;
-- Step 5: Fill release_id in tracks from the newly inserted releases
UPDATE tracks
SET release_id = releases.id
FROM releases
WHERE tracks.release_group_id = releases.release_group_id;
-- Step 6: Set release_id to NOT NULL now that it's populated
ALTER TABLE tracks ALTER COLUMN release_id SET NOT NULL;
-- Step 7: Drop old FK and column for release_group_id
ALTER TABLE tracks DROP CONSTRAINT tracks_release_group_id_fkey;
ALTER TABLE tracks DROP COLUMN release_group_id;
-- Step 8: Drop old artist_release_groups and migrate to artist_releases
INSERT INTO artist_releases (artist_id, release_id)
SELECT arg.artist_id, r.id
FROM artist_release_groups arg
JOIN releases r ON arg.release_group_id = r.release_group_id;
DROP TABLE artist_release_groups;
-- Step 9: Add indexes for new relations
CREATE INDEX idx_tracks_release_id ON tracks(release_id);
CREATE INDEX idx_artist_releases ON artist_releases(artist_id, release_id);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
-- Rollback: Recreate artist_release_groups
CREATE TABLE artist_release_groups (
artist_id INT REFERENCES artists(id) ON DELETE CASCADE,
release_group_id INT REFERENCES release_groups(id) ON DELETE CASCADE,
PRIMARY KEY (artist_id, release_group_id)
);
-- Recreate release_group_id in tracks
ALTER TABLE tracks ADD COLUMN release_group_id INT;
-- Restore release_group_id values
UPDATE tracks
SET release_group_id = r.release_group_id
FROM releases r
WHERE tracks.release_id = r.id;
-- Restore artist_release_groups values
INSERT INTO artist_release_groups (artist_id, release_group_id)
SELECT ar.artist_id, r.release_group_id
FROM artist_releases ar
JOIN releases r ON ar.release_id = r.id;
-- Drop new tables and columns
ALTER TABLE tracks DROP COLUMN release_id;
DROP INDEX IF EXISTS idx_tracks_release_id;
DROP INDEX IF EXISTS idx_artist_releases;
DROP TABLE artist_releases;
DROP TABLE releases;
-- +goose StatementEnd

View file

@ -0,0 +1,52 @@
-- +goose Up
-- +goose StatementBegin
CREATE TYPE role AS ENUM ('admin', 'user');
CREATE TABLE IF NOT EXISTS users (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY,
PRIMARY KEY(id),
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role role NOT NULL DEFAULT 'user'
);
CREATE TABLE IF NOT EXISTS api_keys (
id INT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
key TEXT NOT NULL UNIQUE,
user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
label TEXT
);
CREATE TABLE IF NOT EXISTS release_aliases (
release_id INT NOT NULL REFERENCES releases(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
PRIMARY KEY (release_id, alias),
source TEXT NOT NULL
);
ALTER TABLE listens
ADD user_id INT NOT NULL REFERENCES users(id);
ALTER TABLE listens
ADD client TEXT;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE listens
DROP COLUMN client;
ALTER TABLE listens
DROP COLUMN user_id;
DROP TABLE IF EXISTS release_aliases;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS users;
DROP TYPE IF EXISTS role;
-- +goose StatementEnd

View file

@ -0,0 +1,8 @@
-- +goose Up
-- +goose StatementBegin
INSERT INTO release_aliases (release_id, alias, source)
SELECT id, title, 'Canonical'
FROM releases;
-- +goose StatementEnd

View file

@ -0,0 +1,7 @@
-- +goose Up
-- +goose StatementBegin
CREATE INDEX idx_artist_aliases_alias_trgm ON artist_aliases USING GIN (alias gin_trgm_ops);
CREATE INDEX idx_release_aliases_alias_trgm ON release_aliases USING GIN (alias gin_trgm_ops);
-- +goose StatementEnd

View file

@ -0,0 +1,7 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE users DROP COLUMN password;
ALTER TABLE users ADD password BYTEA NOT NULL;
-- +goose StatementEnd

View file

@ -0,0 +1,19 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE sessions (
id UUID PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL,
persistent BOOLEAN NOT NULL DEFAULT false
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS sessions;
-- +goose StatementEnd

View file

@ -0,0 +1,30 @@
-- +goose Up
-- +goose StatementBegin
CREATE OR REPLACE FUNCTION delete_orphan_releases()
RETURNS TRIGGER AS $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM artist_releases
WHERE release_id = OLD.release_id
) THEN
DELETE FROM releases WHERE id = OLD.release_id;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_delete_orphan_releases
AFTER DELETE ON artist_releases
FOR EACH ROW
EXECUTE FUNCTION delete_orphan_releases();
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TRIGGER IF EXISTS trg_delete_orphan_releases ON artist_releases;
DROP FUNCTION IF EXISTS delete_orphan_releases;
-- +goose StatementEnd

View file

@ -0,0 +1,81 @@
-- +goose Up
-- +goose StatementBegin
-- Step 1: Add the column as nullable initially
ALTER TABLE artist_aliases
ADD COLUMN is_primary boolean;
-- Step 2: Set it to true if alias matches artist name, false otherwise
UPDATE artist_aliases aa
SET is_primary = (aa.alias = a.name)
FROM artists a
WHERE aa.artist_id = a.id;
-- Step 3: Make the column NOT NULL
ALTER TABLE artist_aliases
ALTER COLUMN is_primary SET NOT NULL;
-- Step 1: Add the column as nullable initially
ALTER TABLE release_aliases
ADD COLUMN is_primary boolean;
-- Step 2: Set is_primary to true if alias matches release title, false otherwise
UPDATE release_aliases ra
SET is_primary = (ra.alias = r.title)
FROM releases r
WHERE ra.release_id = r.id;
-- Step 3: Make the column NOT NULL
ALTER TABLE release_aliases
ALTER COLUMN is_primary SET NOT NULL;
-- Step 1: Create the table
CREATE TABLE track_aliases (
track_id INTEGER NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
alias TEXT NOT NULL,
is_primary BOOLEAN NOT NULL,
source TEXT NOT NULL,
PRIMARY KEY (track_id, alias)
);
-- Step 2: Insert canonical titles from the tracks table
INSERT INTO track_aliases (track_id, alias, is_primary, source)
SELECT
id,
title,
TRUE,
'Canonical'
FROM tracks;
ALTER TABLE artists DROP COLUMN IF EXISTS name;
ALTER TABLE tracks DROP COLUMN IF EXISTS title;
ALTER TABLE releases DROP COLUMN IF EXISTS title;
CREATE VIEW IF NOT EXISTS artists_with_name AS
SELECT
a.*,
aa.alias AS name
FROM artists a
JOIN artist_aliases aa ON aa.artist_id = a.id
WHERE aa.is_primary = TRUE;
CREATE VIEW IF NOT EXISTS releases_with_title AS
SELECT
r.*,
ra.alias AS title
FROM releases r
JOIN release_aliases ra ON ra.release_id = r.id
WHERE ra.is_primary = TRUE;
CREATE VIEW IF NOT EXISTS tracks_with_title AS
SELECT
t.*,
ta.alias AS title
FROM tracks t
JOIN track_aliases ta ON ta.track_id = t.id
WHERE ta.is_primary = TRUE;
CREATE INDEX ON release_aliases (release_id) WHERE is_primary = TRUE;
CREATE INDEX ON track_aliases (track_id) WHERE is_primary = TRUE;
-- +goose StatementEnd