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) }