Files
minstrel/internal/db/queries/reacquisition.sql
T
bvandeusenandClaude Opus 5.5 04515a977c
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 1m21s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 4m57s
release / android (push) Successful in 5m11s
release / Build signed APK (releases and dev) (push) Successful in 5m28s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
feat: a wanted list — follow artists on release-type profiles; requests and re-acquisition become wants (M502 #5520)
Minstrel keeps its own list of what it is to get, keyed by MusicBrainz
release group. A want comes from an approved request, a new release by a
followed artist, or an album whose files went missing, and is held once the
library has a full edition of it.

- Migration 0081: release_type_profiles (Standard seeded: Album, EP,
  Single, studio only), follows, follow_seen_groups and wants.
  lidarr_requests becomes requests with plain *_mbid columns; approved
  album and track requests move onto the wanted list, approved artist
  requests become follows, and the library's album artists start out
  followed. missing_reacquisitions and the retry settings go.
- internal/wanted: follows are checked against MusicBrainz discographies
  daily. The first check sets a baseline, and only releases after it are
  wanted unless the follow asked for what already exists. Profiles filter
  by primary and secondary type, so live recordings stay out unless a
  profile ticks Live.
- internal/requests (was lidarrrequests): approving puts the request on
  the wanted list, and an artist request can be approved on a chosen
  profile. A request completes when its want is held.
- The re-acquisition sweeper wants a missing album back instead of asking
  Lidarr; searching for it is the search schedule's (#5521).
- Web: an admin Wanted page (Wanted, Missing files, Held, Following), a
  follow control on artist pages, a release-type profiles card on
  Integrations, "Approve on a profile" on Requests, and want state on
  Missing files. The artist detail API returns the artist's MBID.
- Android: the request body sends artist_mbid, album_mbid and track_mbid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:46:22 -04:00

86 lines
4.0 KiB
SQL

-- Re-acquisition of missing files (milestone #290, reworked for M502 #5520).
-- The unit is the album: an album whose files went missing becomes a want for
-- its release group, and the search schedule (#5521) looks for it.
-- name: GetReacquisitionSettings :one
SELECT * FROM reacquisition_settings WHERE id = true;
-- name: UpdateReacquisitionSettings :one
-- Whole-row write from the admin card; the CHECKs in migration 0056 are the
-- validation, so a bad value fails loudly rather than being clamped silently.
UPDATE reacquisition_settings
SET enabled = sqlc.arg(enabled),
grace_hours = sqlc.arg(grace_hours),
max_per_pass = sqlc.arg(max_per_pass)
WHERE id = true
RETURNING *;
-- name: ListAlbumsDueReacquisition :many
-- The sweeper's selection. An album qualifies when:
--
-- * it still has at least one track whose file has been missing longer than
-- the grace window. Measured on missing_since, which reconcile never
-- re-stamps (#2523), so it is a genuine "gone since" clock;
-- * MusicBrainz can name it: the album and its artist have MBIDs. Albums
-- failing this are counted separately (CountAlbumsMissingWithoutMbid);
-- * no want already covers it: none open, and none held since its files
-- went missing. A want held after the loss means the library has a full
-- copy despite the missing rows (#5245), so asking again would loop.
SELECT albums.id AS album_id,
albums.title AS album_title,
albums.mbid AS album_mbid,
albums.release_group_mbid AS album_release_group_mbid,
artists.name AS artist_name,
artists.mbid AS artist_mbid,
COUNT(tracks.id)::bigint AS missing_track_count,
MAX(tracks.missing_since)::timestamptz AS last_missing_since
FROM albums
JOIN artists ON artists.id = albums.artist_id
JOIN tracks ON tracks.album_id = albums.id
WHERE tracks.missing_since IS NOT NULL
AND tracks.missing_since <= now() - make_interval(hours => sqlc.arg(grace_hours)::int)
AND albums.mbid IS NOT NULL
AND artists.mbid IS NOT NULL
GROUP BY albums.id, albums.title, albums.mbid, albums.release_group_mbid,
artists.name, artists.mbid
HAVING NOT EXISTS (
SELECT 1 FROM wants w
WHERE (w.album_id = albums.id OR w.release_group_mbid = albums.release_group_mbid)
AND (w.fulfilled_at IS NULL OR w.fulfilled_at > MAX(tracks.missing_since)))
ORDER BY MIN(tracks.missing_since), albums.sort_title
LIMIT sqlc.arg(page_limit);
-- name: CountAlbumsMissingWithoutMbid :one
-- Albums with missing files that can never be wanted automatically because
-- nothing identifies them to MusicBrainz. Surfaced on the admin card so the
-- gap is visible: silently doing nothing for these would read as broken.
SELECT COUNT(DISTINCT albums.id)::bigint
FROM albums
JOIN artists ON artists.id = albums.artist_id
JOIN tracks ON tracks.album_id = albums.id
WHERE tracks.missing_since IS NOT NULL
AND (albums.mbid IS NULL OR artists.mbid IS NULL);
-- name: WantMissingAlbum :one
-- Records an album with missing files as wanted. A held want for the same
-- group is reopened; an open one is left as it is, and no row comes back.
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id)
VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_name),
sqlc.arg(title), 'missing', sqlc.arg(album_id))
ON CONFLICT (release_group_mbid) DO UPDATE
SET reason = 'missing',
album_id = EXCLUDED.album_id,
wanted_at = now(),
fulfilled_at = NULL,
updated_at = now()
WHERE wants.fulfilled_at IS NOT NULL
RETURNING *;
-- name: GetWantsForAlbums :many
-- What the wanted list says about each album on the missing-files page.
SELECT a.id AS album_id, w.id AS want_id, w.wanted_at, w.fulfilled_at
FROM albums a
JOIN wants w ON w.album_id = a.id OR w.release_group_mbid = a.release_group_mbid
WHERE a.id = ANY(sqlc.arg(album_ids)::uuid[])
ORDER BY w.wanted_at DESC;