feat: a wanted list — follow artists on release-type profiles; requests and re-acquisition become wants (M502 #5520)
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
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
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>
This commit is contained in:
+105
-77
@@ -99,92 +99,92 @@ func (ns NullLidarrQuarantineReason) Value() (driver.Value, error) {
|
||||
return string(ns.LidarrQuarantineReason), nil
|
||||
}
|
||||
|
||||
type LidarrRequestKind string
|
||||
type RequestKind string
|
||||
|
||||
const (
|
||||
LidarrRequestKindArtist LidarrRequestKind = "artist"
|
||||
LidarrRequestKindAlbum LidarrRequestKind = "album"
|
||||
LidarrRequestKindTrack LidarrRequestKind = "track"
|
||||
RequestKindArtist RequestKind = "artist"
|
||||
RequestKindAlbum RequestKind = "album"
|
||||
RequestKindTrack RequestKind = "track"
|
||||
)
|
||||
|
||||
func (e *LidarrRequestKind) Scan(src interface{}) error {
|
||||
func (e *RequestKind) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrRequestKind(s)
|
||||
*e = RequestKind(s)
|
||||
case string:
|
||||
*e = LidarrRequestKind(s)
|
||||
*e = RequestKind(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrRequestKind: %T", src)
|
||||
return fmt.Errorf("unsupported scan type for RequestKind: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrRequestKind struct {
|
||||
LidarrRequestKind LidarrRequestKind
|
||||
Valid bool // Valid is true if LidarrRequestKind is not NULL
|
||||
type NullRequestKind struct {
|
||||
RequestKind RequestKind
|
||||
Valid bool // Valid is true if RequestKind is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrRequestKind) Scan(value interface{}) error {
|
||||
func (ns *NullRequestKind) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrRequestKind, ns.Valid = "", false
|
||||
ns.RequestKind, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrRequestKind.Scan(value)
|
||||
return ns.RequestKind.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrRequestKind) Value() (driver.Value, error) {
|
||||
func (ns NullRequestKind) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrRequestKind), nil
|
||||
return string(ns.RequestKind), nil
|
||||
}
|
||||
|
||||
type LidarrRequestStatus string
|
||||
type RequestStatus string
|
||||
|
||||
const (
|
||||
LidarrRequestStatusPending LidarrRequestStatus = "pending"
|
||||
LidarrRequestStatusApproved LidarrRequestStatus = "approved"
|
||||
LidarrRequestStatusRejected LidarrRequestStatus = "rejected"
|
||||
LidarrRequestStatusCompleted LidarrRequestStatus = "completed"
|
||||
LidarrRequestStatusFailed LidarrRequestStatus = "failed"
|
||||
RequestStatusPending RequestStatus = "pending"
|
||||
RequestStatusApproved RequestStatus = "approved"
|
||||
RequestStatusRejected RequestStatus = "rejected"
|
||||
RequestStatusCompleted RequestStatus = "completed"
|
||||
RequestStatusFailed RequestStatus = "failed"
|
||||
)
|
||||
|
||||
func (e *LidarrRequestStatus) Scan(src interface{}) error {
|
||||
func (e *RequestStatus) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrRequestStatus(s)
|
||||
*e = RequestStatus(s)
|
||||
case string:
|
||||
*e = LidarrRequestStatus(s)
|
||||
*e = RequestStatus(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrRequestStatus: %T", src)
|
||||
return fmt.Errorf("unsupported scan type for RequestStatus: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrRequestStatus struct {
|
||||
LidarrRequestStatus LidarrRequestStatus
|
||||
Valid bool // Valid is true if LidarrRequestStatus is not NULL
|
||||
type NullRequestStatus struct {
|
||||
RequestStatus RequestStatus
|
||||
Valid bool // Valid is true if RequestStatus is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrRequestStatus) Scan(value interface{}) error {
|
||||
func (ns *NullRequestStatus) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrRequestStatus, ns.Valid = "", false
|
||||
ns.RequestStatus, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrRequestStatus.Scan(value)
|
||||
return ns.RequestStatus.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrRequestStatus) Value() (driver.Value, error) {
|
||||
func (ns NullRequestStatus) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrRequestStatus), nil
|
||||
return string(ns.RequestStatus), nil
|
||||
}
|
||||
|
||||
type AcoustidSetting struct {
|
||||
@@ -384,6 +384,24 @@ type FingerprintSetting struct {
|
||||
AutoResolve bool
|
||||
}
|
||||
|
||||
type Follow 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
|
||||
}
|
||||
|
||||
type FollowSeenGroup struct {
|
||||
ArtistMbid string
|
||||
ReleaseGroupMbid string
|
||||
SeenAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type GeneralLike struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
@@ -443,31 +461,6 @@ type LidarrQuarantineAction struct {
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrRequest struct {
|
||||
ID pgtype.UUID
|
||||
UserID pgtype.UUID
|
||||
Status LidarrRequestStatus
|
||||
Kind LidarrRequestKind
|
||||
LidarrArtistMbid string
|
||||
LidarrAlbumMbid *string
|
||||
LidarrTrackMbid *string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
TrackTitle *string
|
||||
QualityProfileID *int32
|
||||
RootFolderPath *string
|
||||
DecidedAt pgtype.Timestamptz
|
||||
DecidedBy pgtype.UUID
|
||||
Notes *string
|
||||
CompletedAt pgtype.Timestamptz
|
||||
MatchedTrackID pgtype.UUID
|
||||
MatchedAlbumID pgtype.UUID
|
||||
MatchedArtistID pgtype.UUID
|
||||
RequestedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
LidarrAddConfirmedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type ListenbrainzSimilarRecording struct {
|
||||
SeedTrackID pgtype.UUID
|
||||
RecordingMbid string
|
||||
@@ -535,16 +528,6 @@ type MbReleaseTrack struct {
|
||||
RecordingMbid string
|
||||
}
|
||||
|
||||
type MissingReacquisition struct {
|
||||
AlbumID pgtype.UUID
|
||||
Attempts int32
|
||||
LastAttemptAt pgtype.Timestamptz
|
||||
LastRequestID pgtype.UUID
|
||||
GaveUpAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type NetworkSetting struct {
|
||||
ID bool
|
||||
TrustedProxyHops int32
|
||||
@@ -636,14 +619,10 @@ type PlaylistTrack struct {
|
||||
}
|
||||
|
||||
type ReacquisitionSetting struct {
|
||||
ID bool
|
||||
Enabled bool
|
||||
GraceHours int32
|
||||
BackoffBaseHours int32
|
||||
BackoffMaxHours int32
|
||||
MaxAttempts int32
|
||||
MaxPerPass int32
|
||||
AutoApprove bool
|
||||
ID bool
|
||||
Enabled bool
|
||||
GraceHours int32
|
||||
MaxPerPass int32
|
||||
}
|
||||
|
||||
type RecommendationTuningAudit struct {
|
||||
@@ -673,6 +652,38 @@ type RegistrationSetting struct {
|
||||
Mode string
|
||||
}
|
||||
|
||||
type ReleaseTypeProfile struct {
|
||||
ID pgtype.UUID
|
||||
Name string
|
||||
PrimaryTypes []string
|
||||
SecondaryTypes []string
|
||||
IsDefault bool
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ID pgtype.UUID
|
||||
UserID pgtype.UUID
|
||||
Status RequestStatus
|
||||
Kind RequestKind
|
||||
ArtistMbid string
|
||||
AlbumMbid *string
|
||||
TrackMbid *string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
TrackTitle *string
|
||||
DecidedAt pgtype.Timestamptz
|
||||
DecidedBy pgtype.UUID
|
||||
Notes *string
|
||||
CompletedAt pgtype.Timestamptz
|
||||
MatchedTrackID pgtype.UUID
|
||||
MatchedAlbumID pgtype.UUID
|
||||
MatchedArtistID pgtype.UUID
|
||||
RequestedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type ScanRun struct {
|
||||
ID pgtype.UUID
|
||||
StartedAt pgtype.Timestamptz
|
||||
@@ -950,6 +961,23 @@ type UserNotificationPref struct {
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type Want struct {
|
||||
ID pgtype.UUID
|
||||
ReleaseGroupMbid string
|
||||
ArtistMbid string
|
||||
ArtistName string
|
||||
Title string
|
||||
PrimaryType string
|
||||
FirstReleaseDate string
|
||||
Reason string
|
||||
AlbumID pgtype.UUID
|
||||
CreatedBy pgtype.UUID
|
||||
WantedAt pgtype.Timestamptz
|
||||
FulfilledAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type YouMightLikeAlbum struct {
|
||||
UserID pgtype.UUID
|
||||
AlbumID pgtype.UUID
|
||||
|
||||
Reference in New Issue
Block a user