feat: Minstrel no longer talks to Lidarr: its connection, Delete via Lidarr and the unmonitor options are gone, and the track reports are plain quarantine (M502 #5525)
release / govulncheck (push) Successful in 15s
release / web (push) Failing after 17s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
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 17s
release / go (push) Successful in 1m13s
release / integration (push) Successful in 4m56s
release / android (push) Successful in 5m9s
release / Build signed APK (releases and dev) (push) Successful in 5m19s
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
Migration 0091 drops lidarr_config and renames the quarantine tables, types, indexes and the history column off the Lidarr name. The Integrations Lidarr card, the Reports "Delete via Lidarr" action, the merge and remove-track unmonitor options, the Lidarr audit actions and error codes go, on the server, web and Android. The README describes acquisition as it is now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,11 @@ SELECT
|
||||
-- it, or it failed to play (#5606). Counted by track, as the Reports
|
||||
-- page lists them.
|
||||
(SELECT count(*) FROM (
|
||||
SELECT track_id FROM lidarr_quarantine
|
||||
SELECT track_id FROM quarantine
|
||||
UNION
|
||||
SELECT track_id FROM playback_errors WHERE resolved_at IS NULL) r)::bigint AS reports,
|
||||
(SELECT min(at) FROM (
|
||||
SELECT created_at AS at FROM lidarr_quarantine
|
||||
SELECT created_at AS at FROM quarantine
|
||||
UNION ALL
|
||||
SELECT occurred_at FROM playback_errors WHERE resolved_at IS NULL) r)::timestamptz AS reports_since,
|
||||
(SELECT count(*) FROM duplicate_groups g
|
||||
|
||||
@@ -84,8 +84,8 @@ type ListAuditLogByActionsParams struct {
|
||||
}
|
||||
|
||||
// One page of the given actions, newest first: the duplicates report's
|
||||
// "resolved automatically" activity (M498) reads the resolver's merges and
|
||||
// Lidarr release changes from here.
|
||||
// "resolved automatically" activity (M498) reads the resolver's merges from
|
||||
// here.
|
||||
func (q *Queries) ListAuditLogByActions(ctx context.Context, arg ListAuditLogByActionsParams) ([]AuditLog, error) {
|
||||
rows, err := q.db.Query(ctx, listAuditLogByActions,
|
||||
arg.Actions,
|
||||
|
||||
@@ -28,7 +28,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE ml.user_id = $1 AND ml.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.album_id, t.artist_id
|
||||
@@ -108,7 +108,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE gl.user_id = $1 AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -173,7 +173,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE gl.user_id = $1 AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -237,7 +237,7 @@ WITH scored AS (
|
||||
WHERE gl.user_id = $3 AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $3 AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.album_id, t.artist_id
|
||||
|
||||
@@ -24,7 +24,7 @@ SELECT pe.id AS event_id,
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.was_skipped = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY pe.started_at DESC
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: lidarr_config.sql
|
||||
|
||||
package dbq
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const getLidarrConfig = `-- name: GetLidarrConfig :one
|
||||
SELECT id, enabled, base_url, api_key, default_quality_profile_id,
|
||||
default_metadata_profile_id, default_root_folder_path,
|
||||
created_at, updated_at
|
||||
FROM lidarr_config
|
||||
WHERE id = 1
|
||||
`
|
||||
|
||||
type GetLidarrConfigRow struct {
|
||||
ID int16
|
||||
Enabled bool
|
||||
BaseUrl *string
|
||||
ApiKey *string
|
||||
DefaultQualityProfileID *int32
|
||||
DefaultMetadataProfileID *int32
|
||||
DefaultRootFolderPath *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
func (q *Queries) GetLidarrConfig(ctx context.Context) (GetLidarrConfigRow, error) {
|
||||
row := q.db.QueryRow(ctx, getLidarrConfig)
|
||||
var i GetLidarrConfigRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Enabled,
|
||||
&i.BaseUrl,
|
||||
&i.ApiKey,
|
||||
&i.DefaultQualityProfileID,
|
||||
&i.DefaultMetadataProfileID,
|
||||
&i.DefaultRootFolderPath,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateLidarrConfig = `-- name: UpdateLidarrConfig :one
|
||||
UPDATE lidarr_config
|
||||
SET enabled = $1,
|
||||
base_url = $2,
|
||||
api_key = $3,
|
||||
default_quality_profile_id = $4,
|
||||
default_metadata_profile_id = $5,
|
||||
default_root_folder_path = $6,
|
||||
updated_at = now()
|
||||
WHERE id = 1
|
||||
RETURNING id, enabled, base_url, api_key, default_quality_profile_id,
|
||||
default_metadata_profile_id, default_root_folder_path,
|
||||
created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateLidarrConfigParams struct {
|
||||
Enabled bool
|
||||
BaseUrl *string
|
||||
ApiKey *string
|
||||
DefaultQualityProfileID *int32
|
||||
DefaultMetadataProfileID *int32
|
||||
DefaultRootFolderPath *string
|
||||
}
|
||||
|
||||
type UpdateLidarrConfigRow struct {
|
||||
ID int16
|
||||
Enabled bool
|
||||
BaseUrl *string
|
||||
ApiKey *string
|
||||
DefaultQualityProfileID *int32
|
||||
DefaultMetadataProfileID *int32
|
||||
DefaultRootFolderPath *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateLidarrConfig(ctx context.Context, arg UpdateLidarrConfigParams) (UpdateLidarrConfigRow, error) {
|
||||
row := q.db.QueryRow(ctx, updateLidarrConfig,
|
||||
arg.Enabled,
|
||||
arg.BaseUrl,
|
||||
arg.ApiKey,
|
||||
arg.DefaultQualityProfileID,
|
||||
arg.DefaultMetadataProfileID,
|
||||
arg.DefaultRootFolderPath,
|
||||
)
|
||||
var i UpdateLidarrConfigRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Enabled,
|
||||
&i.BaseUrl,
|
||||
&i.ApiKey,
|
||||
&i.DefaultQualityProfileID,
|
||||
&i.DefaultMetadataProfileID,
|
||||
&i.DefaultRootFolderPath,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -12,11 +12,9 @@ import (
|
||||
)
|
||||
|
||||
const listDuplicateGroupMergeMembers = `-- name: ListDuplicateGroupMergeMembers :many
|
||||
SELECT t.id, t.file_path, t.file_format, t.file_size, t.added_at, t.album_id,
|
||||
t.mbid, albums.mbid AS album_mbid
|
||||
SELECT t.id, t.file_path, t.file_format, t.file_size, t.added_at, t.album_id
|
||||
FROM duplicate_group_members m
|
||||
JOIN tracks t ON t.id = m.track_id
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN tracks t ON t.id = m.track_id
|
||||
WHERE m.group_id = $1
|
||||
ORDER BY t.id
|
||||
`
|
||||
@@ -28,8 +26,6 @@ type ListDuplicateGroupMergeMembersRow struct {
|
||||
FileSize int64
|
||||
AddedAt pgtype.Timestamptz
|
||||
AlbumID pgtype.UUID
|
||||
Mbid *string
|
||||
AlbumMbid *string
|
||||
}
|
||||
|
||||
func (q *Queries) ListDuplicateGroupMergeMembers(ctx context.Context, groupID pgtype.UUID) ([]ListDuplicateGroupMergeMembersRow, error) {
|
||||
@@ -48,8 +44,6 @@ func (q *Queries) ListDuplicateGroupMergeMembers(ctx context.Context, groupID pg
|
||||
&i.FileSize,
|
||||
&i.AddedAt,
|
||||
&i.AlbumID,
|
||||
&i.Mbid,
|
||||
&i.AlbumMbid,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ type ListMissingTrackPairsRow struct {
|
||||
}
|
||||
|
||||
// Missing-pair pass (M485). A track marked missing whose replacement is already
|
||||
// on disk under another row — most often a Lidarr quality upgrade, mp3 → flac at
|
||||
// a new path — is folded into that replacement, so its history moves across and
|
||||
// on disk under another row — most often a quality upgrade, mp3 → flac at a new
|
||||
// path — is folded into that replacement, so its history moves across and
|
||||
// re-acquisition stops chasing music the library already has.
|
||||
//
|
||||
// Move adoption (#2528) cannot catch these: it links a new file to a missing row
|
||||
|
||||
+55
-67
@@ -11,92 +11,92 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type LidarrQuarantineActionKind string
|
||||
type QuarantineActionKind string
|
||||
|
||||
const (
|
||||
LidarrQuarantineActionKindResolved LidarrQuarantineActionKind = "resolved"
|
||||
LidarrQuarantineActionKindDeletedFile LidarrQuarantineActionKind = "deleted_file"
|
||||
LidarrQuarantineActionKindDeletedViaLidarr LidarrQuarantineActionKind = "deleted_via_lidarr"
|
||||
QuarantineActionKindResolved QuarantineActionKind = "resolved"
|
||||
QuarantineActionKindDeletedFile QuarantineActionKind = "deleted_file"
|
||||
QuarantineActionKindDeletedAlbum QuarantineActionKind = "deleted_album"
|
||||
)
|
||||
|
||||
func (e *LidarrQuarantineActionKind) Scan(src interface{}) error {
|
||||
func (e *QuarantineActionKind) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrQuarantineActionKind(s)
|
||||
*e = QuarantineActionKind(s)
|
||||
case string:
|
||||
*e = LidarrQuarantineActionKind(s)
|
||||
*e = QuarantineActionKind(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrQuarantineActionKind: %T", src)
|
||||
return fmt.Errorf("unsupported scan type for QuarantineActionKind: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrQuarantineActionKind struct {
|
||||
LidarrQuarantineActionKind LidarrQuarantineActionKind
|
||||
Valid bool // Valid is true if LidarrQuarantineActionKind is not NULL
|
||||
type NullQuarantineActionKind struct {
|
||||
QuarantineActionKind QuarantineActionKind
|
||||
Valid bool // Valid is true if QuarantineActionKind is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrQuarantineActionKind) Scan(value interface{}) error {
|
||||
func (ns *NullQuarantineActionKind) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrQuarantineActionKind, ns.Valid = "", false
|
||||
ns.QuarantineActionKind, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrQuarantineActionKind.Scan(value)
|
||||
return ns.QuarantineActionKind.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrQuarantineActionKind) Value() (driver.Value, error) {
|
||||
func (ns NullQuarantineActionKind) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrQuarantineActionKind), nil
|
||||
return string(ns.QuarantineActionKind), nil
|
||||
}
|
||||
|
||||
type LidarrQuarantineReason string
|
||||
type QuarantineReason string
|
||||
|
||||
const (
|
||||
LidarrQuarantineReasonBadRip LidarrQuarantineReason = "bad_rip"
|
||||
LidarrQuarantineReasonWrongFile LidarrQuarantineReason = "wrong_file"
|
||||
LidarrQuarantineReasonWrongTags LidarrQuarantineReason = "wrong_tags"
|
||||
LidarrQuarantineReasonDuplicate LidarrQuarantineReason = "duplicate"
|
||||
LidarrQuarantineReasonOther LidarrQuarantineReason = "other"
|
||||
QuarantineReasonBadRip QuarantineReason = "bad_rip"
|
||||
QuarantineReasonWrongFile QuarantineReason = "wrong_file"
|
||||
QuarantineReasonWrongTags QuarantineReason = "wrong_tags"
|
||||
QuarantineReasonDuplicate QuarantineReason = "duplicate"
|
||||
QuarantineReasonOther QuarantineReason = "other"
|
||||
)
|
||||
|
||||
func (e *LidarrQuarantineReason) Scan(src interface{}) error {
|
||||
func (e *QuarantineReason) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = LidarrQuarantineReason(s)
|
||||
*e = QuarantineReason(s)
|
||||
case string:
|
||||
*e = LidarrQuarantineReason(s)
|
||||
*e = QuarantineReason(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for LidarrQuarantineReason: %T", src)
|
||||
return fmt.Errorf("unsupported scan type for QuarantineReason: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullLidarrQuarantineReason struct {
|
||||
LidarrQuarantineReason LidarrQuarantineReason
|
||||
Valid bool // Valid is true if LidarrQuarantineReason is not NULL
|
||||
type NullQuarantineReason struct {
|
||||
QuarantineReason QuarantineReason
|
||||
Valid bool // Valid is true if QuarantineReason is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullLidarrQuarantineReason) Scan(value interface{}) error {
|
||||
func (ns *NullQuarantineReason) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.LidarrQuarantineReason, ns.Valid = "", false
|
||||
ns.QuarantineReason, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.LidarrQuarantineReason.Scan(value)
|
||||
return ns.QuarantineReason.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullLidarrQuarantineReason) Value() (driver.Value, error) {
|
||||
func (ns NullQuarantineReason) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.LidarrQuarantineReason), nil
|
||||
return string(ns.QuarantineReason), nil
|
||||
}
|
||||
|
||||
type RequestKind string
|
||||
@@ -506,39 +506,6 @@ type LibraryChange struct {
|
||||
ChangedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrConfig struct {
|
||||
ID int16
|
||||
Enabled bool
|
||||
BaseUrl *string
|
||||
ApiKey *string
|
||||
DefaultQualityProfileID *int32
|
||||
DefaultRootFolderPath *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
DefaultMetadataProfileID *int32
|
||||
}
|
||||
|
||||
type LidarrQuarantine struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
Reason LidarrQuarantineReason
|
||||
Notes *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type LidarrQuarantineAction struct {
|
||||
ID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action LidarrQuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
AffectedUsers int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type ListenbrainzSimilarRecording struct {
|
||||
SeedTrackID pgtype.UUID
|
||||
RecordingMbid string
|
||||
@@ -696,6 +663,27 @@ type PlaylistTrack struct {
|
||||
PickKind *string
|
||||
}
|
||||
|
||||
type Quarantine struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
Reason QuarantineReason
|
||||
Notes *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type QuarantineAction struct {
|
||||
ID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action QuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
AlbumMbid *string
|
||||
AffectedUsers int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type ReacquisitionSetting struct {
|
||||
ID bool
|
||||
Enabled bool
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: lidarr_quarantine.sql
|
||||
// source: quarantine.sql
|
||||
|
||||
package dbq
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const countQuarantineForTrack = `-- name: CountQuarantineForTrack :one
|
||||
SELECT count(*)::int FROM lidarr_quarantine WHERE track_id = $1
|
||||
SELECT count(*)::int FROM quarantine WHERE track_id = $1
|
||||
`
|
||||
|
||||
// Reads affected_users for the audit row before the delete fires.
|
||||
@@ -24,7 +24,7 @@ func (q *Queries) CountQuarantineForTrack(ctx context.Context, trackID pgtype.UU
|
||||
}
|
||||
|
||||
const deleteQuarantine = `-- name: DeleteQuarantine :one
|
||||
DELETE FROM lidarr_quarantine
|
||||
DELETE FROM quarantine
|
||||
WHERE user_id = $1 AND track_id = $2
|
||||
RETURNING user_id, track_id, reason, notes, created_at
|
||||
`
|
||||
@@ -36,9 +36,9 @@ type DeleteQuarantineParams struct {
|
||||
|
||||
// Removes the caller's row. Returns the deleted row so the handler can
|
||||
// distinguish "no row existed" (zero rows -> ErrNoRows) from success.
|
||||
func (q *Queries) DeleteQuarantine(ctx context.Context, arg DeleteQuarantineParams) (LidarrQuarantine, error) {
|
||||
func (q *Queries) DeleteQuarantine(ctx context.Context, arg DeleteQuarantineParams) (Quarantine, error) {
|
||||
row := q.db.QueryRow(ctx, deleteQuarantine, arg.UserID, arg.TrackID)
|
||||
var i LidarrQuarantine
|
||||
var i Quarantine
|
||||
err := row.Scan(
|
||||
&i.UserID,
|
||||
&i.TrackID,
|
||||
@@ -50,7 +50,7 @@ func (q *Queries) DeleteQuarantine(ctx context.Context, arg DeleteQuarantinePara
|
||||
}
|
||||
|
||||
const deleteQuarantineForTrack = `-- name: DeleteQuarantineForTrack :exec
|
||||
DELETE FROM lidarr_quarantine WHERE track_id = $1
|
||||
DELETE FROM quarantine WHERE track_id = $1
|
||||
`
|
||||
|
||||
// Clears all per-user rows for a given track. Used by Resolve and the
|
||||
@@ -68,26 +68,24 @@ SELECT
|
||||
ar.name AS artist_name,
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at)::timestamptz AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
GROUP BY t.id, ar.name, al.title, al.id, al.mbid
|
||||
GROUP BY t.id, ar.name, al.title, al.id
|
||||
ORDER BY max(q.created_at) DESC
|
||||
`
|
||||
|
||||
type ListAdminQuarantineQueueRow struct {
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle string
|
||||
AlbumID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
ReportCount int32
|
||||
LatestAt pgtype.Timestamptz
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle string
|
||||
AlbumID pgtype.UUID
|
||||
ReportCount int32
|
||||
LatestAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
// Aggregated admin queue. One row per track. The handler post-processes
|
||||
@@ -108,7 +106,6 @@ func (q *Queries) ListAdminQuarantineQueue(ctx context.Context) ([]ListAdminQuar
|
||||
&i.ArtistName,
|
||||
&i.AlbumTitle,
|
||||
&i.AlbumID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.ReportCount,
|
||||
&i.LatestAt,
|
||||
); err != nil {
|
||||
@@ -123,20 +120,20 @@ func (q *Queries) ListAdminQuarantineQueue(ctx context.Context) ([]ListAdminQuar
|
||||
}
|
||||
|
||||
const listQuarantineActions = `-- name: ListQuarantineActions :many
|
||||
SELECT id, track_id, track_title, artist_name, album_title, action, admin_id, lidarr_album_mbid, affected_users, created_at FROM lidarr_quarantine_actions
|
||||
SELECT id, track_id, track_title, artist_name, album_title, action, admin_id, album_mbid, affected_users, created_at FROM quarantine_actions
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
func (q *Queries) ListQuarantineActions(ctx context.Context, limit int32) ([]LidarrQuarantineAction, error) {
|
||||
func (q *Queries) ListQuarantineActions(ctx context.Context, limit int32) ([]QuarantineAction, error) {
|
||||
rows, err := q.db.Query(ctx, listQuarantineActions, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []LidarrQuarantineAction
|
||||
var items []QuarantineAction
|
||||
for rows.Next() {
|
||||
var i LidarrQuarantineAction
|
||||
var i QuarantineAction
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.TrackID,
|
||||
@@ -145,7 +142,7 @@ func (q *Queries) ListQuarantineActions(ctx context.Context, limit int32) ([]Lid
|
||||
&i.AlbumTitle,
|
||||
&i.Action,
|
||||
&i.AdminID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.AlbumMbid,
|
||||
&i.AffectedUsers,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
@@ -170,7 +167,7 @@ SELECT
|
||||
al.cover_art_path AS album_cover_art_path,
|
||||
ar.id AS artist_id,
|
||||
ar.name AS artist_name
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
@@ -179,7 +176,7 @@ ORDER BY q.created_at DESC
|
||||
`
|
||||
|
||||
type ListQuarantineForUserRow struct {
|
||||
LidarrQuarantine LidarrQuarantine
|
||||
Quarantine Quarantine
|
||||
TrackIDJoin pgtype.UUID
|
||||
TrackTitle string
|
||||
TrackDurationMs int32
|
||||
@@ -204,11 +201,11 @@ func (q *Queries) ListQuarantineForUser(ctx context.Context, userID pgtype.UUID)
|
||||
for rows.Next() {
|
||||
var i ListQuarantineForUserRow
|
||||
if err := rows.Scan(
|
||||
&i.LidarrQuarantine.UserID,
|
||||
&i.LidarrQuarantine.TrackID,
|
||||
&i.LidarrQuarantine.Reason,
|
||||
&i.LidarrQuarantine.Notes,
|
||||
&i.LidarrQuarantine.CreatedAt,
|
||||
&i.Quarantine.UserID,
|
||||
&i.Quarantine.TrackID,
|
||||
&i.Quarantine.Reason,
|
||||
&i.Quarantine.Notes,
|
||||
&i.Quarantine.CreatedAt,
|
||||
&i.TrackIDJoin,
|
||||
&i.TrackTitle,
|
||||
&i.TrackDurationMs,
|
||||
@@ -235,7 +232,7 @@ SELECT
|
||||
q.reason,
|
||||
q.notes,
|
||||
q.created_at
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN users u ON u.id = q.user_id
|
||||
WHERE q.track_id = $1
|
||||
ORDER BY q.created_at DESC
|
||||
@@ -244,7 +241,7 @@ ORDER BY q.created_at DESC
|
||||
type ListQuarantineReportsForTrackRow struct {
|
||||
UserID pgtype.UUID
|
||||
Username string
|
||||
Reason LidarrQuarantineReason
|
||||
Reason QuarantineReason
|
||||
Notes *string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
@@ -278,7 +275,7 @@ func (q *Queries) ListQuarantineReportsForTrack(ctx context.Context, trackID pgt
|
||||
}
|
||||
|
||||
const upsertQuarantine = `-- name: UpsertQuarantine :one
|
||||
INSERT INTO lidarr_quarantine (user_id, track_id, reason, notes)
|
||||
INSERT INTO quarantine (user_id, track_id, reason, notes)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id, track_id) DO UPDATE SET
|
||||
reason = EXCLUDED.reason,
|
||||
@@ -290,20 +287,20 @@ RETURNING user_id, track_id, reason, notes, created_at
|
||||
type UpsertQuarantineParams struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
Reason LidarrQuarantineReason
|
||||
Reason QuarantineReason
|
||||
Notes *string
|
||||
}
|
||||
|
||||
// Insert a new quarantine row, or update reason/notes if the user has
|
||||
// already flagged this track.
|
||||
func (q *Queries) UpsertQuarantine(ctx context.Context, arg UpsertQuarantineParams) (LidarrQuarantine, error) {
|
||||
func (q *Queries) UpsertQuarantine(ctx context.Context, arg UpsertQuarantineParams) (Quarantine, error) {
|
||||
row := q.db.QueryRow(ctx, upsertQuarantine,
|
||||
arg.UserID,
|
||||
arg.TrackID,
|
||||
arg.Reason,
|
||||
arg.Notes,
|
||||
)
|
||||
var i LidarrQuarantine
|
||||
var i Quarantine
|
||||
err := row.Scan(
|
||||
&i.UserID,
|
||||
&i.TrackID,
|
||||
@@ -315,26 +312,25 @@ func (q *Queries) UpsertQuarantine(ctx context.Context, arg UpsertQuarantinePara
|
||||
}
|
||||
|
||||
const writeQuarantineAction = `-- name: WriteQuarantineAction :one
|
||||
INSERT INTO lidarr_quarantine_actions (
|
||||
INSERT INTO quarantine_actions (
|
||||
track_id, track_title, artist_name, album_title,
|
||||
action, admin_id, lidarr_album_mbid, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, track_id, track_title, artist_name, album_title, action, admin_id, lidarr_album_mbid, affected_users, created_at
|
||||
action, admin_id, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, track_id, track_title, artist_name, album_title, action, admin_id, album_mbid, affected_users, created_at
|
||||
`
|
||||
|
||||
type WriteQuarantineActionParams struct {
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action LidarrQuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
LidarrAlbumMbid *string
|
||||
AffectedUsers int32
|
||||
TrackID pgtype.UUID
|
||||
TrackTitle string
|
||||
ArtistName string
|
||||
AlbumTitle *string
|
||||
Action QuarantineActionKind
|
||||
AdminID pgtype.UUID
|
||||
AffectedUsers int32
|
||||
}
|
||||
|
||||
// Audit row for an admin destructive action.
|
||||
func (q *Queries) WriteQuarantineAction(ctx context.Context, arg WriteQuarantineActionParams) (LidarrQuarantineAction, error) {
|
||||
func (q *Queries) WriteQuarantineAction(ctx context.Context, arg WriteQuarantineActionParams) (QuarantineAction, error) {
|
||||
row := q.db.QueryRow(ctx, writeQuarantineAction,
|
||||
arg.TrackID,
|
||||
arg.TrackTitle,
|
||||
@@ -342,10 +338,9 @@ func (q *Queries) WriteQuarantineAction(ctx context.Context, arg WriteQuarantine
|
||||
arg.AlbumTitle,
|
||||
arg.Action,
|
||||
arg.AdminID,
|
||||
arg.LidarrAlbumMbid,
|
||||
arg.AffectedUsers,
|
||||
)
|
||||
var i LidarrQuarantineAction
|
||||
var i QuarantineAction
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TrackID,
|
||||
@@ -354,7 +349,7 @@ func (q *Queries) WriteQuarantineAction(ctx context.Context, arg WriteQuarantine
|
||||
&i.AlbumTitle,
|
||||
&i.Action,
|
||||
&i.AdminID,
|
||||
&i.LidarrAlbumMbid,
|
||||
&i.AlbumMbid,
|
||||
&i.AffectedUsers,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
@@ -218,7 +218,7 @@ JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.artist_id = $1
|
||||
AND t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY p.cnt DESC, t.id
|
||||
@@ -304,7 +304,7 @@ JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY p.cnt DESC, t.id
|
||||
@@ -728,7 +728,7 @@ WHERE t.id <> $2
|
||||
AND started_at > now() - $3 * interval '1 hour'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
`
|
||||
@@ -827,7 +827,7 @@ excluded_ids AS (
|
||||
WHERE pe.user_id = $1 AND pe.started_at > now() - $3 * interval '1 hour'
|
||||
UNION ALL
|
||||
SELECT q.track_id AS id
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
WHERE q.user_id = $1
|
||||
),
|
||||
lb_similar AS (
|
||||
|
||||
@@ -43,7 +43,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
AND COALESCE(pc.c, 0) <= 2
|
||||
AND COALESCE(sc.c, 0) < 2
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -129,7 +129,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier
|
||||
AND alt.tier IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM attempted a WHERE a.track_id = t.id)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY
|
||||
@@ -229,7 +229,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier
|
||||
JOIN albums_tiered alt ON alt.album_id = t.album_id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY alt.tier, alt.created_at DESC, t.album_id,
|
||||
@@ -308,7 +308,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
JOIN windowed w ON w.track_id = t.id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY w.c DESC, md5(t.id::text || $2::text)
|
||||
@@ -375,7 +375,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
AND s.c >= 3
|
||||
AND s.last_at <= now() - interval '30 days'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
-- Declined-resurfacing cooldown (#1258): a skip on a
|
||||
|
||||
@@ -197,7 +197,7 @@ JOIN albums a ON a.id = t.album_id
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.started_at > now() - ($2::float8 * INTERVAL '1 day')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
`
|
||||
|
||||
@@ -46,8 +46,8 @@ UPDATE tracks
|
||||
AND NOT regexp_replace(file_path, '^.*/', '') ~* $1::text
|
||||
`
|
||||
|
||||
// A held-back track whose name no longer carries a marker (renamed, retagged
|
||||
// by Lidarr) is released again.
|
||||
// A held-back track whose name no longer carries a marker (renamed or
|
||||
// retagged) is released again.
|
||||
func (q *Queries) ClearStaleSuspectSourceTracks(ctx context.Context, pattern string) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, clearStaleSuspectSourceTracks, pattern)
|
||||
if err != nil {
|
||||
@@ -130,7 +130,7 @@ const countTracksMatching = `-- name: CountTracksMatching :one
|
||||
SELECT COUNT(*) FROM tracks
|
||||
WHERE title ILIKE '%' || $1::text || '%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
)
|
||||
`
|
||||
@@ -162,8 +162,8 @@ type DeleteTrackRow struct {
|
||||
}
|
||||
|
||||
// M7 #372: hard delete with FK cascade. The CASCADE on track_id from
|
||||
// play_events / general_likes_tracks / lidarr_quarantine /
|
||||
// lidarr_quarantine_actions handles their cleanup. RETURNING gives us
|
||||
// play_events / general_likes_tracks / quarantine /
|
||||
// quarantine_actions handles their cleanup. RETURNING gives us
|
||||
// album_id + artist_id for the album-empty / artist-empty cascade
|
||||
// checks the service does next.
|
||||
func (q *Queries) DeleteTrack(ctx context.Context, id pgtype.UUID) (DeleteTrackRow, error) {
|
||||
@@ -414,7 +414,7 @@ JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.artist_id = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY albums.release_date NULLS LAST, albums.sort_title,
|
||||
@@ -433,7 +433,7 @@ type ListArtistTracksForUserRow struct {
|
||||
}
|
||||
|
||||
// M6a: every track for the artist across their albums, with album_title
|
||||
// and artist_name joined. Honors per-user lidarr_quarantine. Used by
|
||||
// and artist_name joined. Honors per-user quarantine. Used by
|
||||
// /api/artists/{id}/tracks for the artist-card play affordance, which
|
||||
// shuffles client-side. Ordering matches album/track natural order so
|
||||
// the shuffle has a deterministic input.
|
||||
@@ -578,7 +578,7 @@ FROM tracks t
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY random()
|
||||
@@ -786,7 +786,7 @@ const listTracksByAlbum = `-- name: ListTracksByAlbum :many
|
||||
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version, missing_since, mbid_source, song_id, song_key, source_verdict, source_verdict_at FROM tracks
|
||||
WHERE album_id = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
)
|
||||
ORDER BY disc_number NULLS LAST, track_number NULLS LAST
|
||||
@@ -910,7 +910,7 @@ const searchTracks = `-- name: SearchTracks :many
|
||||
SELECT id, title, album_id, artist_id, track_number, disc_number, duration_ms, file_path, file_size, file_format, bitrate, mbid, genre, added_at, updated_at, tag_source, tag_sources_version, tag_read_version, missing_since, mbid_source, song_id, song_key, source_verdict, source_verdict_at FROM tracks
|
||||
WHERE title ILIKE '%' || $1::text || '%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
)
|
||||
ORDER BY title
|
||||
|
||||
@@ -205,7 +205,7 @@ DELETE FROM users WHERE id = $1
|
||||
// Hard delete with cascade. The schema's ON DELETE CASCADE foreign keys
|
||||
// handle plays/likes/sessions/etc. Caller must enforce the last-admin
|
||||
// guard at the HTTP layer (count admins, refuse if target is the only
|
||||
// admin). The lidarr_quarantine, general_likes*, play_events,
|
||||
// admin). The quarantine, general_likes*, play_events,
|
||||
// sessions, and other user-FK tables all have ON DELETE CASCADE so
|
||||
// this is a clean drop.
|
||||
func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
ALTER TABLE quarantine_actions RENAME CONSTRAINT quarantine_actions_admin_id_fkey TO lidarr_quarantine_actions_admin_id_fkey;
|
||||
ALTER TABLE quarantine RENAME CONSTRAINT quarantine_track_id_fkey TO lidarr_quarantine_track_id_fkey;
|
||||
ALTER TABLE quarantine RENAME CONSTRAINT quarantine_user_id_fkey TO lidarr_quarantine_user_id_fkey;
|
||||
|
||||
ALTER INDEX quarantine_actions_created_idx RENAME TO lidarr_quarantine_actions_created_idx;
|
||||
ALTER INDEX quarantine_actions_track_idx RENAME TO lidarr_quarantine_actions_track_idx;
|
||||
ALTER INDEX quarantine_actions_pkey RENAME TO lidarr_quarantine_actions_pkey;
|
||||
ALTER TABLE quarantine_actions RENAME COLUMN album_mbid TO lidarr_album_mbid;
|
||||
ALTER TYPE quarantine_action_kind RENAME VALUE 'deleted_album' TO 'deleted_via_lidarr';
|
||||
ALTER TYPE quarantine_action_kind RENAME TO lidarr_quarantine_action_kind;
|
||||
ALTER TABLE quarantine_actions RENAME TO lidarr_quarantine_actions;
|
||||
|
||||
ALTER INDEX quarantine_pkey RENAME TO lidarr_quarantine_pkey;
|
||||
ALTER INDEX quarantine_track_idx RENAME TO lidarr_quarantine_track_idx;
|
||||
ALTER TYPE quarantine_reason RENAME TO lidarr_quarantine_reason;
|
||||
ALTER TABLE quarantine RENAME TO lidarr_quarantine;
|
||||
|
||||
CREATE TABLE lidarr_config (
|
||||
id smallint PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
||||
enabled boolean NOT NULL DEFAULT false,
|
||||
base_url text,
|
||||
api_key text,
|
||||
default_quality_profile_id int,
|
||||
default_root_folder_path text,
|
||||
default_metadata_profile_id integer,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
INSERT INTO lidarr_config (id, enabled) VALUES (1, false);
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Minstrel no longer talks to Lidarr (M502 #5525): it finds, downloads,
|
||||
-- checks and files music itself.
|
||||
--
|
||||
-- lidarr_config the connection's settings: dropped.
|
||||
-- lidarr_quarantine(_actions) the per-user track reports and the admin
|
||||
-- actions on them, which never needed Lidarr:
|
||||
-- renamed to quarantine(_actions), with their
|
||||
-- types and indexes.
|
||||
-- 'deleted_via_lidarr' the history of an album removed through
|
||||
-- Lidarr reads as what it did: 'deleted_album'.
|
||||
-- lidarr_album_mbid the album id that removal recorded: album_mbid.
|
||||
DROP TABLE lidarr_config;
|
||||
|
||||
ALTER TABLE lidarr_quarantine RENAME TO quarantine;
|
||||
ALTER TYPE lidarr_quarantine_reason RENAME TO quarantine_reason;
|
||||
ALTER INDEX lidarr_quarantine_track_idx RENAME TO quarantine_track_idx;
|
||||
ALTER INDEX lidarr_quarantine_pkey RENAME TO quarantine_pkey;
|
||||
|
||||
ALTER TABLE lidarr_quarantine_actions RENAME TO quarantine_actions;
|
||||
ALTER TYPE lidarr_quarantine_action_kind RENAME TO quarantine_action_kind;
|
||||
ALTER TYPE quarantine_action_kind RENAME VALUE 'deleted_via_lidarr' TO 'deleted_album';
|
||||
ALTER TABLE quarantine_actions RENAME COLUMN lidarr_album_mbid TO album_mbid;
|
||||
ALTER INDEX lidarr_quarantine_actions_pkey RENAME TO quarantine_actions_pkey;
|
||||
ALTER INDEX lidarr_quarantine_actions_track_idx RENAME TO quarantine_actions_track_idx;
|
||||
ALTER INDEX lidarr_quarantine_actions_created_idx RENAME TO quarantine_actions_created_idx;
|
||||
|
||||
ALTER TABLE quarantine RENAME CONSTRAINT lidarr_quarantine_user_id_fkey TO quarantine_user_id_fkey;
|
||||
ALTER TABLE quarantine RENAME CONSTRAINT lidarr_quarantine_track_id_fkey TO quarantine_track_id_fkey;
|
||||
ALTER TABLE quarantine_actions RENAME CONSTRAINT lidarr_quarantine_actions_admin_id_fkey TO quarantine_actions_admin_id_fkey;
|
||||
@@ -11,11 +11,11 @@ SELECT
|
||||
-- it, or it failed to play (#5606). Counted by track, as the Reports
|
||||
-- page lists them.
|
||||
(SELECT count(*) FROM (
|
||||
SELECT track_id FROM lidarr_quarantine
|
||||
SELECT track_id FROM quarantine
|
||||
UNION
|
||||
SELECT track_id FROM playback_errors WHERE resolved_at IS NULL) r)::bigint AS reports,
|
||||
(SELECT min(at) FROM (
|
||||
SELECT created_at AS at FROM lidarr_quarantine
|
||||
SELECT created_at AS at FROM quarantine
|
||||
UNION ALL
|
||||
SELECT occurred_at FROM playback_errors WHERE resolved_at IS NULL) r)::timestamptz AS reports_since,
|
||||
(SELECT count(*) FROM duplicate_groups g
|
||||
|
||||
@@ -15,8 +15,8 @@ SELECT count(*)::bigint FROM audit_log
|
||||
|
||||
-- name: ListAuditLogByActions :many
|
||||
-- One page of the given actions, newest first: the duplicates report's
|
||||
-- "resolved automatically" activity (M498) reads the resolver's merges and
|
||||
-- Lidarr release changes from here.
|
||||
-- "resolved automatically" activity (M498) reads the resolver's merges from
|
||||
-- here.
|
||||
SELECT * FROM audit_log
|
||||
WHERE action = ANY(sqlc.arg(actions)::text[])
|
||||
AND (NOT sqlc.arg(system_only)::boolean OR actor_id IS NULL)
|
||||
|
||||
@@ -41,7 +41,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE gl.user_id = $1 AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -74,7 +74,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE ml.user_id = $1 AND ml.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.album_id, t.artist_id
|
||||
@@ -100,7 +100,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
WHERE gl.user_id = $1 AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -144,7 +144,7 @@ WITH scored AS (
|
||||
WHERE gl.user_id = sqlc.arg(user_id) AND gl.track_id = t.id
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = sqlc.arg(user_id) AND q.track_id = t.id
|
||||
)
|
||||
GROUP BY t.id, t.album_id, t.artist_id
|
||||
|
||||
@@ -16,7 +16,7 @@ SELECT pe.id AS event_id,
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.was_skipped = false
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY pe.started_at DESC
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
-- name: GetLidarrConfig :one
|
||||
SELECT id, enabled, base_url, api_key, default_quality_profile_id,
|
||||
default_metadata_profile_id, default_root_folder_path,
|
||||
created_at, updated_at
|
||||
FROM lidarr_config
|
||||
WHERE id = 1;
|
||||
|
||||
-- name: UpdateLidarrConfig :one
|
||||
UPDATE lidarr_config
|
||||
SET enabled = $1,
|
||||
base_url = $2,
|
||||
api_key = $3,
|
||||
default_quality_profile_id = $4,
|
||||
default_metadata_profile_id = $5,
|
||||
default_root_folder_path = $6,
|
||||
updated_at = now()
|
||||
WHERE id = 1
|
||||
RETURNING id, enabled, base_url, api_key, default_quality_profile_id,
|
||||
default_metadata_profile_id, default_root_folder_path,
|
||||
created_at, updated_at;
|
||||
@@ -13,11 +13,9 @@ SELECT id, tier, status
|
||||
FOR UPDATE;
|
||||
|
||||
-- name: ListDuplicateGroupMergeMembers :many
|
||||
SELECT t.id, t.file_path, t.file_format, t.file_size, t.added_at, t.album_id,
|
||||
t.mbid, albums.mbid AS album_mbid
|
||||
SELECT t.id, t.file_path, t.file_format, t.file_size, t.added_at, t.album_id
|
||||
FROM duplicate_group_members m
|
||||
JOIN tracks t ON t.id = m.track_id
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN tracks t ON t.id = m.track_id
|
||||
WHERE m.group_id = sqlc.arg(group_id)
|
||||
ORDER BY t.id;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Missing-pair pass (M485). A track marked missing whose replacement is already
|
||||
-- on disk under another row — most often a Lidarr quality upgrade, mp3 → flac at
|
||||
-- a new path — is folded into that replacement, so its history moves across and
|
||||
-- on disk under another row — most often a quality upgrade, mp3 → flac at a new
|
||||
-- path — is folded into that replacement, so its history moves across and
|
||||
-- re-acquisition stops chasing music the library already has.
|
||||
--
|
||||
-- Move adoption (#2528) cannot catch these: it links a new file to a missing row
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- name: UpsertQuarantine :one
|
||||
-- Insert a new quarantine row, or update reason/notes if the user has
|
||||
-- already flagged this track.
|
||||
INSERT INTO lidarr_quarantine (user_id, track_id, reason, notes)
|
||||
INSERT INTO quarantine (user_id, track_id, reason, notes)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id, track_id) DO UPDATE SET
|
||||
reason = EXCLUDED.reason,
|
||||
@@ -12,7 +12,7 @@ RETURNING user_id, track_id, reason, notes, created_at;
|
||||
-- name: DeleteQuarantine :one
|
||||
-- Removes the caller's row. Returns the deleted row so the handler can
|
||||
-- distinguish "no row existed" (zero rows -> ErrNoRows) from success.
|
||||
DELETE FROM lidarr_quarantine
|
||||
DELETE FROM quarantine
|
||||
WHERE user_id = $1 AND track_id = $2
|
||||
RETURNING user_id, track_id, reason, notes, created_at;
|
||||
|
||||
@@ -31,7 +31,7 @@ SELECT
|
||||
al.cover_art_path AS album_cover_art_path,
|
||||
ar.id AS artist_id,
|
||||
ar.name AS artist_name
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
@@ -48,14 +48,13 @@ SELECT
|
||||
ar.name AS artist_name,
|
||||
al.title AS album_title,
|
||||
al.id AS album_id,
|
||||
al.mbid AS lidarr_album_mbid,
|
||||
count(q.user_id)::int AS report_count,
|
||||
max(q.created_at)::timestamptz AS latest_at
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN tracks t ON t.id = q.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
JOIN artists ar ON ar.id = t.artist_id
|
||||
GROUP BY t.id, ar.name, al.title, al.id, al.mbid
|
||||
GROUP BY t.id, ar.name, al.title, al.id
|
||||
ORDER BY max(q.created_at) DESC;
|
||||
|
||||
-- name: ListQuarantineReportsForTrack :many
|
||||
@@ -67,7 +66,7 @@ SELECT
|
||||
q.reason,
|
||||
q.notes,
|
||||
q.created_at
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
JOIN users u ON u.id = q.user_id
|
||||
WHERE q.track_id = $1
|
||||
ORDER BY q.created_at DESC;
|
||||
@@ -76,21 +75,21 @@ ORDER BY q.created_at DESC;
|
||||
-- Clears all per-user rows for a given track. Used by Resolve and the
|
||||
-- two delete actions. Caller writes the audit row separately before
|
||||
-- this fires (so we can capture the affected_users count).
|
||||
DELETE FROM lidarr_quarantine WHERE track_id = $1;
|
||||
DELETE FROM quarantine WHERE track_id = $1;
|
||||
|
||||
-- name: CountQuarantineForTrack :one
|
||||
-- Reads affected_users for the audit row before the delete fires.
|
||||
SELECT count(*)::int FROM lidarr_quarantine WHERE track_id = $1;
|
||||
SELECT count(*)::int FROM quarantine WHERE track_id = $1;
|
||||
|
||||
-- name: WriteQuarantineAction :one
|
||||
-- Audit row for an admin destructive action.
|
||||
INSERT INTO lidarr_quarantine_actions (
|
||||
INSERT INTO quarantine_actions (
|
||||
track_id, track_title, artist_name, album_title,
|
||||
action, admin_id, lidarr_album_mbid, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
action, admin_id, affected_users
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *;
|
||||
|
||||
-- name: ListQuarantineActions :many
|
||||
SELECT * FROM lidarr_quarantine_actions
|
||||
SELECT * FROM quarantine_actions
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1;
|
||||
@@ -31,7 +31,7 @@ WHERE t.id <> $2
|
||||
AND started_at > now() - $3 * interval '1 hour'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
);
|
||||
|
||||
@@ -83,7 +83,7 @@ excluded_ids AS (
|
||||
WHERE pe.user_id = $1 AND pe.started_at > now() - $3 * interval '1 hour'
|
||||
UNION ALL
|
||||
SELECT q.track_id AS id
|
||||
FROM lidarr_quarantine q
|
||||
FROM quarantine q
|
||||
WHERE q.user_id = $1
|
||||
),
|
||||
lb_similar AS (
|
||||
@@ -416,7 +416,7 @@ JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY p.cnt DESC, t.id
|
||||
@@ -443,7 +443,7 @@ JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.artist_id = sqlc.arg(artist_id)
|
||||
AND t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = sqlc.arg(user_id) AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY p.cnt DESC, t.id
|
||||
|
||||
@@ -44,7 +44,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
AND COALESCE(pc.c, 0) <= 2
|
||||
AND COALESCE(sc.c, 0) < 2
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY md5(t.id::text || $2::text)
|
||||
@@ -75,7 +75,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
AND s.c >= 3
|
||||
AND s.last_at <= now() - interval '30 days'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
-- Declined-resurfacing cooldown (#1258): a skip on a
|
||||
@@ -153,7 +153,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier
|
||||
JOIN albums_tiered alt ON alt.album_id = t.album_id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY alt.tier, alt.created_at DESC, t.album_id,
|
||||
@@ -192,7 +192,7 @@ SELECT t.id, t.album_id, t.artist_id
|
||||
JOIN windowed w ON w.track_id = t.id
|
||||
WHERE t.missing_since IS NULL -- #2523: never offer a file that is gone
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY w.c DESC, md5(t.id::text || $2::text)
|
||||
@@ -248,7 +248,7 @@ SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier
|
||||
AND alt.tier IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM attempted a WHERE a.track_id = t.id)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY
|
||||
|
||||
@@ -24,7 +24,7 @@ JOIN albums a ON a.id = t.album_id
|
||||
WHERE pe.user_id = $1
|
||||
AND pe.started_at > now() - ($2::float8 * INTERVAL '1 day')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ SELECT * FROM tracks WHERE file_path = $1;
|
||||
SELECT * FROM tracks
|
||||
WHERE album_id = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
)
|
||||
ORDER BY disc_number NULLS LAST, track_number NULLS LAST;
|
||||
@@ -76,7 +76,7 @@ SELECT count(*) FROM tracks WHERE album_id = $1;
|
||||
SELECT * FROM tracks
|
||||
WHERE title ILIKE '%' || $1::text || '%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
)
|
||||
ORDER BY title
|
||||
@@ -87,13 +87,13 @@ LIMIT $3 OFFSET $4;
|
||||
SELECT COUNT(*) FROM tracks
|
||||
WHERE title ILIKE '%' || $1::text || '%'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = tracks.id
|
||||
);
|
||||
|
||||
-- name: ListArtistTracksForUser :many
|
||||
-- M6a: every track for the artist across their albums, with album_title
|
||||
-- and artist_name joined. Honors per-user lidarr_quarantine. Used by
|
||||
-- and artist_name joined. Honors per-user quarantine. Used by
|
||||
-- /api/artists/{id}/tracks for the artist-card play affordance, which
|
||||
-- shuffles client-side. Ordering matches album/track natural order so
|
||||
-- the shuffle has a deterministic input.
|
||||
@@ -105,7 +105,7 @@ JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE t.artist_id = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $2 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY albums.release_date NULLS LAST, albums.sort_title,
|
||||
@@ -122,7 +122,7 @@ FROM tracks t
|
||||
JOIN albums ON albums.id = t.album_id
|
||||
JOIN artists ON artists.id = t.artist_id
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM lidarr_quarantine q
|
||||
SELECT 1 FROM quarantine q
|
||||
WHERE q.user_id = $1 AND q.track_id = t.id
|
||||
)
|
||||
ORDER BY random()
|
||||
@@ -136,8 +136,8 @@ SELECT COUNT(*) FROM tracks WHERE artist_id = $1;
|
||||
|
||||
-- name: DeleteTrack :one
|
||||
-- M7 #372: hard delete with FK cascade. The CASCADE on track_id from
|
||||
-- play_events / general_likes_tracks / lidarr_quarantine /
|
||||
-- lidarr_quarantine_actions handles their cleanup. RETURNING gives us
|
||||
-- play_events / general_likes_tracks / quarantine /
|
||||
-- quarantine_actions handles their cleanup. RETURNING gives us
|
||||
-- album_id + artist_id for the album-empty / artist-empty cascade
|
||||
-- checks the service does next.
|
||||
DELETE FROM tracks WHERE id = $1
|
||||
@@ -321,8 +321,8 @@ UPDATE tracks
|
||||
AND regexp_replace(file_path, '^.*/', '') ~* sqlc.arg(pattern)::text;
|
||||
|
||||
-- name: ClearStaleSuspectSourceTracks :execrows
|
||||
-- A held-back track whose name no longer carries a marker (renamed, retagged
|
||||
-- by Lidarr) is released again.
|
||||
-- A held-back track whose name no longer carries a marker (renamed or
|
||||
-- retagged) is released again.
|
||||
UPDATE tracks
|
||||
SET source_verdict = NULL, source_verdict_at = NULL
|
||||
WHERE source_verdict = 'suspect'
|
||||
|
||||
@@ -96,7 +96,7 @@ RETURNING *;
|
||||
-- Hard delete with cascade. The schema's ON DELETE CASCADE foreign keys
|
||||
-- handle plays/likes/sessions/etc. Caller must enforce the last-admin
|
||||
-- guard at the HTTP layer (count admins, refuse if target is the only
|
||||
-- admin). The lidarr_quarantine, general_likes*, play_events,
|
||||
-- admin). The quarantine, general_likes*, play_events,
|
||||
-- sessions, and other user-FK tables all have ON DELETE CASCADE so
|
||||
-- this is a clean drop.
|
||||
DELETE FROM users WHERE id = $1;
|
||||
|
||||
Reference in New Issue
Block a user