release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m30s
release / go (push) Successful in 1m46s
release / integration (push) Successful in 5m23s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 6m2s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1696 lines
52 KiB
Go
1696 lines
52 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
||
// versions:
|
||
// sqlc v1.31.1
|
||
// source: wanted.sql
|
||
|
||
package dbq
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/jackc/pgx/v5/pgtype"
|
||
)
|
||
|
||
const clearDefaultReleaseTypeProfile = `-- name: ClearDefaultReleaseTypeProfile :exec
|
||
UPDATE release_type_profiles SET is_default = false, updated_at = now() WHERE is_default
|
||
`
|
||
|
||
func (q *Queries) ClearDefaultReleaseTypeProfile(ctx context.Context) error {
|
||
_, err := q.db.Exec(ctx, clearDefaultReleaseTypeProfile)
|
||
return err
|
||
}
|
||
|
||
const clearHeldUpgrades = `-- name: ClearHeldUpgrades :execrows
|
||
UPDATE wants SET upgrade = '', held_tier = '', held_quality = '', upgrade_checked_at = now()
|
||
WHERE fulfilled_at IS NOT NULL AND upgrade <> ''
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = wants.album_id AND t.missing_since IS NULL AND t.duration_ms > 0)
|
||
`
|
||
|
||
// A held want whose album has no track left to judge is not wanted again.
|
||
func (q *Queries) ClearHeldUpgrades(ctx context.Context) (int64, error) {
|
||
result, err := q.db.Exec(ctx, clearHeldUpgrades)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const countAlbumsToMatch = `-- name: CountAlbumsToMatch :one
|
||
SELECT COUNT(*)
|
||
FROM albums a
|
||
WHERE (a.created_at <= $1::timestamptz
|
||
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_at <= $1::timestamptz))
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
|
||
WHERE m.album_id = a.id
|
||
AND (m.outcome IN ('listed', 'dismissed')
|
||
OR (m.matcher >= $2::int
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))))
|
||
`
|
||
|
||
type CountAlbumsToMatchParams struct {
|
||
Cutoff pgtype.Timestamptz
|
||
Matcher int32
|
||
}
|
||
|
||
// Whether a finished listing has albums to go back to: those
|
||
// ListAlbumsToMatch would hand it.
|
||
func (q *Queries) CountAlbumsToMatch(ctx context.Context, arg CountAlbumsToMatchParams) (int64, error) {
|
||
row := q.db.QueryRow(ctx, countAlbumsToMatch, arg.Cutoff, arg.Matcher)
|
||
var count int64
|
||
err := row.Scan(&count)
|
||
return count, err
|
||
}
|
||
|
||
const countWants = `-- name: CountWants :one
|
||
SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted,
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing,
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held,
|
||
-- Held albums wanted for a better copy (#5524).
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL AND upgrade <> '')::bigint AS upgrades,
|
||
COUNT(DISTINCT artist_mbid)::bigint AS artists,
|
||
-- Library albums waiting to be matched by hand (#5579).
|
||
(SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))::bigint AS unmatched,
|
||
(SELECT COUNT(*) FROM follows)::bigint AS following,
|
||
-- The Wanted view's other tabs (#5522): downloads running, those
|
||
-- waiting on a person, and releases set aside.
|
||
(SELECT COUNT(*) FROM grabs WHERE ended_at IS NULL)::bigint AS activity,
|
||
(SELECT COUNT(*) FROM grabs
|
||
WHERE ended_at IS NULL AND import_state IN ('review', 'failed'))::bigint AS attention,
|
||
(SELECT COUNT(*) FROM release_blocks b
|
||
WHERE b.expires_at IS NULL OR b.expires_at > now())::bigint AS blocked
|
||
FROM wants
|
||
`
|
||
|
||
type CountWantsRow struct {
|
||
Wanted int64
|
||
Missing int64
|
||
Held int64
|
||
Upgrades int64
|
||
Artists int64
|
||
Unmatched int64
|
||
Following int64
|
||
Activity int64
|
||
Attention int64
|
||
Blocked int64
|
||
}
|
||
|
||
func (q *Queries) CountWants(ctx context.Context) (CountWantsRow, error) {
|
||
row := q.db.QueryRow(ctx, countWants)
|
||
var i CountWantsRow
|
||
err := row.Scan(
|
||
&i.Wanted,
|
||
&i.Missing,
|
||
&i.Held,
|
||
&i.Upgrades,
|
||
&i.Artists,
|
||
&i.Unmatched,
|
||
&i.Following,
|
||
&i.Activity,
|
||
&i.Attention,
|
||
&i.Blocked,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const createReleaseTypeProfile = `-- name: CreateReleaseTypeProfile :one
|
||
INSERT INTO release_type_profiles (name, primary_types, secondary_types)
|
||
VALUES ($1, $2, $3)
|
||
RETURNING id, name, primary_types, secondary_types, is_default, created_at, updated_at
|
||
`
|
||
|
||
type CreateReleaseTypeProfileParams struct {
|
||
Name string
|
||
PrimaryTypes []string
|
||
SecondaryTypes []string
|
||
}
|
||
|
||
func (q *Queries) CreateReleaseTypeProfile(ctx context.Context, arg CreateReleaseTypeProfileParams) (ReleaseTypeProfile, error) {
|
||
row := q.db.QueryRow(ctx, createReleaseTypeProfile, arg.Name, arg.PrimaryTypes, arg.SecondaryTypes)
|
||
var i ReleaseTypeProfile
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Name,
|
||
&i.PrimaryTypes,
|
||
&i.SecondaryTypes,
|
||
&i.IsDefault,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const deleteFollow = `-- name: DeleteFollow :execrows
|
||
DELETE FROM follows WHERE artist_mbid = $1
|
||
`
|
||
|
||
func (q *Queries) DeleteFollow(ctx context.Context, artistMbid string) (int64, error) {
|
||
result, err := q.db.Exec(ctx, deleteFollow, artistMbid)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const deleteReleaseTypeProfile = `-- name: DeleteReleaseTypeProfile :execrows
|
||
DELETE FROM release_type_profiles WHERE id = $1 AND NOT is_default
|
||
`
|
||
|
||
// The default stays; one in use is refused by the follows foreign key.
|
||
func (q *Queries) DeleteReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (int64, error) {
|
||
result, err := q.db.Exec(ctx, deleteReleaseTypeProfile, id)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const deleteWant = `-- name: DeleteWant :execrows
|
||
DELETE FROM wants WHERE id = $1
|
||
`
|
||
|
||
func (q *Queries) DeleteWant(ctx context.Context, id pgtype.UUID) (int64, error) {
|
||
result, err := q.db.Exec(ctx, deleteWant, id)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const finishLibraryListing = `-- name: FinishLibraryListing :one
|
||
UPDATE acquisition_settings
|
||
SET library_listed_at = COALESCE(library_listed_at, now()),
|
||
library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
|
||
WHERE id = true
|
||
RETURNING library_listed_at, library_unlisted
|
||
`
|
||
|
||
type FinishLibraryListingRow struct {
|
||
LibraryListedAt pgtype.Timestamptz
|
||
LibraryUnlisted int32
|
||
}
|
||
|
||
// The listing is done, or done again after a newer matcher's retries:
|
||
// count the albums no match was found for. The first finish is kept.
|
||
func (q *Queries) FinishLibraryListing(ctx context.Context) (FinishLibraryListingRow, error) {
|
||
row := q.db.QueryRow(ctx, finishLibraryListing)
|
||
var i FinishLibraryListingRow
|
||
err := row.Scan(&i.LibraryListedAt, &i.LibraryUnlisted)
|
||
return i, err
|
||
}
|
||
|
||
const fulfillWant = `-- name: FulfillWant :exec
|
||
UPDATE wants
|
||
SET fulfilled_at = now(),
|
||
album_id = $1,
|
||
updated_at = now()
|
||
WHERE id = $2 AND fulfilled_at IS NULL
|
||
`
|
||
|
||
type FulfillWantParams struct {
|
||
AlbumID pgtype.UUID
|
||
ID pgtype.UUID
|
||
}
|
||
|
||
func (q *Queries) FulfillWant(ctx context.Context, arg FulfillWantParams) error {
|
||
_, err := q.db.Exec(ctx, fulfillWant, arg.AlbumID, arg.ID)
|
||
return err
|
||
}
|
||
|
||
const getAlbumToMatch = `-- name: GetAlbumToMatch :one
|
||
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
JOIN library_album_matches m ON m.album_id = a.id AND m.outcome IN ('unmatched', 'dismissed')
|
||
WHERE a.id = $1
|
||
`
|
||
|
||
type GetAlbumToMatchRow struct {
|
||
ID pgtype.UUID
|
||
Title string
|
||
Mbid *string
|
||
ReleaseGroupMbid *string
|
||
ArtistName string
|
||
ArtistMbid *string
|
||
}
|
||
|
||
// One album a person is matching by hand, as the matcher reads albums.
|
||
func (q *Queries) GetAlbumToMatch(ctx context.Context, id pgtype.UUID) (GetAlbumToMatchRow, error) {
|
||
row := q.db.QueryRow(ctx, getAlbumToMatch, id)
|
||
var i GetAlbumToMatchRow
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Title,
|
||
&i.Mbid,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistName,
|
||
&i.ArtistMbid,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const getDefaultReleaseTypeProfile = `-- name: GetDefaultReleaseTypeProfile :one
|
||
SELECT id, name, primary_types, secondary_types, is_default, created_at, updated_at FROM release_type_profiles WHERE is_default
|
||
`
|
||
|
||
func (q *Queries) GetDefaultReleaseTypeProfile(ctx context.Context) (ReleaseTypeProfile, error) {
|
||
row := q.db.QueryRow(ctx, getDefaultReleaseTypeProfile)
|
||
var i ReleaseTypeProfile
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Name,
|
||
&i.PrimaryTypes,
|
||
&i.SecondaryTypes,
|
||
&i.IsDefault,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const getFollow = `-- name: GetFollow :one
|
||
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_by, f.followed_at, f.baseline_at, f.want_existing, f.checked_at, f.updated_at, p.name AS profile_name
|
||
FROM follows f
|
||
JOIN release_type_profiles p ON p.id = f.profile_id
|
||
WHERE f.artist_mbid = $1
|
||
`
|
||
|
||
type GetFollowRow struct {
|
||
ArtistMbid string
|
||
ArtistName string
|
||
ProfileID pgtype.UUID
|
||
FollowedBy pgtype.UUID
|
||
FollowedAt pgtype.Timestamptz
|
||
BaselineAt pgtype.Timestamptz
|
||
WantExisting bool
|
||
CheckedAt pgtype.Timestamptz
|
||
UpdatedAt pgtype.Timestamptz
|
||
ProfileName string
|
||
}
|
||
|
||
func (q *Queries) GetFollow(ctx context.Context, artistMbid string) (GetFollowRow, error) {
|
||
row := q.db.QueryRow(ctx, getFollow, artistMbid)
|
||
var i GetFollowRow
|
||
err := row.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.ProfileID,
|
||
&i.FollowedBy,
|
||
&i.FollowedAt,
|
||
&i.BaselineAt,
|
||
&i.WantExisting,
|
||
&i.CheckedAt,
|
||
&i.UpdatedAt,
|
||
&i.ProfileName,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const getLibraryListing = `-- name: GetLibraryListing :one
|
||
|
||
SELECT library_listing_started_at, library_listed_at, library_unlisted
|
||
FROM acquisition_settings WHERE id = true
|
||
`
|
||
|
||
type GetLibraryListingRow struct {
|
||
LibraryListingStartedAt pgtype.Timestamptz
|
||
LibraryListedAt pgtype.Timestamptz
|
||
LibraryUnlisted int32
|
||
}
|
||
|
||
// The one-time listing of the library (#5567). ---------------------------
|
||
func (q *Queries) GetLibraryListing(ctx context.Context) (GetLibraryListingRow, error) {
|
||
row := q.db.QueryRow(ctx, getLibraryListing)
|
||
var i GetLibraryListingRow
|
||
err := row.Scan(&i.LibraryListingStartedAt, &i.LibraryListedAt, &i.LibraryUnlisted)
|
||
return i, err
|
||
}
|
||
|
||
const getReleaseTypeProfile = `-- name: GetReleaseTypeProfile :one
|
||
SELECT id, name, primary_types, secondary_types, is_default, created_at, updated_at FROM release_type_profiles WHERE id = $1
|
||
`
|
||
|
||
func (q *Queries) GetReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (ReleaseTypeProfile, error) {
|
||
row := q.db.QueryRow(ctx, getReleaseTypeProfile, id)
|
||
var i ReleaseTypeProfile
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Name,
|
||
&i.PrimaryTypes,
|
||
&i.SecondaryTypes,
|
||
&i.IsDefault,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const getWantAlbum = `-- name: GetWantAlbum :one
|
||
SELECT id, release_group_mbid, album_id FROM wants WHERE id = $1
|
||
`
|
||
|
||
type GetWantAlbumRow struct {
|
||
ID pgtype.UUID
|
||
ReleaseGroupMbid string
|
||
AlbumID pgtype.UUID
|
||
}
|
||
|
||
// What the Wanted view's track list needs of a want (#5572).
|
||
func (q *Queries) GetWantAlbum(ctx context.Context, id pgtype.UUID) (GetWantAlbumRow, error) {
|
||
row := q.db.QueryRow(ctx, getWantAlbum, id)
|
||
var i GetWantAlbumRow
|
||
err := row.Scan(&i.ID, &i.ReleaseGroupMbid, &i.AlbumID)
|
||
return i, err
|
||
}
|
||
|
||
const getWantByReleaseGroup = `-- name: GetWantByReleaseGroup :one
|
||
SELECT id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error, upgrade, held_tier, held_quality, upgrade_checked_at FROM wants WHERE release_group_mbid = $1
|
||
`
|
||
|
||
func (q *Queries) GetWantByReleaseGroup(ctx context.Context, releaseGroupMbid string) (Want, error) {
|
||
row := q.db.QueryRow(ctx, getWantByReleaseGroup, releaseGroupMbid)
|
||
var i Want
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Title,
|
||
&i.PrimaryType,
|
||
&i.FirstReleaseDate,
|
||
&i.Reason,
|
||
&i.AlbumID,
|
||
&i.CreatedBy,
|
||
&i.WantedAt,
|
||
&i.FulfilledAt,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
&i.SearchedAt,
|
||
&i.SearchOutcome,
|
||
&i.SearchConsidered,
|
||
&i.BestRelease,
|
||
&i.BestVerdict,
|
||
&i.SearchError,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const insertLibraryWant = `-- name: InsertLibraryWant :execrows
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date,
|
||
reason, album_id, wanted_at, fulfilled_at)
|
||
VALUES ($1, $2, $3, $4,
|
||
$5, $6, 'library', $7::uuid, now(),
|
||
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = $7::uuid)
|
||
- interval '1 second', now()))
|
||
ON CONFLICT (release_group_mbid) DO NOTHING
|
||
`
|
||
|
||
type InsertLibraryWantParams struct {
|
||
ReleaseGroupMbid string
|
||
ArtistMbid string
|
||
ArtistName string
|
||
Title string
|
||
PrimaryType string
|
||
FirstReleaseDate string
|
||
AlbumID pgtype.UUID
|
||
}
|
||
|
||
// Puts one matched album on the list as held, from just before its first
|
||
// lost track if it has one. A group already on the list is left as it is.
|
||
func (q *Queries) InsertLibraryWant(ctx context.Context, arg InsertLibraryWantParams) (int64, error) {
|
||
result, err := q.db.Exec(ctx, insertLibraryWant,
|
||
arg.ReleaseGroupMbid,
|
||
arg.ArtistMbid,
|
||
arg.ArtistName,
|
||
arg.Title,
|
||
arg.PrimaryType,
|
||
arg.FirstReleaseDate,
|
||
arg.AlbumID,
|
||
)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const libraryHoldsReleaseGroup = `-- name: LibraryHoldsReleaseGroup :one
|
||
SELECT EXISTS (
|
||
SELECT 1 FROM albums a
|
||
WHERE (a.release_group_mbid = $1::text
|
||
OR a.mbid = $1
|
||
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = $1)
|
||
OR (lower(a.title) = lower($2::text)
|
||
AND a.artist_id IN (SELECT ar.id FROM artists ar WHERE ar.mbid = $3::text)))
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)
|
||
)
|
||
`
|
||
|
||
type LibraryHoldsReleaseGroupParams struct {
|
||
GroupMbid string
|
||
Title string
|
||
ArtistMbid string
|
||
}
|
||
|
||
// Whether the library already has the group: an album tagged with it or
|
||
// with one of its releases, or failing that an album of the artist's with
|
||
// the same title. Only albums with a track on disk count.
|
||
func (q *Queries) LibraryHoldsReleaseGroup(ctx context.Context, arg LibraryHoldsReleaseGroupParams) (bool, error) {
|
||
row := q.db.QueryRow(ctx, libraryHoldsReleaseGroup, arg.GroupMbid, arg.Title, arg.ArtistMbid)
|
||
var exists bool
|
||
err := row.Scan(&exists)
|
||
return exists, err
|
||
}
|
||
|
||
const listAlbumTracksToMatch = `-- name: ListAlbumTracksToMatch :many
|
||
SELECT title, disc_number, track_number, duration_ms, mbid
|
||
FROM tracks
|
||
WHERE album_id = $1
|
||
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title
|
||
`
|
||
|
||
type ListAlbumTracksToMatchRow struct {
|
||
Title string
|
||
DiscNumber *int32
|
||
TrackNumber *int32
|
||
DurationMs int32
|
||
Mbid *string
|
||
}
|
||
|
||
// What an album's tracks say about themselves, to check a match against.
|
||
func (q *Queries) ListAlbumTracksToMatch(ctx context.Context, albumID pgtype.UUID) ([]ListAlbumTracksToMatchRow, error) {
|
||
rows, err := q.db.Query(ctx, listAlbumTracksToMatch, albumID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListAlbumTracksToMatchRow
|
||
for rows.Next() {
|
||
var i ListAlbumTracksToMatchRow
|
||
if err := rows.Scan(
|
||
&i.Title,
|
||
&i.DiscNumber,
|
||
&i.TrackNumber,
|
||
&i.DurationMs,
|
||
&i.Mbid,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listAlbumsToMatch = `-- name: ListAlbumsToMatch :many
|
||
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE (a.created_at <= $1::timestamptz
|
||
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_at <= $1::timestamptz))
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
|
||
WHERE m.album_id = a.id
|
||
AND (m.outcome IN ('listed', 'dismissed')
|
||
OR (m.matcher >= $2::int
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))))
|
||
ORDER BY a.created_at, a.id
|
||
LIMIT $3
|
||
`
|
||
|
||
type ListAlbumsToMatchParams struct {
|
||
Cutoff pgtype.Timestamptz
|
||
Matcher int32
|
||
MaxRows int32
|
||
}
|
||
|
||
type ListAlbumsToMatchRow struct {
|
||
ID pgtype.UUID
|
||
Title string
|
||
Mbid *string
|
||
ReleaseGroupMbid *string
|
||
ArtistName string
|
||
ArtistMbid *string
|
||
}
|
||
|
||
// Library albums the listing has not decided, or that an older matcher
|
||
// refused, or whose tracks changed since they were refused: each is matched
|
||
// to MusicBrainz (#5569). A library album is one from before the cutoff, or
|
||
// one whose tracks were: a re-tag files a track under a new album row but
|
||
// keeps the track (#5579). An album row with no tracks is not an album in
|
||
// the library; one a person left off stays off.
|
||
func (q *Queries) ListAlbumsToMatch(ctx context.Context, arg ListAlbumsToMatchParams) ([]ListAlbumsToMatchRow, error) {
|
||
rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.Matcher, arg.MaxRows)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListAlbumsToMatchRow
|
||
for rows.Next() {
|
||
var i ListAlbumsToMatchRow
|
||
if err := rows.Scan(
|
||
&i.ID,
|
||
&i.Title,
|
||
&i.Mbid,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistName,
|
||
&i.ArtistMbid,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listFollowGroups = `-- name: ListFollowGroups :many
|
||
SELECT g.mbid, g.title, g.primary_type, g.secondary_types, g.first_release_date,
|
||
EXISTS (SELECT 1 FROM follow_seen_groups s
|
||
WHERE s.artist_mbid = $1 AND s.release_group_mbid = g.mbid) AS seen
|
||
FROM mb_artist_release_groups ag
|
||
JOIN mb_release_groups g ON g.mbid = ag.release_group_mbid
|
||
WHERE ag.artist_mbid = $1
|
||
ORDER BY g.first_release_date, g.title
|
||
`
|
||
|
||
type ListFollowGroupsRow struct {
|
||
Mbid string
|
||
Title string
|
||
PrimaryType string
|
||
SecondaryTypes []string
|
||
FirstReleaseDate string
|
||
Seen bool
|
||
}
|
||
|
||
// An artist's release groups from the catalogue, each with whether the
|
||
// follow has judged it before.
|
||
func (q *Queries) ListFollowGroups(ctx context.Context, artistMbid string) ([]ListFollowGroupsRow, error) {
|
||
rows, err := q.db.Query(ctx, listFollowGroups, artistMbid)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListFollowGroupsRow
|
||
for rows.Next() {
|
||
var i ListFollowGroupsRow
|
||
if err := rows.Scan(
|
||
&i.Mbid,
|
||
&i.Title,
|
||
&i.PrimaryType,
|
||
&i.SecondaryTypes,
|
||
&i.FirstReleaseDate,
|
||
&i.Seen,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listFollows = `-- name: ListFollows :many
|
||
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_at, f.baseline_at, f.checked_at,
|
||
p.name AS profile_name,
|
||
(SELECT a.id FROM artists a WHERE a.mbid = f.artist_mbid) AS artist_id,
|
||
(SELECT COUNT(*) FROM wants w
|
||
WHERE w.artist_mbid = f.artist_mbid AND w.fulfilled_at IS NULL)::bigint AS open_wants
|
||
FROM follows f
|
||
JOIN release_type_profiles p ON p.id = f.profile_id
|
||
ORDER BY lower(f.artist_name), f.artist_mbid
|
||
`
|
||
|
||
type ListFollowsRow struct {
|
||
ArtistMbid string
|
||
ArtistName string
|
||
ProfileID pgtype.UUID
|
||
FollowedAt pgtype.Timestamptz
|
||
BaselineAt pgtype.Timestamptz
|
||
CheckedAt pgtype.Timestamptz
|
||
ProfileName string
|
||
ArtistID pgtype.UUID
|
||
OpenWants int64
|
||
}
|
||
|
||
func (q *Queries) ListFollows(ctx context.Context) ([]ListFollowsRow, error) {
|
||
rows, err := q.db.Query(ctx, listFollows)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListFollowsRow
|
||
for rows.Next() {
|
||
var i ListFollowsRow
|
||
if err := rows.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.ProfileID,
|
||
&i.FollowedAt,
|
||
&i.BaselineAt,
|
||
&i.CheckedAt,
|
||
&i.ProfileName,
|
||
&i.ArtistID,
|
||
&i.OpenWants,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listFollowsDue = `-- name: ListFollowsDue :many
|
||
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_by, f.followed_at, f.baseline_at, f.want_existing, f.checked_at, f.updated_at
|
||
FROM follows f
|
||
WHERE f.baseline_at IS NULL OR f.checked_at IS NULL OR f.checked_at < $1
|
||
ORDER BY f.baseline_at NULLS FIRST, f.checked_at NULLS FIRST, f.artist_mbid
|
||
LIMIT $2
|
||
`
|
||
|
||
type ListFollowsDueParams struct {
|
||
Before pgtype.Timestamptz
|
||
MaxRows int32
|
||
}
|
||
|
||
// Follows whose discography has not been applied since @before, never-read
|
||
// first, so a new follow's baseline is not stuck behind daily re-checks.
|
||
func (q *Queries) ListFollowsDue(ctx context.Context, arg ListFollowsDueParams) ([]Follow, error) {
|
||
rows, err := q.db.Query(ctx, listFollowsDue, arg.Before, arg.MaxRows)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []Follow
|
||
for rows.Next() {
|
||
var i Follow
|
||
if err := rows.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.ProfileID,
|
||
&i.FollowedBy,
|
||
&i.FollowedAt,
|
||
&i.BaselineAt,
|
||
&i.WantExisting,
|
||
&i.CheckedAt,
|
||
&i.UpdatedAt,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listHeldWantFormats = `-- name: ListHeldWantFormats :many
|
||
|
||
SELECT w.id AS want_id, w.upgrade, w.held_tier, w.held_quality,
|
||
lower(t.file_format)::text AS file_format,
|
||
SUM(t.file_size)::bigint AS bytes,
|
||
SUM(t.duration_ms)::bigint AS duration_ms,
|
||
COUNT(*)::int AS tracks,
|
||
COUNT(*) FILTER (WHERE t.source_verdict = 'suspect')::int AS rips
|
||
FROM wants w
|
||
JOIN tracks t ON t.album_id = w.album_id
|
||
WHERE w.fulfilled_at IS NOT NULL
|
||
AND t.missing_since IS NULL AND t.duration_ms > 0
|
||
GROUP BY w.id, lower(t.file_format)
|
||
ORDER BY w.id
|
||
`
|
||
|
||
type ListHeldWantFormatsRow struct {
|
||
WantID pgtype.UUID
|
||
Upgrade string
|
||
HeldTier string
|
||
HeldQuality string
|
||
FileFormat string
|
||
Bytes int64
|
||
DurationMs int64
|
||
Tracks int32
|
||
Rips int32
|
||
}
|
||
|
||
// Upgrades (#5524). -------------------------------------------------------
|
||
// What each held album's copy is made of, for judging whether it is worth
|
||
// upgrading: its present tracks grouped by format, with their bytes and
|
||
// length summed, and how many are held back as video rips. A held want whose
|
||
// album has no track with a length yet has no rows.
|
||
func (q *Queries) ListHeldWantFormats(ctx context.Context) ([]ListHeldWantFormatsRow, error) {
|
||
rows, err := q.db.Query(ctx, listHeldWantFormats)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListHeldWantFormatsRow
|
||
for rows.Next() {
|
||
var i ListHeldWantFormatsRow
|
||
if err := rows.Scan(
|
||
&i.WantID,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.FileFormat,
|
||
&i.Bytes,
|
||
&i.DurationMs,
|
||
&i.Tracks,
|
||
&i.Rips,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listLibraryAlbumTracks = `-- name: ListLibraryAlbumTracks :many
|
||
SELECT title, disc_number, track_number, duration_ms, (missing_since IS NOT NULL)::bool AS missing
|
||
FROM tracks
|
||
WHERE album_id = $1
|
||
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title
|
||
`
|
||
|
||
type ListLibraryAlbumTracksRow struct {
|
||
Title string
|
||
DiscNumber *int32
|
||
TrackNumber *int32
|
||
DurationMs int32
|
||
Missing bool
|
||
}
|
||
|
||
// A held or missing album's own tracks, for the Wanted view's track list;
|
||
// missing says its file is gone.
|
||
func (q *Queries) ListLibraryAlbumTracks(ctx context.Context, albumID pgtype.UUID) ([]ListLibraryAlbumTracksRow, error) {
|
||
rows, err := q.db.Query(ctx, listLibraryAlbumTracks, albumID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListLibraryAlbumTracksRow
|
||
for rows.Next() {
|
||
var i ListLibraryAlbumTracksRow
|
||
if err := rows.Scan(
|
||
&i.Title,
|
||
&i.DiscNumber,
|
||
&i.TrackNumber,
|
||
&i.DurationMs,
|
||
&i.Missing,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listLibraryAlbums = `-- name: ListLibraryAlbums :execrows
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id, wanted_at, fulfilled_at)
|
||
SELECT DISTINCT ON (a.release_group_mbid)
|
||
a.release_group_mbid, ar.mbid, ar.name, a.title, 'library', a.id, now(),
|
||
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = a.id) - interval '1 second', now())
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
|
||
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
|
||
AND a.created_at <= $1::timestamptz
|
||
AND ($2::uuid IS NULL OR a.id = $2::uuid)
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
ORDER BY a.release_group_mbid,
|
||
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL) DESC, a.id
|
||
ON CONFLICT (release_group_mbid) DO NOTHING
|
||
`
|
||
|
||
type ListLibraryAlbumsParams struct {
|
||
Cutoff pgtype.Timestamptz
|
||
AlbumID pgtype.UUID
|
||
}
|
||
|
||
// Puts library albums on the list as held: each release group once, by the
|
||
// album holding the most tracks. Only albums the library had at the cutoff,
|
||
// and with album_id given only that album. A group already on the list is
|
||
// left as it is. An album with tracks already missing is held from just
|
||
// before the first loss, so re-acquisition still fetches it back.
|
||
func (q *Queries) ListLibraryAlbums(ctx context.Context, arg ListLibraryAlbumsParams) (int64, error) {
|
||
result, err := q.db.Exec(ctx, listLibraryAlbums, arg.Cutoff, arg.AlbumID)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const listOpenWants = `-- name: ListOpenWants :many
|
||
SELECT id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error, upgrade, held_tier, held_quality, upgrade_checked_at FROM wants
|
||
WHERE fulfilled_at IS NULL
|
||
ORDER BY wanted_at, id
|
||
LIMIT $1
|
||
`
|
||
|
||
func (q *Queries) ListOpenWants(ctx context.Context, limit int32) ([]Want, error) {
|
||
rows, err := q.db.Query(ctx, listOpenWants, limit)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []Want
|
||
for rows.Next() {
|
||
var i Want
|
||
if err := rows.Scan(
|
||
&i.ID,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Title,
|
||
&i.PrimaryType,
|
||
&i.FirstReleaseDate,
|
||
&i.Reason,
|
||
&i.AlbumID,
|
||
&i.CreatedBy,
|
||
&i.WantedAt,
|
||
&i.FulfilledAt,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
&i.SearchedAt,
|
||
&i.SearchOutcome,
|
||
&i.SearchConsidered,
|
||
&i.BestRelease,
|
||
&i.BestVerdict,
|
||
&i.SearchError,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listReleaseTypeProfiles = `-- name: ListReleaseTypeProfiles :many
|
||
|
||
SELECT p.id, p.name, p.primary_types, p.secondary_types, p.is_default, p.created_at, p.updated_at,
|
||
(SELECT COUNT(*) FROM follows f WHERE f.profile_id = p.id)::bigint AS follow_count
|
||
FROM release_type_profiles p
|
||
ORDER BY p.is_default DESC, p.name
|
||
`
|
||
|
||
type ListReleaseTypeProfilesRow struct {
|
||
ID pgtype.UUID
|
||
Name string
|
||
PrimaryTypes []string
|
||
SecondaryTypes []string
|
||
IsDefault bool
|
||
CreatedAt pgtype.Timestamptz
|
||
UpdatedAt pgtype.Timestamptz
|
||
FollowCount int64
|
||
}
|
||
|
||
// The wanted list (M502 #5520): release-type profiles, follows and wants.
|
||
func (q *Queries) ListReleaseTypeProfiles(ctx context.Context) ([]ListReleaseTypeProfilesRow, error) {
|
||
rows, err := q.db.Query(ctx, listReleaseTypeProfiles)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListReleaseTypeProfilesRow
|
||
for rows.Next() {
|
||
var i ListReleaseTypeProfilesRow
|
||
if err := rows.Scan(
|
||
&i.ID,
|
||
&i.Name,
|
||
&i.PrimaryTypes,
|
||
&i.SecondaryTypes,
|
||
&i.IsDefault,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
&i.FollowCount,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listUnmatchedLibraryAlbums = `-- name: ListUnmatchedLibraryAlbums :many
|
||
SELECT m.album_id, a.title, ar.name AS artist_name, m.detail, m.outcome, m.decided_at,
|
||
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)::int AS tracks
|
||
FROM library_album_matches m
|
||
JOIN albums a ON a.id = m.album_id
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE m.outcome IN ('unmatched', 'dismissed')
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id)
|
||
ORDER BY m.outcome DESC, lower(ar.name), lower(a.title)
|
||
LIMIT $1
|
||
`
|
||
|
||
type ListUnmatchedLibraryAlbumsRow struct {
|
||
AlbumID pgtype.UUID
|
||
Title string
|
||
ArtistName string
|
||
Detail string
|
||
Outcome string
|
||
DecidedAt pgtype.Timestamptz
|
||
Tracks int32
|
||
}
|
||
|
||
// The Collection's Unmatched section (#5579): the albums the listing could
|
||
// not match, and below them the ones a person left off. An album whose
|
||
// tracks have all moved to another row is gone, not unmatched.
|
||
func (q *Queries) ListUnmatchedLibraryAlbums(ctx context.Context, maxRows int32) ([]ListUnmatchedLibraryAlbumsRow, error) {
|
||
rows, err := q.db.Query(ctx, listUnmatchedLibraryAlbums, maxRows)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListUnmatchedLibraryAlbumsRow
|
||
for rows.Next() {
|
||
var i ListUnmatchedLibraryAlbumsRow
|
||
if err := rows.Scan(
|
||
&i.AlbumID,
|
||
&i.Title,
|
||
&i.ArtistName,
|
||
&i.Detail,
|
||
&i.Outcome,
|
||
&i.DecidedAt,
|
||
&i.Tracks,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listWantArtists = `-- name: ListWantArtists :many
|
||
WITH a AS (
|
||
SELECT w.artist_mbid,
|
||
(array_agg(w.artist_name ORDER BY w.wanted_at DESC))[1] AS artist_name,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NOT NULL)::int AS held,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason <> 'missing')::int AS wanted,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason = 'missing')::int AS missing,
|
||
COUNT(*)::int AS albums,
|
||
bool_or($1::text <> '' AND strpos(lower(w.title), lower($1::text)) > 0) AS title_match
|
||
FROM wants w
|
||
WHERE $4::text = '' OR w.artist_mbid = $4::text
|
||
GROUP BY w.artist_mbid
|
||
)
|
||
SELECT a.artist_mbid, a.artist_name::text AS artist_name, a.held, a.wanted, a.missing, a.albums,
|
||
COALESCE(NULLIF(la.sort_name, ''), a.artist_name)::text AS sort_name,
|
||
la.id AS library_artist_id,
|
||
(f.artist_mbid IS NOT NULL)::bool AS following,
|
||
(SELECT COUNT(*) FROM grabs g JOIN wants x ON x.id = g.want_id
|
||
WHERE x.artist_mbid = a.artist_mbid AND g.ended_at IS NULL)::int AS downloading,
|
||
COUNT(*) OVER () AS total
|
||
FROM a
|
||
LEFT JOIN LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = a.artist_mbid LIMIT 1) la ON true
|
||
LEFT JOIN follows f ON f.artist_mbid = a.artist_mbid
|
||
WHERE $1::text = ''
|
||
OR strpos(lower(a.artist_name), lower($1::text)) > 0
|
||
OR a.title_match
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), a.artist_name)), a.artist_mbid
|
||
LIMIT $3 OFFSET $2
|
||
`
|
||
|
||
type ListWantArtistsParams struct {
|
||
Q string
|
||
Skip int32
|
||
MaxRows int32
|
||
ArtistMbid string
|
||
}
|
||
|
||
type ListWantArtistsRow struct {
|
||
ArtistMbid string
|
||
ArtistName string
|
||
Held int32
|
||
Wanted int32
|
||
Missing int32
|
||
Albums int32
|
||
SortName string
|
||
LibraryArtistID pgtype.UUID
|
||
Following bool
|
||
Downloading int32
|
||
Total int64
|
||
}
|
||
|
||
// The Wanted view's main page (#5572): every artist on the list, A–Z like
|
||
// the library, with how many of their albums are held of how many are on
|
||
// the list. q matches the artist's name or any of their albums' titles;
|
||
// artist_mbid narrows to one artist, for their page's header.
|
||
func (q *Queries) ListWantArtists(ctx context.Context, arg ListWantArtistsParams) ([]ListWantArtistsRow, error) {
|
||
rows, err := q.db.Query(ctx, listWantArtists,
|
||
arg.Q,
|
||
arg.Skip,
|
||
arg.MaxRows,
|
||
arg.ArtistMbid,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListWantArtistsRow
|
||
for rows.Next() {
|
||
var i ListWantArtistsRow
|
||
if err := rows.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Held,
|
||
&i.Wanted,
|
||
&i.Missing,
|
||
&i.Albums,
|
||
&i.SortName,
|
||
&i.LibraryArtistID,
|
||
&i.Following,
|
||
&i.Downloading,
|
||
&i.Total,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const listWants = `-- name: ListWants :many
|
||
SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason,
|
||
w.album_id, w.wanted_at, w.fulfilled_at,
|
||
-- Why a held album is wanted again, and the copy it has (#5524).
|
||
w.upgrade, w.held_quality,
|
||
-- A request names an album before the catalogue has read it: the
|
||
-- catalogue's type and date fill in once it has.
|
||
COALESCE(NULLIF(w.primary_type, ''), g.primary_type, '')::text AS primary_type,
|
||
COALESCE(g.secondary_types, '{}')::text[] AS secondary_types,
|
||
COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, '')::text AS first_release_date,
|
||
u.username AS requested_by,
|
||
la.id AS library_artist_id,
|
||
COUNT(*) OVER () AS total,
|
||
-- The search schedule's last look (#5521), and the download running.
|
||
w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error,
|
||
-- Its latest download, running or ended, and what the import made of
|
||
-- it (#5522).
|
||
gr.id AS grab_id, gr.name AS grab_name, gr.grabbed_at, gr.origin AS grab_origin,
|
||
gr.quality AS grab_quality, gr.ended_at AS grab_ended_at, gr.end_reason AS grab_end_reason,
|
||
gr.progress AS grab_progress, gr.import_state AS grab_import_state,
|
||
gr.import_reason AS grab_import_reason, gr.end_detail AS grab_end_detail, gr.imported_at AS grab_imported_at,
|
||
gr.imported_files AS grab_imported_files
|
||
FROM wants w
|
||
LEFT JOIN users u ON u.id = w.created_by
|
||
LEFT JOIN mb_release_groups g ON g.mbid = w.release_group_mbid
|
||
LEFT JOIN LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = w.artist_mbid LIMIT 1) la ON true
|
||
LEFT JOIN grabs gr ON gr.id = (SELECT x.id FROM grabs x WHERE x.want_id = w.id
|
||
ORDER BY x.ended_at IS NULL DESC, x.grabbed_at DESC LIMIT 1)
|
||
WHERE CASE $1::text
|
||
WHEN 'all' THEN true
|
||
WHEN 'held' THEN w.fulfilled_at IS NOT NULL
|
||
WHEN 'upgrade' THEN w.fulfilled_at IS NOT NULL AND w.upgrade <> ''
|
||
WHEN 'missing' THEN w.fulfilled_at IS NULL AND w.reason = 'missing'
|
||
ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing'
|
||
END
|
||
AND ($2::text = '' OR w.artist_mbid = $2::text)
|
||
-- want_ids narrows to given wants: the Missing page's folders (M504).
|
||
AND (COALESCE(cardinality($3::uuid[]), 0) = 0 OR w.id = ANY($3::uuid[]))
|
||
AND ($4::text = ''
|
||
OR strpos(lower(w.artist_name), lower($4::text)) > 0
|
||
OR strpos(lower(w.title), lower($4::text)) > 0)
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), w.artist_name)), w.artist_mbid,
|
||
NULLIF(COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, ''), '') NULLS LAST,
|
||
lower(w.title), w.id
|
||
LIMIT $6 OFFSET $5
|
||
`
|
||
|
||
type ListWantsParams struct {
|
||
State string
|
||
ArtistMbid string
|
||
WantIds []pgtype.UUID
|
||
Q string
|
||
Skip int32
|
||
MaxRows int32
|
||
}
|
||
|
||
type ListWantsRow struct {
|
||
ID pgtype.UUID
|
||
ReleaseGroupMbid string
|
||
ArtistMbid string
|
||
ArtistName string
|
||
Title string
|
||
Reason string
|
||
AlbumID pgtype.UUID
|
||
WantedAt pgtype.Timestamptz
|
||
FulfilledAt pgtype.Timestamptz
|
||
Upgrade string
|
||
HeldQuality string
|
||
PrimaryType string
|
||
SecondaryTypes []string
|
||
FirstReleaseDate string
|
||
RequestedBy *string
|
||
LibraryArtistID pgtype.UUID
|
||
Total int64
|
||
SearchedAt pgtype.Timestamptz
|
||
SearchOutcome string
|
||
SearchConsidered int32
|
||
BestRelease string
|
||
BestVerdict string
|
||
SearchError string
|
||
GrabID pgtype.UUID
|
||
GrabName *string
|
||
GrabbedAt pgtype.Timestamptz
|
||
GrabOrigin *string
|
||
GrabQuality *string
|
||
GrabEndedAt pgtype.Timestamptz
|
||
GrabEndReason *string
|
||
GrabProgress *float32
|
||
GrabImportState *string
|
||
GrabImportReason *string
|
||
GrabEndDetail *string
|
||
GrabImportedAt pgtype.Timestamptz
|
||
GrabImportedFiles *int32
|
||
}
|
||
|
||
// The Wanted page: open wants (all but missing ones, or only missing ones),
|
||
// the held ones, or all of them; artist_mbid narrows to one artist's page,
|
||
// want_ids to given wants. Ordered like the library (#5572): artists A–Z by the
|
||
// library's sort name when it holds the artist, each artist's albums by
|
||
// release date, so the page groups them as they arrive. q narrows to an
|
||
// artist or album title; total counts every match, past the page.
|
||
func (q *Queries) ListWants(ctx context.Context, arg ListWantsParams) ([]ListWantsRow, error) {
|
||
rows, err := q.db.Query(ctx, listWants,
|
||
arg.State,
|
||
arg.ArtistMbid,
|
||
arg.WantIds,
|
||
arg.Q,
|
||
arg.Skip,
|
||
arg.MaxRows,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []ListWantsRow
|
||
for rows.Next() {
|
||
var i ListWantsRow
|
||
if err := rows.Scan(
|
||
&i.ID,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Title,
|
||
&i.Reason,
|
||
&i.AlbumID,
|
||
&i.WantedAt,
|
||
&i.FulfilledAt,
|
||
&i.Upgrade,
|
||
&i.HeldQuality,
|
||
&i.PrimaryType,
|
||
&i.SecondaryTypes,
|
||
&i.FirstReleaseDate,
|
||
&i.RequestedBy,
|
||
&i.LibraryArtistID,
|
||
&i.Total,
|
||
&i.SearchedAt,
|
||
&i.SearchOutcome,
|
||
&i.SearchConsidered,
|
||
&i.BestRelease,
|
||
&i.BestVerdict,
|
||
&i.SearchError,
|
||
&i.GrabID,
|
||
&i.GrabName,
|
||
&i.GrabbedAt,
|
||
&i.GrabOrigin,
|
||
&i.GrabQuality,
|
||
&i.GrabEndedAt,
|
||
&i.GrabEndReason,
|
||
&i.GrabProgress,
|
||
&i.GrabImportState,
|
||
&i.GrabImportReason,
|
||
&i.GrabEndDetail,
|
||
&i.GrabImportedAt,
|
||
&i.GrabImportedFiles,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const markFollowChecked = `-- name: MarkFollowChecked :exec
|
||
UPDATE follows
|
||
SET checked_at = now(),
|
||
baseline_at = COALESCE(baseline_at, now()),
|
||
want_existing = false,
|
||
updated_at = now()
|
||
WHERE artist_mbid = $1
|
||
`
|
||
|
||
func (q *Queries) MarkFollowChecked(ctx context.Context, artistMbid string) error {
|
||
_, err := q.db.Exec(ctx, markFollowChecked, artistMbid)
|
||
return err
|
||
}
|
||
|
||
const markGroupSeen = `-- name: MarkGroupSeen :exec
|
||
INSERT INTO follow_seen_groups (artist_mbid, release_group_mbid)
|
||
VALUES ($1, $2)
|
||
ON CONFLICT DO NOTHING
|
||
`
|
||
|
||
type MarkGroupSeenParams struct {
|
||
ArtistMbid string
|
||
ReleaseGroupMbid string
|
||
}
|
||
|
||
func (q *Queries) MarkGroupSeen(ctx context.Context, arg MarkGroupSeenParams) error {
|
||
_, err := q.db.Exec(ctx, markGroupSeen, arg.ArtistMbid, arg.ReleaseGroupMbid)
|
||
return err
|
||
}
|
||
|
||
const recordAlbumMatch = `-- name: RecordAlbumMatch :exec
|
||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail, matcher)
|
||
VALUES ($1, $2, $3, $4, $5,
|
||
$6)
|
||
ON CONFLICT (album_id) DO UPDATE
|
||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method, release_group_mbid = EXCLUDED.release_group_mbid,
|
||
detail = EXCLUDED.detail, matcher = EXCLUDED.matcher, decided_at = now()
|
||
`
|
||
|
||
type RecordAlbumMatchParams struct {
|
||
AlbumID pgtype.UUID
|
||
Outcome string
|
||
Method string
|
||
ReleaseGroupMbid string
|
||
Detail string
|
||
Matcher int32
|
||
}
|
||
|
||
func (q *Queries) RecordAlbumMatch(ctx context.Context, arg RecordAlbumMatchParams) error {
|
||
_, err := q.db.Exec(ctx, recordAlbumMatch,
|
||
arg.AlbumID,
|
||
arg.Outcome,
|
||
arg.Method,
|
||
arg.ReleaseGroupMbid,
|
||
arg.Detail,
|
||
arg.Matcher,
|
||
)
|
||
return err
|
||
}
|
||
|
||
const recordListedAlbums = `-- name: RecordListedAlbums :exec
|
||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid)
|
||
SELECT a.id, 'listed', 'tags', a.release_group_mbid
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
|
||
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
|
||
AND a.created_at <= $1::timestamptz
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
ON CONFLICT (album_id) DO NOTHING
|
||
`
|
||
|
||
// The albums ListLibraryAlbums takes are decided as listed by their tags,
|
||
// so the matching that follows leaves them alone (#5569).
|
||
func (q *Queries) RecordListedAlbums(ctx context.Context, cutoff pgtype.Timestamptz) error {
|
||
_, err := q.db.Exec(ctx, recordListedAlbums, cutoff)
|
||
return err
|
||
}
|
||
|
||
const recountLibraryUnlisted = `-- name: RecountLibraryUnlisted :exec
|
||
UPDATE acquisition_settings
|
||
SET library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
|
||
WHERE m.outcome = 'unmatched'
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
|
||
WHERE id = true
|
||
`
|
||
|
||
// After a person matches or leaves off an album, the count the catalogue
|
||
// shows.
|
||
func (q *Queries) RecountLibraryUnlisted(ctx context.Context) error {
|
||
_, err := q.db.Exec(ctx, recountLibraryUnlisted)
|
||
return err
|
||
}
|
||
|
||
const repointWant = `-- name: RepointWant :exec
|
||
UPDATE wants SET release_group_mbid = $2, updated_at = now() WHERE id = $1
|
||
`
|
||
|
||
type RepointWantParams struct {
|
||
ID pgtype.UUID
|
||
ReleaseGroupMbid string
|
||
}
|
||
|
||
// A want recorded under a release id before #5241 is moved to its group.
|
||
func (q *Queries) RepointWant(ctx context.Context, arg RepointWantParams) error {
|
||
_, err := q.db.Exec(ctx, repointWant, arg.ID, arg.ReleaseGroupMbid)
|
||
return err
|
||
}
|
||
|
||
const setAlbumMatchOutcome = `-- name: SetAlbumMatchOutcome :execrows
|
||
UPDATE library_album_matches SET outcome = $1, decided_at = now()
|
||
WHERE album_id = $2 AND outcome = $3
|
||
`
|
||
|
||
type SetAlbumMatchOutcomeParams struct {
|
||
Outcome string
|
||
AlbumID pgtype.UUID
|
||
Was string
|
||
}
|
||
|
||
// Leave an unmatched album off the list, or put a left-off one back.
|
||
func (q *Queries) SetAlbumMatchOutcome(ctx context.Context, arg SetAlbumMatchOutcomeParams) (int64, error) {
|
||
result, err := q.db.Exec(ctx, setAlbumMatchOutcome, arg.Outcome, arg.AlbumID, arg.Was)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const setAlbumReleaseGroup = `-- name: SetAlbumReleaseGroup :exec
|
||
UPDATE albums SET release_group_mbid = $1, updated_at = now()
|
||
WHERE id = $2 AND (release_group_mbid IS NULL OR release_group_mbid = '')
|
||
`
|
||
|
||
type SetAlbumReleaseGroupParams struct {
|
||
ReleaseGroupMbid *string
|
||
ID pgtype.UUID
|
||
}
|
||
|
||
// Names a matched album's release group, filling only what nothing named.
|
||
func (q *Queries) SetAlbumReleaseGroup(ctx context.Context, arg SetAlbumReleaseGroupParams) error {
|
||
_, err := q.db.Exec(ctx, setAlbumReleaseGroup, arg.ReleaseGroupMbid, arg.ID)
|
||
return err
|
||
}
|
||
|
||
const setDefaultReleaseTypeProfile = `-- name: SetDefaultReleaseTypeProfile :execrows
|
||
UPDATE release_type_profiles SET is_default = true, updated_at = now() WHERE id = $1
|
||
`
|
||
|
||
func (q *Queries) SetDefaultReleaseTypeProfile(ctx context.Context, id pgtype.UUID) (int64, error) {
|
||
result, err := q.db.Exec(ctx, setDefaultReleaseTypeProfile, id)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return result.RowsAffected(), nil
|
||
}
|
||
|
||
const setWantUpgrades = `-- name: SetWantUpgrades :exec
|
||
UPDATE wants w
|
||
SET upgrade = v.upgrade,
|
||
held_tier = v.held_tier,
|
||
held_quality = v.held_quality,
|
||
upgrade_checked_at = now(),
|
||
updated_at = CASE WHEN w.upgrade <> v.upgrade THEN now() ELSE w.updated_at END
|
||
FROM (SELECT unnest($1::uuid[]) AS id,
|
||
unnest($2::text[]) AS upgrade,
|
||
unnest($3::text[]) AS held_tier,
|
||
unnest($4::text[]) AS held_quality) v
|
||
WHERE w.id = v.id
|
||
`
|
||
|
||
type SetWantUpgradesParams struct {
|
||
Ids []pgtype.UUID
|
||
Upgrades []string
|
||
HeldTiers []string
|
||
HeldQualities []string
|
||
}
|
||
|
||
// Writes the upgrade check's verdicts, one row per want.
|
||
func (q *Queries) SetWantUpgrades(ctx context.Context, arg SetWantUpgradesParams) error {
|
||
_, err := q.db.Exec(ctx, setWantUpgrades,
|
||
arg.Ids,
|
||
arg.Upgrades,
|
||
arg.HeldTiers,
|
||
arg.HeldQualities,
|
||
)
|
||
return err
|
||
}
|
||
|
||
const startLibraryListing = `-- name: StartLibraryListing :one
|
||
UPDATE acquisition_settings
|
||
SET library_listing_started_at = COALESCE(library_listing_started_at, now())
|
||
WHERE id = true
|
||
RETURNING library_listing_started_at
|
||
`
|
||
|
||
// Marks the listing begun, once; its time is the cutoff for which albums
|
||
// count as the library's.
|
||
func (q *Queries) StartLibraryListing(ctx context.Context) (pgtype.Timestamptz, error) {
|
||
row := q.db.QueryRow(ctx, startLibraryListing)
|
||
var library_listing_started_at pgtype.Timestamptz
|
||
err := row.Scan(&library_listing_started_at)
|
||
return library_listing_started_at, err
|
||
}
|
||
|
||
const updateReleaseTypeProfile = `-- name: UpdateReleaseTypeProfile :one
|
||
UPDATE release_type_profiles
|
||
SET name = $2,
|
||
primary_types = $3,
|
||
secondary_types = $4,
|
||
updated_at = now()
|
||
WHERE id = $1
|
||
RETURNING id, name, primary_types, secondary_types, is_default, created_at, updated_at
|
||
`
|
||
|
||
type UpdateReleaseTypeProfileParams struct {
|
||
ID pgtype.UUID
|
||
Name string
|
||
PrimaryTypes []string
|
||
SecondaryTypes []string
|
||
}
|
||
|
||
func (q *Queries) UpdateReleaseTypeProfile(ctx context.Context, arg UpdateReleaseTypeProfileParams) (ReleaseTypeProfile, error) {
|
||
row := q.db.QueryRow(ctx, updateReleaseTypeProfile,
|
||
arg.ID,
|
||
arg.Name,
|
||
arg.PrimaryTypes,
|
||
arg.SecondaryTypes,
|
||
)
|
||
var i ReleaseTypeProfile
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.Name,
|
||
&i.PrimaryTypes,
|
||
&i.SecondaryTypes,
|
||
&i.IsDefault,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const upsertFollow = `-- name: UpsertFollow :one
|
||
INSERT INTO follows (artist_mbid, artist_name, profile_id, followed_by, want_existing)
|
||
VALUES ($1, $2, $3,
|
||
$4, $5)
|
||
ON CONFLICT (artist_mbid) DO UPDATE
|
||
SET artist_name = EXCLUDED.artist_name,
|
||
profile_id = EXCLUDED.profile_id,
|
||
want_existing = follows.want_existing OR EXCLUDED.want_existing,
|
||
baseline_at = CASE WHEN EXCLUDED.want_existing THEN NULL ELSE follows.baseline_at END,
|
||
updated_at = now()
|
||
RETURNING artist_mbid, artist_name, profile_id, followed_by, followed_at, baseline_at, want_existing, checked_at, updated_at
|
||
`
|
||
|
||
type UpsertFollowParams struct {
|
||
ArtistMbid string
|
||
ArtistName string
|
||
ProfileID pgtype.UUID
|
||
FollowedBy pgtype.UUID
|
||
WantExisting bool
|
||
}
|
||
|
||
// Following an artist already followed changes its profile. want_existing
|
||
// only ever turns on here; the worker turns it off once applied, and turning
|
||
// it on again re-reads the baseline so the existing releases are judged.
|
||
func (q *Queries) UpsertFollow(ctx context.Context, arg UpsertFollowParams) (Follow, error) {
|
||
row := q.db.QueryRow(ctx, upsertFollow,
|
||
arg.ArtistMbid,
|
||
arg.ArtistName,
|
||
arg.ProfileID,
|
||
arg.FollowedBy,
|
||
arg.WantExisting,
|
||
)
|
||
var i Follow
|
||
err := row.Scan(
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.ProfileID,
|
||
&i.FollowedBy,
|
||
&i.FollowedAt,
|
||
&i.BaselineAt,
|
||
&i.WantExisting,
|
||
&i.CheckedAt,
|
||
&i.UpdatedAt,
|
||
)
|
||
return i, err
|
||
}
|
||
|
||
const wantHoldings = `-- name: WantHoldings :many
|
||
SELECT a.id AS album_id,
|
||
COUNT(*) FILTER (WHERE t.missing_since IS NULL)::int AS present,
|
||
COALESCE(bool_or(t.missing_since IS NULL AND t.added_at > $1::timestamptz), false)::bool AS arrived_since,
|
||
COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < $1::timestamptz), false)::bool AS missing_before,
|
||
-- Missing tracks no track on disk stands in for, by position or
|
||
-- title. A missing row whose song is back at a new path (#5245) is
|
||
-- covered; a lost bonus track is not.
|
||
COUNT(*) FILTER (WHERE t.missing_since IS NOT NULL AND NOT EXISTS (
|
||
SELECT 1 FROM tracks p
|
||
WHERE p.album_id = a.id AND p.missing_since IS NULL
|
||
AND ((p.track_number = t.track_number AND p.disc_number IS NOT DISTINCT FROM t.disc_number)
|
||
OR lower(p.title) = lower(t.title))))::int AS uncovered_missing
|
||
FROM albums a
|
||
JOIN tracks t ON t.album_id = a.id
|
||
WHERE a.release_group_mbid = $2::text
|
||
OR a.mbid = $2
|
||
OR a.id = $3
|
||
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = $2)
|
||
GROUP BY a.id
|
||
ORDER BY present DESC, a.id
|
||
`
|
||
|
||
type WantHoldingsParams struct {
|
||
Since pgtype.Timestamptz
|
||
GroupMbid string
|
||
AlbumID pgtype.UUID
|
||
}
|
||
|
||
type WantHoldingsRow struct {
|
||
AlbumID pgtype.UUID
|
||
Present int32
|
||
ArrivedSince bool
|
||
MissingBefore bool
|
||
UncoveredMissing int32
|
||
}
|
||
|
||
// Each library album that may answer a want, with what it has on disk:
|
||
// tracks present, whether one arrived since the want, and whether a track
|
||
// missing before the want is still missing.
|
||
func (q *Queries) WantHoldings(ctx context.Context, arg WantHoldingsParams) ([]WantHoldingsRow, error) {
|
||
rows, err := q.db.Query(ctx, wantHoldings, arg.Since, arg.GroupMbid, arg.AlbumID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []WantHoldingsRow
|
||
for rows.Next() {
|
||
var i WantHoldingsRow
|
||
if err := rows.Scan(
|
||
&i.AlbumID,
|
||
&i.Present,
|
||
&i.ArrivedSince,
|
||
&i.MissingBefore,
|
||
&i.UncoveredMissing,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const wantReleaseGroup = `-- name: WantReleaseGroup :one
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type,
|
||
first_release_date, reason, created_by)
|
||
VALUES ($1, $2, $3, $4,
|
||
$5, $6, $7, $8)
|
||
ON CONFLICT (release_group_mbid) DO UPDATE SET updated_at = wants.updated_at
|
||
RETURNING id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error, upgrade, held_tier, held_quality, upgrade_checked_at
|
||
`
|
||
|
||
type WantReleaseGroupParams struct {
|
||
ReleaseGroupMbid string
|
||
ArtistMbid string
|
||
ArtistName string
|
||
Title string
|
||
PrimaryType string
|
||
FirstReleaseDate string
|
||
Reason string
|
||
CreatedBy pgtype.UUID
|
||
}
|
||
|
||
// Wants a release group, or returns the want already recorded for it. A
|
||
// held want is not reopened here: the library has it.
|
||
func (q *Queries) WantReleaseGroup(ctx context.Context, arg WantReleaseGroupParams) (Want, error) {
|
||
row := q.db.QueryRow(ctx, wantReleaseGroup,
|
||
arg.ReleaseGroupMbid,
|
||
arg.ArtistMbid,
|
||
arg.ArtistName,
|
||
arg.Title,
|
||
arg.PrimaryType,
|
||
arg.FirstReleaseDate,
|
||
arg.Reason,
|
||
arg.CreatedBy,
|
||
)
|
||
var i Want
|
||
err := row.Scan(
|
||
&i.ID,
|
||
&i.ReleaseGroupMbid,
|
||
&i.ArtistMbid,
|
||
&i.ArtistName,
|
||
&i.Title,
|
||
&i.PrimaryType,
|
||
&i.FirstReleaseDate,
|
||
&i.Reason,
|
||
&i.AlbumID,
|
||
&i.CreatedBy,
|
||
&i.WantedAt,
|
||
&i.FulfilledAt,
|
||
&i.CreatedAt,
|
||
&i.UpdatedAt,
|
||
&i.SearchedAt,
|
||
&i.SearchOutcome,
|
||
&i.SearchConsidered,
|
||
&i.BestRelease,
|
||
&i.BestVerdict,
|
||
&i.SearchError,
|
||
&i.Upgrade,
|
||
&i.HeldTier,
|
||
&i.HeldQuality,
|
||
&i.UpgradeCheckedAt,
|
||
)
|
||
return i, err
|
||
}
|