Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. 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 DELETE /api/admin/tracks/{id}'s answer. 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.
|
|
//
|
|
// 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)
|
|
}
|