Files
minstrel/internal/db/dbq/catalogue.sql.go
T
bvandeusenandClaude Opus 5.5 77389f88bc
release / govulncheck (push) Successful in 22s
release / web (push) Failing after 24s
release / go (push) Successful in 1m57s
release / integration (push) Successful in 4m1s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / android (push) Canceled after 4m38s
release / Build signed APK (releases and dev) (push) Canceled after 4m37s
feat: a release catalogue from MusicBrainz; request search asks it directly (M502 #5519)
Minstrel keeps its own record of what artists have released, read from
MusicBrainz, so the wanted list, search and import no longer need Lidarr.

- internal/musicbrainz: searches (artists, release groups, recordings) and
  browses (an artist's release groups; a group's releases with track lists).
  One process-wide pacer at 1.1s, shared with tag enrichment, because
  MusicBrainz's limit is per address. A 503 throttle is retried.
- Migration 0080: mb_artists, mb_release_groups, mb_artist_release_groups
  (a collaboration belongs to each credited artist), mb_releases,
  mb_release_tracks.
- internal/catalogue: Discography and Releases serve the held copy and
  re-read it when older than 7 or 30 days. An unreachable MusicBrainz
  serves the stale copy, marked. An hourly pass re-reads due artists.
- Art for what isn't held comes through /api/catalogue/art from the
  enabled cover-art sources (Cover Art Archive by release group, then the
  name-based sources) and is kept on disk, so the browser never fetches a
  third-party host. A miss is a 404 and the card shows its icon.
- Request search moves from /api/lidarr/search to /api/catalogue/search,
  with the same response shape. Albums are release groups, and "in
  library" matches albums.release_group_mbid. A 10-minute cache absorbs
  search-as-you-type. Discover suggestions point at the art endpoint
  instead of looking up Lidarr.
- Android calls the new path and loads Discover art through ServerImage.
  Installed builds older than this one lose Discover search until they
  update (no compatibility path, rule 22).

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

403 lines
11 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: catalogue.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addMBArtistReleaseGroup = `-- name: AddMBArtistReleaseGroup :exec
INSERT INTO mb_artist_release_groups (artist_mbid, release_group_mbid)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`
type AddMBArtistReleaseGroupParams struct {
ArtistMbid string
ReleaseGroupMbid string
}
func (q *Queries) AddMBArtistReleaseGroup(ctx context.Context, arg AddMBArtistReleaseGroupParams) error {
_, err := q.db.Exec(ctx, addMBArtistReleaseGroup, arg.ArtistMbid, arg.ReleaseGroupMbid)
return err
}
const deleteMBArtistReleaseGroups = `-- name: DeleteMBArtistReleaseGroups :exec
DELETE FROM mb_artist_release_groups WHERE artist_mbid = $1
`
func (q *Queries) DeleteMBArtistReleaseGroups(ctx context.Context, artistMbid string) error {
_, err := q.db.Exec(ctx, deleteMBArtistReleaseGroups, artistMbid)
return err
}
const deleteMBReleasesForGroup = `-- name: DeleteMBReleasesForGroup :exec
DELETE FROM mb_releases WHERE release_group_mbid = $1
`
// Tracks go with their releases (ON DELETE CASCADE).
func (q *Queries) DeleteMBReleasesForGroup(ctx context.Context, releaseGroupMbid string) error {
_, err := q.db.Exec(ctx, deleteMBReleasesForGroup, releaseGroupMbid)
return err
}
const getMBArtist = `-- name: GetMBArtist :one
SELECT mbid, name, sort_name, disambiguation, artist_type, country, discography_refreshed_at, created_at, updated_at FROM mb_artists WHERE mbid = $1
`
func (q *Queries) GetMBArtist(ctx context.Context, mbid string) (MbArtist, error) {
row := q.db.QueryRow(ctx, getMBArtist, mbid)
var i MbArtist
err := row.Scan(
&i.Mbid,
&i.Name,
&i.SortName,
&i.Disambiguation,
&i.ArtistType,
&i.Country,
&i.DiscographyRefreshedAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getMBReleaseGroup = `-- name: GetMBReleaseGroup :one
SELECT mbid, title, primary_type, secondary_types, first_release_date, artist_credit, releases_refreshed_at, created_at, updated_at FROM mb_release_groups WHERE mbid = $1
`
func (q *Queries) GetMBReleaseGroup(ctx context.Context, mbid string) (MbReleaseGroup, error) {
row := q.db.QueryRow(ctx, getMBReleaseGroup, mbid)
var i MbReleaseGroup
err := row.Scan(
&i.Mbid,
&i.Title,
&i.PrimaryType,
&i.SecondaryTypes,
&i.FirstReleaseDate,
&i.ArtistCredit,
&i.ReleasesRefreshedAt,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const insertMBRelease = `-- name: InsertMBRelease :exec
INSERT INTO mb_releases (mbid, release_group_mbid, title, status, release_date, country,
disambiguation, formats, track_count)
VALUES ($1, $2, $3, $4,
$5, $6, $7, $8,
$9)
ON CONFLICT (mbid) DO UPDATE SET
release_group_mbid = EXCLUDED.release_group_mbid,
title = EXCLUDED.title,
status = EXCLUDED.status,
release_date = EXCLUDED.release_date,
country = EXCLUDED.country,
disambiguation = EXCLUDED.disambiguation,
formats = EXCLUDED.formats,
track_count = EXCLUDED.track_count
`
type InsertMBReleaseParams struct {
Mbid string
ReleaseGroupMbid string
Title string
Status string
ReleaseDate string
Country string
Disambiguation string
Formats string
TrackCount int32
}
func (q *Queries) InsertMBRelease(ctx context.Context, arg InsertMBReleaseParams) error {
_, err := q.db.Exec(ctx, insertMBRelease,
arg.Mbid,
arg.ReleaseGroupMbid,
arg.Title,
arg.Status,
arg.ReleaseDate,
arg.Country,
arg.Disambiguation,
arg.Formats,
arg.TrackCount,
)
return err
}
const insertMBReleaseTrack = `-- name: InsertMBReleaseTrack :exec
INSERT INTO mb_release_tracks (release_mbid, medium_position, position, medium_format, number,
title, length_ms, recording_mbid)
VALUES ($1, $2, $3,
$4, $5, $6, $7,
$8)
`
type InsertMBReleaseTrackParams struct {
ReleaseMbid string
MediumPosition int32
Position int32
MediumFormat string
Number string
Title string
LengthMs int32
RecordingMbid string
}
func (q *Queries) InsertMBReleaseTrack(ctx context.Context, arg InsertMBReleaseTrackParams) error {
_, err := q.db.Exec(ctx, insertMBReleaseTrack,
arg.ReleaseMbid,
arg.MediumPosition,
arg.Position,
arg.MediumFormat,
arg.Number,
arg.Title,
arg.LengthMs,
arg.RecordingMbid,
)
return err
}
const listMBArtistReleaseGroups = `-- name: ListMBArtistReleaseGroups :many
SELECT g.mbid, g.title, g.primary_type, g.secondary_types, g.first_release_date, g.artist_credit, g.releases_refreshed_at, g.created_at, g.updated_at FROM mb_release_groups g
JOIN mb_artist_release_groups ag ON ag.release_group_mbid = g.mbid
WHERE ag.artist_mbid = $1
ORDER BY g.first_release_date = '', g.first_release_date, g.title, g.mbid
`
// An artist's discography, oldest first; undated groups last.
func (q *Queries) ListMBArtistReleaseGroups(ctx context.Context, artistMbid string) ([]MbReleaseGroup, error) {
rows, err := q.db.Query(ctx, listMBArtistReleaseGroups, artistMbid)
if err != nil {
return nil, err
}
defer rows.Close()
var items []MbReleaseGroup
for rows.Next() {
var i MbReleaseGroup
if err := rows.Scan(
&i.Mbid,
&i.Title,
&i.PrimaryType,
&i.SecondaryTypes,
&i.FirstReleaseDate,
&i.ArtistCredit,
&i.ReleasesRefreshedAt,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMBArtistsDueForRefresh = `-- name: ListMBArtistsDueForRefresh :many
SELECT mbid FROM mb_artists
WHERE discography_refreshed_at IS NULL OR discography_refreshed_at < $1
ORDER BY discography_refreshed_at NULLS FIRST, mbid
LIMIT $2
`
type ListMBArtistsDueForRefreshParams struct {
Before pgtype.Timestamptz
MaxRows int32
}
// Artists whose discography has never been read, or was read before the
// cutoff: never-read first, then oldest first.
func (q *Queries) ListMBArtistsDueForRefresh(ctx context.Context, arg ListMBArtistsDueForRefreshParams) ([]string, error) {
rows, err := q.db.Query(ctx, listMBArtistsDueForRefresh, arg.Before, arg.MaxRows)
if err != nil {
return nil, err
}
defer rows.Close()
var items []string
for rows.Next() {
var mbid string
if err := rows.Scan(&mbid); err != nil {
return nil, err
}
items = append(items, mbid)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMBReleaseTracksForGroup = `-- name: ListMBReleaseTracksForGroup :many
SELECT t.release_mbid, t.medium_position, t.position, t.medium_format, t.number, t.title, t.length_ms, t.recording_mbid FROM mb_release_tracks t
JOIN mb_releases r ON r.mbid = t.release_mbid
WHERE r.release_group_mbid = $1
ORDER BY t.release_mbid, t.medium_position, t.position
`
func (q *Queries) ListMBReleaseTracksForGroup(ctx context.Context, releaseGroupMbid string) ([]MbReleaseTrack, error) {
rows, err := q.db.Query(ctx, listMBReleaseTracksForGroup, releaseGroupMbid)
if err != nil {
return nil, err
}
defer rows.Close()
var items []MbReleaseTrack
for rows.Next() {
var i MbReleaseTrack
if err := rows.Scan(
&i.ReleaseMbid,
&i.MediumPosition,
&i.Position,
&i.MediumFormat,
&i.Number,
&i.Title,
&i.LengthMs,
&i.RecordingMbid,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMBReleasesForGroup = `-- name: ListMBReleasesForGroup :many
SELECT mbid, release_group_mbid, title, status, release_date, country, disambiguation, formats, track_count, created_at FROM mb_releases
WHERE release_group_mbid = $1
ORDER BY status <> 'Official', release_date = '', release_date, title, mbid
`
// Official editions first, then by date; undated last.
func (q *Queries) ListMBReleasesForGroup(ctx context.Context, releaseGroupMbid string) ([]MbRelease, error) {
rows, err := q.db.Query(ctx, listMBReleasesForGroup, releaseGroupMbid)
if err != nil {
return nil, err
}
defer rows.Close()
var items []MbRelease
for rows.Next() {
var i MbRelease
if err := rows.Scan(
&i.Mbid,
&i.ReleaseGroupMbid,
&i.Title,
&i.Status,
&i.ReleaseDate,
&i.Country,
&i.Disambiguation,
&i.Formats,
&i.TrackCount,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const markMBArtistDiscographyRefreshed = `-- name: MarkMBArtistDiscographyRefreshed :exec
UPDATE mb_artists SET discography_refreshed_at = now() WHERE mbid = $1
`
func (q *Queries) MarkMBArtistDiscographyRefreshed(ctx context.Context, mbid string) error {
_, err := q.db.Exec(ctx, markMBArtistDiscographyRefreshed, mbid)
return err
}
const markMBReleaseGroupReleasesRefreshed = `-- name: MarkMBReleaseGroupReleasesRefreshed :exec
UPDATE mb_release_groups SET releases_refreshed_at = now() WHERE mbid = $1
`
func (q *Queries) MarkMBReleaseGroupReleasesRefreshed(ctx context.Context, mbid string) error {
_, err := q.db.Exec(ctx, markMBReleaseGroupReleasesRefreshed, mbid)
return err
}
const upsertMBArtist = `-- name: UpsertMBArtist :exec
INSERT INTO mb_artists (mbid, name, sort_name, disambiguation, artist_type, country)
VALUES ($1, $2, $3, $4,
$5, $6)
ON CONFLICT (mbid) DO UPDATE SET
name = EXCLUDED.name,
sort_name = EXCLUDED.sort_name,
disambiguation = EXCLUDED.disambiguation,
artist_type = EXCLUDED.artist_type,
country = EXCLUDED.country,
updated_at = now()
`
type UpsertMBArtistParams struct {
Mbid string
Name string
SortName string
Disambiguation string
ArtistType string
Country string
}
// The release catalogue read from MusicBrainz (M502 #5519).
func (q *Queries) UpsertMBArtist(ctx context.Context, arg UpsertMBArtistParams) error {
_, err := q.db.Exec(ctx, upsertMBArtist,
arg.Mbid,
arg.Name,
arg.SortName,
arg.Disambiguation,
arg.ArtistType,
arg.Country,
)
return err
}
const upsertMBReleaseGroup = `-- name: UpsertMBReleaseGroup :exec
INSERT INTO mb_release_groups (mbid, title, primary_type, secondary_types, first_release_date, artist_credit)
VALUES ($1, $2, $3, $4::text[],
$5, $6)
ON CONFLICT (mbid) DO UPDATE SET
title = EXCLUDED.title,
primary_type = EXCLUDED.primary_type,
secondary_types = EXCLUDED.secondary_types,
first_release_date = EXCLUDED.first_release_date,
artist_credit = EXCLUDED.artist_credit,
updated_at = now()
`
type UpsertMBReleaseGroupParams struct {
Mbid string
Title string
PrimaryType string
SecondaryTypes []string
FirstReleaseDate string
ArtistCredit string
}
// Leaves releases_refreshed_at alone: re-reading a discography says nothing
// about whether the group's track lists are current.
func (q *Queries) UpsertMBReleaseGroup(ctx context.Context, arg UpsertMBReleaseGroupParams) error {
_, err := q.db.Exec(ctx, upsertMBReleaseGroup,
arg.Mbid,
arg.Title,
arg.PrimaryType,
arg.SecondaryTypes,
arg.FirstReleaseDate,
arg.ArtistCredit,
)
return err
}