Files
minstrel/internal/db/migrations/0081_wanted.up.sql
T
bvandeusenandClaude Opus 5.5 6028d78844
release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m33s
release / go (push) Successful in 1m49s
release / integration (push) Successful in 5m42s
release / android (push) Successful in 5m59s
release / Build signed APK (releases and dev) (push) Successful in 6m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
docs: migration headers keep the before and after, without ids, quotes or one library's numbers (#6277)
Applies rule 32's migration exception: each header still says what the
schema was, what changes and what it prevents, but task, milestone,
decision and rule ids are replaced by the practice they stood for, the
operator quotes by the decision, single-install counts are dropped, and
pointers to a deleted package and an external design doc are gone.
golang-migrate keeps no checksum, so editing applied migrations'
comments is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:34:39 -04:00

166 lines
7.8 KiB
SQL

-- 0081_wanted.up.sql — the wanted list.
--
-- With Lidarr gone, Minstrel keeps its own record of what it is looking for:
--
-- release_type_profiles which kinds of release a follow wants: primary
-- types (Album, EP, Single…) and the secondary types
-- allowed (Live, Compilation, Remix…). An empty
-- secondary list means studio releases only, which
-- is how live recordings are left out.
-- follows artists whose new releases are wanted, each on a
-- profile.
-- follow_seen_groups release groups a follow has already judged, so
-- each is judged once.
-- wants release groups wanted: a request, a followed
-- artist's new release, or an album whose files went
-- missing. A want is held once the library has a
-- full copy (internal/wanted).
--
-- lidarr_requests becomes requests with no Lidarr columns: approving a request
-- now records a want or a follow instead of sending an add to Lidarr. The
-- missing-file sweeper's attempt bookkeeping goes too; retrying belongs to the
-- search schedule, which works per want.
CREATE TABLE release_type_profiles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL UNIQUE,
primary_types text[] NOT NULL,
secondary_types text[] NOT NULL DEFAULT '{}',
is_default boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT release_type_profiles_primary_nonempty CHECK (cardinality(primary_types) > 0)
);
-- At most one default; the service keeps exactly one by moving it.
CREATE UNIQUE INDEX release_type_profiles_one_default
ON release_type_profiles (is_default) WHERE is_default;
-- Lidarr's "Standard" metadata profile, which followed artists used: albums,
-- EPs and singles, studio releases only.
INSERT INTO release_type_profiles (name, primary_types, secondary_types, is_default)
VALUES ('Standard', ARRAY['Album', 'EP', 'Single'], '{}', true);
CREATE TABLE follows (
artist_mbid text PRIMARY KEY,
artist_name text NOT NULL,
-- RESTRICT: a profile in use cannot be deleted out from under a follow.
profile_id uuid NOT NULL REFERENCES release_type_profiles (id) ON DELETE RESTRICT,
followed_by uuid REFERENCES users (id) ON DELETE SET NULL,
followed_at timestamptz NOT NULL DEFAULT now(),
-- When the first discography read marked everything then listed as seen.
-- NULL until then: a follow wants only what appears after it.
baseline_at timestamptz,
-- Whether the baseline also wants what the artist had already released
-- that the profile passes and the library lacks: an approved artist
-- request, or an admin ticking "also want…". Cleared once applied.
want_existing boolean NOT NULL DEFAULT false,
-- The discography read this follow last applied.
checked_at timestamptz,
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX follows_profile_idx ON follows (profile_id);
CREATE TABLE follow_seen_groups (
artist_mbid text NOT NULL REFERENCES follows (artist_mbid) ON DELETE CASCADE,
release_group_mbid text NOT NULL,
seen_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (artist_mbid, release_group_mbid)
);
CREATE TABLE wants (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
release_group_mbid text NOT NULL UNIQUE,
artist_mbid text NOT NULL,
artist_name text NOT NULL,
title text NOT NULL,
primary_type text NOT NULL DEFAULT '',
first_release_date text NOT NULL DEFAULT '',
reason text NOT NULL
CONSTRAINT wants_reason_check CHECK (reason IN ('request', 'follow', 'missing')),
-- The library album this want is about (a missing want), or the one that
-- answered it.
album_id uuid REFERENCES albums (id) ON DELETE SET NULL,
created_by uuid REFERENCES users (id) ON DELETE SET NULL,
-- When it was last wanted: set again when a held want is reopened, so
-- "arrived since it was wanted" measures from the reopening.
wanted_at timestamptz NOT NULL DEFAULT now(),
fulfilled_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX wants_open_idx ON wants (wanted_at) WHERE fulfilled_at IS NULL;
CREATE INDEX wants_artist_idx ON wants (artist_mbid);
-- Requests lose Lidarr.
DROP TABLE missing_reacquisitions;
ALTER TABLE lidarr_requests RENAME TO requests;
ALTER TYPE lidarr_request_status RENAME TO request_status;
ALTER TYPE lidarr_request_kind RENAME TO request_kind;
ALTER TABLE requests RENAME COLUMN lidarr_artist_mbid TO artist_mbid;
ALTER TABLE requests RENAME COLUMN lidarr_album_mbid TO album_mbid;
ALTER TABLE requests RENAME COLUMN lidarr_track_mbid TO track_mbid;
ALTER TABLE requests DROP COLUMN quality_profile_id;
ALTER TABLE requests DROP COLUMN root_folder_path;
ALTER TABLE requests DROP COLUMN lidarr_add_confirmed_at;
ALTER INDEX lidarr_requests_user_id_idx RENAME TO requests_user_id_idx;
ALTER INDEX lidarr_requests_status_idx RENAME TO requests_status_idx;
ALTER INDEX lidarr_requests_artist_mbid_idx RENAME TO requests_artist_mbid_idx;
ALTER INDEX lidarr_requests_album_mbid_idx RENAME TO requests_album_mbid_idx;
-- Older requests may name albums by release id. A want is keyed by
-- release group, so repoint every request the library can translate.
UPDATE requests r
SET album_mbid = a.release_group_mbid,
updated_at = now()
FROM albums a
WHERE a.mbid = r.album_mbid
AND a.release_group_mbid IS NOT NULL
AND a.release_group_mbid <> r.album_mbid;
-- Approved album and track requests still waiting become wants, judged by
-- the same test as any other (internal/wanted).
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, created_by, wanted_at)
SELECT DISTINCT ON (r.album_mbid)
r.album_mbid, r.artist_mbid, r.artist_name, COALESCE(r.album_title, ''), 'request', r.user_id, r.requested_at
FROM requests r
WHERE r.status = 'approved'
AND r.kind IN ('album', 'track')
AND r.album_mbid IS NOT NULL
ORDER BY r.album_mbid, r.requested_at
ON CONFLICT (release_group_mbid) DO NOTHING;
-- The library's album artists start out followed on the default profile: the
-- artists already in the library are the wanted artists. Various Artists and
-- MusicBrainz's bracketed placeholders ([unknown], [anonymous]…) are not
-- artists anyone follows.
INSERT INTO follows (artist_mbid, artist_name, profile_id)
SELECT ar.mbid, ar.name, p.id
FROM artists ar
CROSS JOIN (SELECT id FROM release_type_profiles WHERE is_default) p
WHERE ar.mbid IS NOT NULL AND ar.mbid <> ''
AND ar.mbid <> '89ad4ac3-39f7-470e-963a-56509c546377'
AND ar.name NOT LIKE '[%]'
AND EXISTS (SELECT 1 FROM albums al WHERE al.artist_id = ar.id)
ON CONFLICT (artist_mbid) DO NOTHING;
-- An approved artist request was an artist added to Lidarr: it is followed.
INSERT INTO follows (artist_mbid, artist_name, profile_id, followed_by)
SELECT DISTINCT ON (r.artist_mbid) r.artist_mbid, r.artist_name, p.id, r.decided_by
FROM requests r
CROSS JOIN (SELECT id FROM release_type_profiles WHERE is_default) p
WHERE r.status = 'approved' AND r.kind = 'artist'
ORDER BY r.artist_mbid, r.requested_at
ON CONFLICT (artist_mbid) DO NOTHING;
-- The sweeper now only records a want; how often to look again is the
-- search schedule's business.
ALTER TABLE reacquisition_settings
DROP COLUMN backoff_base_hours,
DROP COLUMN backoff_max_hours,
DROP COLUMN max_attempts,
DROP COLUMN auto_approve;