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,282 @@
-- +goose Up
-- +goose StatementBegin
SELECT 'up SQL query';
-- +goose StatementEnd
-- Extensions
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
-- Types
CREATE TYPE role AS ENUM (
'admin',
'user'
);
-- Functions
-- +goose StatementBegin
CREATE FUNCTION delete_orphan_releases() RETURNS trigger
LANGUAGE plpgsql
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;
$$;
-- +goose StatementEnd
-- Tables
CREATE TABLE artists (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME artists_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
),
musicbrainz_id UUID UNIQUE,
image UUID,
image_source text,
CONSTRAINT artists_pkey PRIMARY KEY (id)
);
CREATE TABLE artist_aliases (
artist_id integer NOT NULL,
alias text NOT NULL,
source text NOT NULL,
is_primary boolean NOT NULL,
CONSTRAINT artist_aliases_pkey PRIMARY KEY (artist_id, alias)
);
CREATE TABLE releases (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME releases_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
),
musicbrainz_id UUID UNIQUE,
image UUID,
various_artists boolean DEFAULT false NOT NULL,
image_source text,
CONSTRAINT releases_pkey PRIMARY KEY (id)
);
CREATE TABLE artist_releases (
artist_id integer NOT NULL,
release_id integer NOT NULL,
CONSTRAINT artist_releases_pkey PRIMARY KEY (artist_id, release_id)
);
CREATE TABLE tracks (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME tracks_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
),
musicbrainz_id UUID UNIQUE,
duration integer DEFAULT 0 NOT NULL,
release_id integer NOT NULL,
CONSTRAINT tracks_pkey PRIMARY KEY (id)
);
CREATE TABLE artist_tracks (
artist_id integer NOT NULL,
track_id integer NOT NULL,
CONSTRAINT artist_tracks_pkey PRIMARY KEY (artist_id, track_id)
);
CREATE TABLE users (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME users_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
),
username text UNIQUE NOT NULL,
role role DEFAULT 'user'::role NOT NULL,
password bytea NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
);
CREATE TABLE api_keys (
id integer NOT NULL GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME api_keys_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
),
key text UNIQUE NOT NULL,
user_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL,
label text NOT NULL,
CONSTRAINT api_keys_pkey PRIMARY KEY (id)
);
CREATE TABLE release_aliases (
release_id integer NOT NULL,
alias text NOT NULL,
source text NOT NULL,
is_primary boolean NOT NULL,
CONSTRAINT release_aliases_pkey PRIMARY KEY (release_id, alias)
);
CREATE TABLE sessions (
id UUID NOT NULL,
user_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL,
expires_at timestamp without time zone NOT NULL,
persistent boolean DEFAULT false NOT NULL,
CONSTRAINT sessions_pkey PRIMARY KEY (id)
);
CREATE TABLE track_aliases (
track_id integer NOT NULL,
alias text NOT NULL,
is_primary boolean NOT NULL,
source text NOT NULL,
CONSTRAINT track_aliases_pkey PRIMARY KEY (track_id, alias)
);
CREATE TABLE listens (
track_id integer NOT NULL,
listened_at timestamptz NOT NULL,
client text,
user_id integer NOT NULL,
CONSTRAINT listens_pkey PRIMARY KEY (track_id, listened_at)
);
-- Views
CREATE VIEW artists_with_name AS
SELECT a.id,
a.musicbrainz_id,
a.image,
a.image_source,
aa.alias AS name
FROM (artists a
JOIN artist_aliases aa ON ((aa.artist_id = a.id)))
WHERE (aa.is_primary = true);
CREATE VIEW releases_with_title AS
SELECT r.id,
r.musicbrainz_id,
r.image,
r.various_artists,
r.image_source,
ra.alias AS title
FROM (releases r
JOIN release_aliases ra ON ((ra.release_id = r.id)))
WHERE (ra.is_primary = true);
CREATE VIEW tracks_with_title AS
SELECT t.id,
t.musicbrainz_id,
t.duration,
t.release_id,
ta.alias AS title
FROM (tracks t
JOIN track_aliases ta ON ((ta.track_id = t.id)))
WHERE (ta.is_primary = true);
-- Foreign Key Constraints
ALTER TABLE ONLY api_keys
ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY artist_aliases
ADD CONSTRAINT artist_aliases_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE;
ALTER TABLE ONLY artist_releases
ADD CONSTRAINT artist_releases_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE;
ALTER TABLE ONLY artist_releases
ADD CONSTRAINT artist_releases_release_id_fkey FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE;
ALTER TABLE ONLY artist_tracks
ADD CONSTRAINT artist_tracks_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE;
ALTER TABLE ONLY artist_tracks
ADD CONSTRAINT artist_tracks_track_id_fkey FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE CASCADE;
ALTER TABLE ONLY listens
ADD CONSTRAINT listens_track_id_fkey FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE CASCADE;
ALTER TABLE ONLY listens
ADD CONSTRAINT listens_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY release_aliases
ADD CONSTRAINT release_aliases_release_id_fkey FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE;
ALTER TABLE ONLY sessions
ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY track_aliases
ADD CONSTRAINT track_aliases_track_id_fkey FOREIGN KEY (track_id) REFERENCES tracks(id) ON DELETE CASCADE;
ALTER TABLE ONLY tracks
ADD CONSTRAINT track_release_id_fkey FOREIGN KEY (release_id) REFERENCES releases(id) ON DELETE CASCADE;
-- Indexes
CREATE INDEX idx_artist_aliases_alias_trgm ON artist_aliases USING gin (alias gin_trgm_ops);
CREATE INDEX idx_artist_aliases_artist_id ON artist_aliases USING btree (artist_id);
CREATE INDEX idx_artist_releases ON artist_releases USING btree (artist_id, release_id);
CREATE INDEX idx_release_aliases_alias_trgm ON release_aliases USING gin (alias gin_trgm_ops);
CREATE INDEX idx_tracks_release_id ON tracks USING btree (release_id);
CREATE INDEX listens_listened_at_idx ON listens USING btree (listened_at);
CREATE INDEX listens_track_id_listened_at_idx ON listens USING btree (track_id, listened_at);
CREATE INDEX release_aliases_release_id_idx ON release_aliases USING btree (release_id) WHERE (is_primary = true);
CREATE INDEX track_aliases_track_id_idx ON track_aliases USING btree (track_id) WHERE (is_primary = true);
CREATE INDEX idx_track_aliases_alias_trgm ON track_aliases USING gin (alias gin_trgm_ops);
-- Triggers
CREATE TRIGGER trg_delete_orphan_releases AFTER DELETE ON artist_releases FOR EACH ROW EXECUTE FUNCTION delete_orphan_releases();
-- +goose Down
-- +goose StatementBegin
SELECT 'down SQL query';
-- +goose StatementEnd
-- Drop Triggers
DROP TRIGGER IF EXISTS trg_delete_orphan_releases ON artist_releases;
-- Drop Views
DROP VIEW IF EXISTS artists_with_name;
DROP VIEW IF EXISTS releases_with_title;
DROP VIEW IF EXISTS tracks_with_title;
-- Drop Tables (in reverse dependency order)
DROP TABLE IF EXISTS listens CASCADE;
DROP TABLE IF EXISTS api_keys CASCADE;
DROP TABLE IF EXISTS artist_tracks CASCADE;
DROP TABLE IF EXISTS artist_releases CASCADE;
DROP TABLE IF EXISTS release_aliases CASCADE;
DROP TABLE IF EXISTS track_aliases CASCADE;
DROP TABLE IF EXISTS sessions CASCADE;
DROP TABLE IF EXISTS tracks CASCADE;
DROP TABLE IF EXISTS artists CASCADE;
DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS artist_aliases CASCADE;
-- Drop Functions
DROP FUNCTION IF EXISTS delete_orphan_releases();
-- Drop Types
DROP TYPE IF EXISTS role;
-- Drop Extensions
DROP EXTENSION IF EXISTS pg_trgm;

