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

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:
2026-10-11 13:48:20 -04:00
co-authored by Claude Opus 5.5
parent c54792c51e
commit 3a16732883
170 changed files with 994 additions and 6242 deletions
+8 -31
View File
@@ -3,7 +3,6 @@ package api
import (
"errors"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
@@ -11,27 +10,22 @@ import (
"git.fabledsword.com/bvandeusen/minstrel/internal/tracks"
)
// removeTrackResponse is the wire shape from spec §5 (M7 #372). The three
// removeTrackResponse is the wire shape from spec §5 (M7 #372). The two
// optional fields are omitted unless the corresponding cleanup actually
// happened, so the response stays minimal in the common case.
type removeTrackResponse struct {
DeletedTrackID string `json:"deleted_track_id"`
DeletedAlbumID *string `json:"deleted_album_id,omitempty"`
DeletedArtistID *string `json:"deleted_artist_id,omitempty"`
LidarrUnmonitorFailed *bool `json:"lidarr_unmonitor_failed,omitempty"`
DeletedTrackID string `json:"deleted_track_id"`
DeletedAlbumID *string `json:"deleted_album_id,omitempty"`
DeletedArtistID *string `json:"deleted_artist_id,omitempty"`
}
// handleRemoveTrack implements DELETE /api/admin/tracks/{id}?unmonitor=true|false.
// handleRemoveTrack implements DELETE /api/admin/tracks/{id}.
//
// Admin-only (gated by auth.RequireAdmin on the /admin route group). Deletes the
// file, then the DB row, and runs the album/artist cascade tidy-up — and deletes
// nothing at all when the file cannot be removed (#3918). When
// unmonitor=true and the track has an mbid, also calls Lidarr.UnmonitorTrack
// — failure there is non-fatal (the destructive part already completed) and
// surfaces as `lidarr_unmonitor_failed: true` in the success envelope.
// nothing at all when the file cannot be removed (#3918).
//
// Per spec §5, Lidarr-side errors during the unmonitor step do NOT map to
// wire error codes. The codes this handler emits are not_found,
// The codes this handler emits are not_found,
// library_not_writable (409) and file_delete_failed when the file could not be
// removed, server_error, plus the auth codes the middleware emits upstream.
func (h *handlers) handleRemoveTrack(w http.ResponseWriter, r *http.Request) {
@@ -46,13 +40,6 @@ func (h *handlers) handleRemoveTrack(w http.ResponseWriter, r *http.Request) {
return
}
unmonitor := false
if v := r.URL.Query().Get("unmonitor"); v != "" {
if parsed, err := strconv.ParseBool(v); err == nil {
unmonitor = parsed
}
}
// Defensive: RequireUser+RequireAdmin should have run upstream. If
// we got here without a user in context the routing is broken.
admin, ok := requireUser(w, r)
@@ -60,9 +47,7 @@ func (h *handlers) handleRemoveTrack(w http.ResponseWriter, r *http.Request) {
return
}
deletedAlbum, deletedArtist, lidarrUnmonitorFailed, err := h.tracks.RemoveTrack(
r.Context(), trackID, admin.ID, unmonitor,
)
deletedAlbum, deletedArtist, err := h.tracks.RemoveTrack(r.Context(), trackID, admin.ID)
if err != nil {
if errors.Is(err, tracks.ErrNotFound) {
writeErr(w, &apierror.Error{Status: http.StatusNotFound, Code: "not_found", Message: "track not found"})
@@ -87,13 +72,5 @@ func (h *handlers) handleRemoveTrack(w http.ResponseWriter, r *http.Request) {
s := uuidToString(*deletedArtist)
resp.DeletedArtistID = &s
}
// Only emit the flag when unmonitor was requested AND it failed.
// Avoids "false" cluttering the wire when the operator didn't ask
// for an unmonitor in the first place.
if unmonitor && lidarrUnmonitorFailed {
t := true
resp.LidarrUnmonitorFailed = &t
}
writeJSON(w, http.StatusOK, resp)
}