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>
77 lines
2.6 KiB
Go
77 lines
2.6 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/tracks"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// 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).
|
|
//
|
|
// 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) {
|
|
idStr := chi.URLParam(r, "id")
|
|
trackID, ok := parseUUID(idStr)
|
|
if !ok {
|
|
// Malformed id is functionally equivalent to "no such track" for
|
|
// the spec's error surface — collapse both to 404 not_found so
|
|
// the client doesn't have to handle a separate bad_request branch
|
|
// for a code path that's only reachable via UI bugs.
|
|
writeErr(w, &apierror.Error{Status: http.StatusNotFound, Code: "not_found", Message: "track not found"})
|
|
return
|
|
}
|
|
|
|
// 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)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
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"})
|
|
return
|
|
}
|
|
if apiErr, ok := fileRemoveAPIError(err); ok {
|
|
logFileRemoveFailure(h.logger, apiErr, "track_id", idStr)
|
|
writeErr(w, apiErr)
|
|
return
|
|
}
|
|
h.logger.Error("api: remove track failed", "err", err, "track_id", idStr)
|
|
writeErr(w, apierror.InternalMsg("remove failed", err))
|
|
return
|
|
}
|
|
|
|
resp := removeTrackResponse{DeletedTrackID: idStr}
|
|
if deletedAlbum != nil {
|
|
s := uuidToString(*deletedAlbum)
|
|
resp.DeletedAlbumID = &s
|
|
}
|
|
if deletedArtist != nil {
|
|
s := uuidToString(*deletedArtist)
|
|
resp.DeletedArtistID = &s
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|