View file

@ -0,0 +1,3 @@
-- +goose Up
ALTER TABLE api_keys DROP CONSTRAINT api_keys_user_id_fkey;
ALTER TABLE api_keys ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE;

772
db/migrations/etc/dump.sql Normal file
View file

@ -0,0 +1,772 @@
-- +goose Up
-- +goose StatementBegin
--
-- PostgreSQL database dump
--
-- Dumped from database version 16.9 (Debian 16.9-1.pgdg120+1)
-- Dumped by pg_dump version 16.4 (Debian 16.4-1.pgdg120+1)
-- Started on 2025-06-11 14:30:57 UTC
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- TOC entry 2 (class 3079 OID 16511)
-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
--
-- TOC entry 3536 (class 0 OID 0)
-- Dependencies: 2
-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner:
--
COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams';
--
-- TOC entry 921 (class 1247 OID 16885)
-- Name: role; Type: TYPE; Schema: public; Owner: postgres
--
CREATE TYPE public.role AS ENUM (
'admin',
'user'
);
ALTER TYPE public.role OWNER TO postgres;
--
-- TOC entry 269 (class 1255 OID 16963)
-- Name: delete_orphan_releases(); Type: FUNCTION; Schema: public; Owner: postgres
--
CREATE FUNCTION public.delete_orphan_releases() RETURNS trigger
LANGUAGE plpgsql
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;
$$;
ALTER FUNCTION public.delete_orphan_releases() OWNER TO postgres;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- TOC entry 231 (class 1259 OID 16901)
-- Name: api_keys; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.api_keys (
id integer NOT NULL,
key text NOT NULL,
user_id integer NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
label text
);
ALTER TABLE public.api_keys OWNER TO postgres;
--
-- TOC entry 230 (class 1259 OID 16900)
-- Name: api_keys_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.api_keys ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.api_keys_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 220 (class 1259 OID 16402)
-- Name: artist_aliases; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.artist_aliases (
artist_id integer NOT NULL,
alias text NOT NULL,
source text NOT NULL,
is_primary boolean NOT NULL
);
ALTER TABLE public.artist_aliases OWNER TO postgres;
--
-- TOC entry 227 (class 1259 OID 16839)
-- Name: artist_releases; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.artist_releases (
artist_id integer NOT NULL,
release_id integer NOT NULL
);
ALTER TABLE public.artist_releases OWNER TO postgres;
--
-- TOC entry 223 (class 1259 OID 16469)
-- Name: artist_tracks; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.artist_tracks (
artist_id integer NOT NULL,
track_id integer NOT NULL
);
ALTER TABLE public.artist_tracks OWNER TO postgres;
--
-- TOC entry 219 (class 1259 OID 16393)
-- Name: artists; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.artists (
id integer NOT NULL,
musicbrainz_id uuid,
image text,
image_source text
);
ALTER TABLE public.artists OWNER TO postgres;
--
-- TOC entry 218 (class 1259 OID 16392)
-- Name: artists_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.artists ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.artists_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 235 (class 1259 OID 16980)
-- Name: artists_with_name; Type: VIEW; Schema: public; Owner: postgres
--
CREATE VIEW public.artists_with_name AS
SELECT a.id,
a.musicbrainz_id,
a.image,
a.image_source,
aa.alias AS name
FROM (public.artists a
JOIN public.artist_aliases aa ON ((aa.artist_id = a.id)))
WHERE (aa.is_primary = true);
ALTER VIEW public.artists_with_name OWNER TO postgres;
--
-- TOC entry 217 (class 1259 OID 16386)
-- Name: goose_db_version; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.goose_db_version (
id integer NOT NULL,
version_id bigint NOT NULL,
is_applied boolean NOT NULL,
tstamp timestamp without time zone DEFAULT now() NOT NULL
);
ALTER TABLE public.goose_db_version OWNER TO postgres;
--
-- TOC entry 216 (class 1259 OID 16385)
-- Name: goose_db_version_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.goose_db_version ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.goose_db_version_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 224 (class 1259 OID 16485)
-- Name: listens; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.listens (
track_id integer NOT NULL,
listened_at timestamp with time zone NOT NULL,
client text,
user_id integer NOT NULL
);
ALTER TABLE public.listens OWNER TO postgres;
--
-- TOC entry 232 (class 1259 OID 16916)
-- Name: release_aliases; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.release_aliases (
release_id integer NOT NULL,
alias text NOT NULL,
source text NOT NULL,
is_primary boolean NOT NULL
);
ALTER TABLE public.release_aliases OWNER TO postgres;
--
-- TOC entry 226 (class 1259 OID 16825)
-- Name: releases; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.releases (
id integer NOT NULL,
musicbrainz_id uuid,
image uuid,
various_artists boolean DEFAULT false NOT NULL,
image_source text
);
ALTER TABLE public.releases OWNER TO postgres;
--
-- TOC entry 225 (class 1259 OID 16824)
-- Name: releases_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.releases ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.releases_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 236 (class 1259 OID 16984)
-- Name: releases_with_title; Type: VIEW; Schema: public; Owner: postgres
--
CREATE VIEW public.releases_with_title AS
SELECT r.id,
r.musicbrainz_id,
r.image,
r.various_artists,
r.image_source,
ra.alias AS title
FROM (public.releases r
JOIN public.release_aliases ra ON ((ra.release_id = r.id)))
WHERE (ra.is_primary = true);
ALTER VIEW public.releases_with_title OWNER TO postgres;
--
-- TOC entry 233 (class 1259 OID 16940)
-- Name: sessions; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.sessions (
id uuid NOT NULL,
user_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL,
expires_at timestamp without time zone NOT NULL,
persistent boolean DEFAULT false NOT NULL
);
ALTER TABLE public.sessions OWNER TO postgres;
--
-- TOC entry 234 (class 1259 OID 16967)
-- Name: track_aliases; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.track_aliases (
track_id integer NOT NULL,
alias text NOT NULL,
is_primary boolean NOT NULL,
source text NOT NULL
);
ALTER TABLE public.track_aliases OWNER TO postgres;
--
-- TOC entry 222 (class 1259 OID 16455)
-- Name: tracks; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.tracks (
id integer NOT NULL,
musicbrainz_id uuid,
duration integer DEFAULT 0 NOT NULL,
release_id integer NOT NULL
);
ALTER TABLE public.tracks OWNER TO postgres;
--
-- TOC entry 221 (class 1259 OID 16454)
-- Name: tracks_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.tracks ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.tracks_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 237 (class 1259 OID 16988)
-- Name: tracks_with_title; Type: VIEW; Schema: public; Owner: postgres
--
CREATE VIEW public.tracks_with_title AS
SELECT t.id,
t.musicbrainz_id,
t.duration,
t.release_id,
ta.alias AS title
FROM (public.tracks t
JOIN public.track_aliases ta ON ((ta.track_id = t.id)))
WHERE (ta.is_primary = true);
ALTER VIEW public.tracks_with_title OWNER TO postgres;
--
-- TOC entry 229 (class 1259 OID 16890)
-- Name: users; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.users (
id integer NOT NULL,
username text NOT NULL,
role public.role DEFAULT 'user'::public.role NOT NULL,
password bytea NOT NULL
);
ALTER TABLE public.users OWNER TO postgres;
--
-- TOC entry 228 (class 1259 OID 16889)
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.users ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.users_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 3361 (class 2606 OID 16910)
-- Name: api_keys api_keys_key_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.api_keys
ADD CONSTRAINT api_keys_key_key UNIQUE (key);
--
-- TOC entry 3363 (class 2606 OID 16908)
-- Name: api_keys api_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.api_keys
ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
--
-- TOC entry 3335 (class 2606 OID 16408)
-- Name: artist_aliases artist_aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_aliases
ADD CONSTRAINT artist_aliases_pkey PRIMARY KEY (artist_id, alias);
--
-- TOC entry 3354 (class 2606 OID 16843)
-- Name: artist_releases artist_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_releases
ADD CONSTRAINT artist_releases_pkey PRIMARY KEY (artist_id, release_id);
--
-- TOC entry 3344 (class 2606 OID 16473)
-- Name: artist_tracks artist_tracks_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_tracks
ADD CONSTRAINT artist_tracks_pkey PRIMARY KEY (artist_id, track_id);
--
-- TOC entry 3331 (class 2606 OID 16401)
-- Name: artists artists_musicbrainz_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artists
ADD CONSTRAINT artists_musicbrainz_id_key UNIQUE (musicbrainz_id);
--
-- TOC entry 3333 (class 2606 OID 16399)
-- Name: artists artists_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artists
ADD CONSTRAINT artists_pkey PRIMARY KEY (id);
--
-- TOC entry 3329 (class 2606 OID 16391)
-- Name: goose_db_version goose_db_version_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.goose_db_version
ADD CONSTRAINT goose_db_version_pkey PRIMARY KEY (id);
--
-- TOC entry 3347 (class 2606 OID 16622)
-- Name: listens listens_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.listens
ADD CONSTRAINT listens_pkey PRIMARY KEY (track_id, listened_at);
--
-- TOC entry 3366 (class 2606 OID 16922)
-- Name: release_aliases release_aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.release_aliases
ADD CONSTRAINT release_aliases_pkey PRIMARY KEY (release_id, alias);
--
-- TOC entry 3350 (class 2606 OID 16833)
-- Name: releases releases_musicbrainz_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.releases
ADD CONSTRAINT releases_musicbrainz_id_key UNIQUE (musicbrainz_id);
--
-- TOC entry 3352 (class 2606 OID 16831)
-- Name: releases releases_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.releases
ADD CONSTRAINT releases_pkey PRIMARY KEY (id);
--
-- TOC entry 3369 (class 2606 OID 16946)
-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.sessions
ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);
--
-- TOC entry 3371 (class 2606 OID 16973)
-- Name: track_aliases track_aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.track_aliases
ADD CONSTRAINT track_aliases_pkey PRIMARY KEY (track_id, alias);
--
-- TOC entry 3340 (class 2606 OID 16463)
-- Name: tracks tracks_musicbrainz_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.tracks
ADD CONSTRAINT tracks_musicbrainz_id_key UNIQUE (musicbrainz_id);
--
-- TOC entry 3342 (class 2606 OID 16461)
-- Name: tracks tracks_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.tracks
ADD CONSTRAINT tracks_pkey PRIMARY KEY (id);
--
-- TOC entry 3357 (class 2606 OID 16897)
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- TOC entry 3359 (class 2606 OID 16899)
-- Name: users users_username_key; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_username_key UNIQUE (username);
--
-- TOC entry 3336 (class 1259 OID 16936)
-- Name: idx_artist_aliases_alias_trgm; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX idx_artist_aliases_alias_trgm ON public.artist_aliases USING gin (alias public.gin_trgm_ops);
--
-- TOC entry 3337 (class 1259 OID 16495)
-- Name: idx_artist_aliases_artist_id; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX idx_artist_aliases_artist_id ON public.artist_aliases USING btree (artist_id);
--
-- TOC entry 3355 (class 1259 OID 16855)
-- Name: idx_artist_releases; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX idx_artist_releases ON public.artist_releases USING btree (artist_id, release_id);
--
-- TOC entry 3364 (class 1259 OID 16937)
-- Name: idx_release_aliases_alias_trgm; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX idx_release_aliases_alias_trgm ON public.release_aliases USING gin (alias public.gin_trgm_ops);
--
-- TOC entry 3338 (class 1259 OID 16854)
-- Name: idx_tracks_release_id; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX idx_tracks_release_id ON public.tracks USING btree (release_id);
--
-- TOC entry 3345 (class 1259 OID 16498)
-- Name: listens_listened_at_idx; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX listens_listened_at_idx ON public.listens USING btree (listened_at);
--
-- TOC entry 3348 (class 1259 OID 16499)
-- Name: listens_track_id_listened_at_idx; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX listens_track_id_listened_at_idx ON public.listens USING btree (track_id, listened_at);
--
-- TOC entry 3367 (class 1259 OID 16992)
-- Name: release_aliases_release_id_idx; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX release_aliases_release_id_idx ON public.release_aliases USING btree (release_id) WHERE (is_primary = true);
--
-- TOC entry 3372 (class 1259 OID 16993)
-- Name: track_aliases_track_id_idx; Type: INDEX; Schema: public; Owner: postgres
--
CREATE INDEX track_aliases_track_id_idx ON public.track_aliases USING btree (track_id) WHERE (is_primary = true);
--
-- TOC entry 3384 (class 2620 OID 16964)
-- Name: artist_releases trg_delete_orphan_releases; Type: TRIGGER; Schema: public; Owner: postgres
--
CREATE TRIGGER trg_delete_orphan_releases AFTER DELETE ON public.artist_releases FOR EACH ROW EXECUTE FUNCTION public.delete_orphan_releases();
--
-- TOC entry 3380 (class 2606 OID 16957)
-- Name: api_keys api_keys_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.api_keys
ADD CONSTRAINT api_keys_user_id_fkey FOREIGN KEY (id) REFERENCES public.users(id) ON DELETE CASCADE;
--
-- TOC entry 3373 (class 2606 OID 16409)
-- Name: artist_aliases artist_aliases_artist_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_aliases
ADD CONSTRAINT artist_aliases_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES public.artists(id) ON DELETE CASCADE;
--
-- TOC entry 3378 (class 2606 OID 16844)
-- Name: artist_releases artist_releases_artist_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_releases
ADD CONSTRAINT artist_releases_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES public.artists(id) ON DELETE CASCADE;
--
-- TOC entry 3379 (class 2606 OID 16849)
-- Name: artist_releases artist_releases_release_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_releases
ADD CONSTRAINT artist_releases_release_id_fkey FOREIGN KEY (release_id) REFERENCES public.releases(id) ON DELETE CASCADE;
--
-- TOC entry 3374 (class 2606 OID 16474)
-- Name: artist_tracks artist_tracks_artist_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_tracks
ADD CONSTRAINT artist_tracks_artist_id_fkey FOREIGN KEY (artist_id) REFERENCES public.artists(id) ON DELETE CASCADE;
--
-- TOC entry 3375 (class 2606 OID 16479)
-- Name: artist_tracks artist_tracks_track_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.artist_tracks
ADD CONSTRAINT artist_tracks_track_id_fkey FOREIGN KEY (track_id) REFERENCES public.tracks(id) ON DELETE CASCADE;
--
-- TOC entry 3376 (class 2606 OID 16490)
-- Name: listens listens_track_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.listens
ADD CONSTRAINT listens_track_id_fkey FOREIGN KEY (track_id) REFERENCES public.tracks(id) ON DELETE CASCADE;
--
-- TOC entry 3377 (class 2606 OID 16952)
-- Name: listens listens_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.listens
ADD CONSTRAINT listens_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id);
--
-- TOC entry 3381 (class 2606 OID 16923)
-- Name: release_aliases release_aliases_release_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.release_aliases
ADD CONSTRAINT release_aliases_release_id_fkey FOREIGN KEY (release_id) REFERENCES public.releases(id) ON DELETE CASCADE;
--
-- TOC entry 3382 (class 2606 OID 16947)
-- Name: sessions sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.sessions
ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id);
--
-- TOC entry 3383 (class 2606 OID 16974)
-- Name: track_aliases track_aliases_track_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.track_aliases
ADD CONSTRAINT track_aliases_track_id_fkey FOREIGN KEY (track_id) REFERENCES public.tracks(id) ON DELETE CASCADE;
-- Completed on 2025-06-11 14:30:58 UTC
--
-- PostgreSQL database dump complete
--
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
-- +goose StatementEnd

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