diff --git a/internal/api/events.go b/internal/api/events.go index af32a3ce..79a1d3ca 100644 --- a/internal/api/events.go +++ b/internal/api/events.go @@ -8,6 +8,7 @@ import ( "github.com/jackc/pgx/v5" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) @@ -34,19 +35,19 @@ type okResponse struct { func (h *handlers) handleEvents(w http.ResponseWriter, r *http.Request) { user, ok := auth.UserFromContext(r.Context()) if !ok { - writeErr(w, http.StatusUnauthorized, "unauthorized", "authentication required") + writeErr(w, apierror.ErrUnauthorized) return } var req eventRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body") + writeErr(w, apierror.BadRequest("bad_request", "invalid JSON body")) return } at := time.Now().UTC() if req.At != nil && *req.At != "" { parsed, err := time.Parse(time.RFC3339, *req.At) if err != nil { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid `at` timestamp") + writeErr(w, apierror.BadRequest("bad_request", "invalid `at` timestamp")) return } at = parsed @@ -63,7 +64,7 @@ func (h *handlers) handleEvents(w http.ResponseWriter, r *http.Request) { case "play_skipped": h.handleEventPlaySkipped(w, r, user, req, at) default: - writeErr(w, http.StatusBadRequest, "bad_request", "unknown event type") + writeErr(w, apierror.BadRequest("bad_request", "unknown event type")) } } @@ -73,22 +74,22 @@ func (h *handlers) handleEventPlayStarted( ) { trackID, ok := parseUUID(req.TrackID) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid track_id") + writeErr(w, apierror.BadRequest("bad_request", "invalid track_id")) return } if _, err := dbq.New(h.pool).GetTrackByID(r.Context(), trackID); err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "track not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "track not found"}) return } h.logger.Error("api: events: lookup track", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } res, err := h.events.RecordPlayStarted(r.Context(), user.ID, trackID, clientID, at) if err != nil { h.logger.Error("api: events: play_started", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "record failed") + writeErr(w, apierror.InternalMsg("record failed", err)) return } writeJSON(w, http.StatusOK, playStartedResponse{ @@ -103,30 +104,30 @@ func (h *handlers) handleEventPlayEnded( ) { playEventID, ok := parseUUID(req.PlayEventID) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid play_event_id") + writeErr(w, apierror.BadRequest("bad_request", "invalid play_event_id")) return } if req.DurationPlayedMs == nil || *req.DurationPlayedMs < 0 { - writeErr(w, http.StatusBadRequest, "bad_request", "duration_played_ms required and must be >= 0") + writeErr(w, apierror.BadRequest("bad_request", "duration_played_ms required and must be >= 0")) return } ev, err := dbq.New(h.pool).GetPlayEventByID(r.Context(), playEventID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "play_event not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "play_event not found"}) return } h.logger.Error("api: events: lookup play_event", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } if ev.UserID != user.ID { - writeErr(w, http.StatusForbidden, "forbidden", "play_event belongs to a different user") + writeErr(w, apierror.Forbidden("forbidden", "play_event belongs to a different user")) return } if err := h.events.RecordPlayEnded(r.Context(), playEventID, *req.DurationPlayedMs, at); err != nil { h.logger.Error("api: events: play_ended", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "record failed") + writeErr(w, apierror.InternalMsg("record failed", err)) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) @@ -138,30 +139,30 @@ func (h *handlers) handleEventPlaySkipped( ) { playEventID, ok := parseUUID(req.PlayEventID) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid play_event_id") + writeErr(w, apierror.BadRequest("bad_request", "invalid play_event_id")) return } if req.PositionMs == nil || *req.PositionMs < 0 { - writeErr(w, http.StatusBadRequest, "bad_request", "position_ms required and must be >= 0") + writeErr(w, apierror.BadRequest("bad_request", "position_ms required and must be >= 0")) return } ev, err := dbq.New(h.pool).GetPlayEventByID(r.Context(), playEventID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "play_event not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "play_event not found"}) return } h.logger.Error("api: events: lookup play_event", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } if ev.UserID != user.ID { - writeErr(w, http.StatusForbidden, "forbidden", "play_event belongs to a different user") + writeErr(w, apierror.Forbidden("forbidden", "play_event belongs to a different user")) return } if err := h.events.RecordPlaySkipped(r.Context(), playEventID, *req.PositionMs, at); err != nil { h.logger.Error("api: events: play_skipped", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "record failed") + writeErr(w, apierror.InternalMsg("record failed", err)) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) diff --git a/internal/api/home.go b/internal/api/home.go index d1284e2a..31654950 100644 --- a/internal/api/home.go +++ b/internal/api/home.go @@ -3,6 +3,7 @@ package api import ( "net/http" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" "git.fabledsword.com/bvandeusen/minstrel/internal/recommendation" @@ -15,13 +16,13 @@ import ( func (h *handlers) handleGetHome(w http.ResponseWriter, r *http.Request) { user, ok := auth.UserFromContext(r.Context()) if !ok { - writeErr(w, http.StatusUnauthorized, "unauthorized", "authentication required") + writeErr(w, apierror.ErrUnauthorized) return } data, err := recommendation.HomeData(r.Context(), h.pool, user.ID) if err != nil { h.logger.Error("api: home data", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "failed to load home") + writeErr(w, apierror.InternalMsg("failed to load home", err)) return } diff --git a/internal/api/library.go b/internal/api/library.go index e57aee78..9e0b66f0 100644 --- a/internal/api/library.go +++ b/internal/api/library.go @@ -7,6 +7,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) @@ -17,30 +18,30 @@ import ( func (h *handlers) handleGetTrack(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid track id") + writeErr(w, apierror.BadRequest("bad_request", "invalid track id")) return } q := dbq.New(h.pool) track, err := q.GetTrackByID(r.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "track not found") + writeErr(w, apierror.NotFound("track")) return } h.logger.Error("api: get track failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } album, err := q.GetAlbumByID(r.Context(), track.AlbumID) if err != nil { h.logger.Error("api: get track album failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } artist, err := q.GetArtistByID(r.Context(), track.ArtistID) if err != nil { h.logger.Error("api: get track artist failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } writeJSON(w, http.StatusOK, trackRefFrom(track, album.Title, artist.Name)) @@ -52,24 +53,24 @@ func (h *handlers) handleGetTrack(w http.ResponseWriter, r *http.Request) { func (h *handlers) handleGetAlbum(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid album id") + writeErr(w, apierror.BadRequest("bad_request", "invalid album id")) return } q := dbq.New(h.pool) album, err := q.GetAlbumByID(r.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "album not found") + writeErr(w, apierror.NotFound("album")) return } h.logger.Error("api: get album failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } artist, err := q.GetArtistByID(r.Context(), album.ArtistID) if err != nil { h.logger.Error("api: get album artist failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } var tracks []dbq.Track @@ -82,7 +83,7 @@ func (h *handlers) handleGetAlbum(w http.ResponseWriter, r *http.Request) { } if err != nil { h.logger.Error("api: list tracks failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } refs := make([]TrackRef, 0, len(tracks)) @@ -105,24 +106,24 @@ func (h *handlers) handleGetAlbum(w http.ResponseWriter, r *http.Request) { func (h *handlers) handleGetArtist(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid artist id") + writeErr(w, apierror.BadRequest("bad_request", "invalid artist id")) return } q := dbq.New(h.pool) artist, err := q.GetArtistByID(r.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "artist not found") + writeErr(w, apierror.NotFound("artist")) return } h.logger.Error("api: get artist failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } albums, err := q.ListAlbumsByArtist(r.Context(), id) if err != nil { h.logger.Error("api: list albums by artist failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } refs := make([]AlbumRef, 0, len(albums)) @@ -130,7 +131,7 @@ func (h *handlers) handleGetArtist(w http.ResponseWriter, r *http.Request) { count, cerr := q.CountTracksByAlbum(r.Context(), a.ID) if cerr != nil { h.logger.Error("api: count tracks failed", "err", cerr) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", cerr)) return } // durationSec=0: not aggregated for nested album lists per spec data flow. @@ -150,22 +151,22 @@ func (h *handlers) handleGetArtist(w http.ResponseWriter, r *http.Request) { func (h *handlers) handleGetArtistTracks(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid artist id") + writeErr(w, apierror.BadRequest("bad_request", "invalid artist id")) return } user, ok := auth.UserFromContext(r.Context()) if !ok { - writeErr(w, http.StatusUnauthorized, "unauthorized", "authentication required") + writeErr(w, apierror.ErrUnauthorized) return } q := dbq.New(h.pool) if _, err := q.GetArtistByID(r.Context(), id); err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "artist not found") + writeErr(w, apierror.NotFound("artist")) return } h.logger.Error("api: get artist for tracks", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } rows, err := q.ListArtistTracksForUser(r.Context(), dbq.ListArtistTracksForUserParams{ @@ -173,7 +174,7 @@ func (h *handlers) handleGetArtistTracks(w http.ResponseWriter, r *http.Request) }) if err != nil { h.logger.Error("api: list artist tracks", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } out := make([]TrackRef, 0, len(rows)) @@ -194,12 +195,12 @@ func (h *handlers) handleListArtists(w http.ResponseWriter, r *http.Request) { sort = "alpha" } if sort != "alpha" && sort != "newest" { - writeErr(w, http.StatusBadRequest, "bad_request", "sort must be alpha or newest") + writeErr(w, apierror.BadRequest("bad_request", "sort must be alpha or newest")) return } limit, offset, err := parsePaging(r.URL.Query()) if err != nil { - writeErr(w, http.StatusBadRequest, "bad_request", err.Error()) + writeErr(w, apierror.BadRequest("bad_request", err.Error())) return } q := dbq.New(h.pool) @@ -211,13 +212,13 @@ func (h *handlers) handleListArtists(w http.ResponseWriter, r *http.Request) { }) if err != nil { h.logger.Error("api: list artists newest", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } total, terr := q.CountArtists(r.Context()) if terr != nil { h.logger.Error("api: count artists", "err", terr) - writeErr(w, http.StatusInternalServerError, "server_error", "count failed") + writeErr(w, apierror.InternalMsg("count failed", terr)) return } items := make([]ArtistRef, 0, len(artists)) @@ -225,7 +226,7 @@ func (h *handlers) handleListArtists(w http.ResponseWriter, r *http.Request) { albums, aerr := q.ListAlbumsByArtist(r.Context(), a.ID) if aerr != nil { h.logger.Error("api: list albums for artist", "err", aerr) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", aerr)) return } items = append(items, artistRefFrom(a, len(albums))) @@ -241,13 +242,13 @@ func (h *handlers) handleListArtists(w http.ResponseWriter, r *http.Request) { }) if err != nil { h.logger.Error("api: list artists alpha", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } total, terr := q.CountArtists(r.Context()) if terr != nil { h.logger.Error("api: count artists", "err", terr) - writeErr(w, http.StatusInternalServerError, "server_error", "count failed") + writeErr(w, apierror.InternalMsg("count failed", terr)) return } items := make([]ArtistRef, 0, len(rows)) diff --git a/internal/api/library_albums.go b/internal/api/library_albums.go index dcb816e4..cff8e0d6 100644 --- a/internal/api/library_albums.go +++ b/internal/api/library_albums.go @@ -3,6 +3,7 @@ package api import ( "net/http" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) @@ -13,7 +14,7 @@ import ( func (h *handlers) handleListLibraryAlbums(w http.ResponseWriter, r *http.Request) { limit, offset, err := parsePaging(r.URL.Query()) if err != nil { - writeErr(w, http.StatusBadRequest, "bad_request", err.Error()) + writeErr(w, apierror.BadRequest("bad_request", err.Error())) return } q := dbq.New(h.pool) @@ -22,13 +23,13 @@ func (h *handlers) handleListLibraryAlbums(w http.ResponseWriter, r *http.Reques }) if err != nil { h.logger.Error("api: list library albums", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed") + writeErr(w, apierror.InternalMsg("lookup failed", err)) return } total, err := q.CountAlbums(r.Context()) if err != nil { h.logger.Error("api: count albums", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "count failed") + writeErr(w, apierror.InternalMsg("count failed", err)) return } items := make([]AlbumRef, 0, len(rows)) diff --git a/internal/api/media.go b/internal/api/media.go index 4d5591ca..bd53abbf 100644 --- a/internal/api/media.go +++ b/internal/api/media.go @@ -17,6 +17,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/coverart" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) @@ -89,23 +90,23 @@ func imageContentType(path string) string { func (h *handlers) handleGetCover(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid album id") + writeErr(w, apierror.BadRequest("bad_request", "invalid album id")) return } q := dbq.New(h.pool) album, err := q.GetAlbumByID(r.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "album not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "album not found"}) return } h.logger.Error("api: get album for cover failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "server error") + writeErr(w, apierror.InternalMsg("server error", err)) return } path := resolveAlbumCoverPath(r.Context(), q, album) if path == "" { - writeErr(w, http.StatusNotFound, "not_found", "cover not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "cover not found"}) return } f, err := os.Open(path) @@ -114,14 +115,14 @@ func (h *handlers) handleGetCover(w http.ResponseWriter, r *http.Request) { // between stat and open, treat it the same as no-art — the user // experience (404) matches, and 500 would misleadingly imply a server // bug rather than a filesystem race. - writeErr(w, http.StatusNotFound, "not_found", "cover not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "cover not found"}) return } defer func() { _ = f.Close() }() info, err := f.Stat() if err != nil { h.logger.Error("api: stat cover failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "server error") + writeErr(w, apierror.InternalMsg("server error", err)) return } w.Header().Set("Content-Type", imageContentType(path)) @@ -134,32 +135,32 @@ func (h *handlers) handleGetCover(w http.ResponseWriter, r *http.Request) { func (h *handlers) handleGetStream(w http.ResponseWriter, r *http.Request) { id, ok := parseUUID(chi.URLParam(r, "id")) if !ok { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid track id") + writeErr(w, apierror.BadRequest("bad_request", "invalid track id")) return } q := dbq.New(h.pool) track, err := q.GetTrackByID(r.Context(), id) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - writeErr(w, http.StatusNotFound, "not_found", "track not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "track not found"}) return } h.logger.Error("api: get track for stream failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "server error") + writeErr(w, apierror.InternalMsg("server error", err)) return } f, err := os.Open(track.FilePath) if err != nil { // File vanished (scanner indexed it, filesystem lost it). Treat as // 404 so clients can fall back to the next item in a queue. - writeErr(w, http.StatusNotFound, "not_found", "track file not found") + writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "track file not found"}) return } defer func() { _ = f.Close() }() info, err := f.Stat() if err != nil { h.logger.Error("api: stat track failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "server error") + writeErr(w, apierror.InternalMsg("server error", err)) return } w.Header().Set("Content-Type", audioContentType(track.FileFormat)) diff --git a/internal/api/search.go b/internal/api/search.go index 9227bab0..efba9ae8 100644 --- a/internal/api/search.go +++ b/internal/api/search.go @@ -7,6 +7,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) @@ -18,12 +19,12 @@ import ( func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { q := strings.TrimSpace(r.URL.Query().Get("q")) if q == "" { - writeErr(w, http.StatusBadRequest, "bad_request", "q is required") + writeErr(w, apierror.BadRequest("bad_request", "q is required")) return } limit, offset, err := parsePaging(r.URL.Query()) if err != nil { - writeErr(w, http.StatusBadRequest, "bad_request", err.Error()) + writeErr(w, apierror.BadRequest("bad_request", err.Error())) return } @@ -35,13 +36,13 @@ func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { }) if err != nil { h.logger.Error("api: search artists failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } artistTotal, err := dbQ.CountArtistsMatching(r.Context(), q) if err != nil { h.logger.Error("api: count artists matching failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } artistItems := make([]ArtistRef, 0, len(artists)) @@ -49,7 +50,7 @@ func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { albums, aerr := dbQ.ListAlbumsByArtist(r.Context(), a.ID) if aerr != nil { h.logger.Error("api: list albums for search artist failed", "err", aerr) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", aerr)) return } artistItems = append(artistItems, artistRefFrom(a, len(albums))) @@ -60,18 +61,18 @@ func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { }) if err != nil { h.logger.Error("api: search albums failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } albumTotal, err := dbQ.CountAlbumsMatching(r.Context(), q) if err != nil { h.logger.Error("api: count albums matching failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } albumItems, err := h.resolveAlbumRefs(r.Context(), dbQ, albums) if err != nil { - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } @@ -87,7 +88,7 @@ func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { } if err != nil { h.logger.Error("api: search tracks failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } var trackTotal int64 @@ -100,12 +101,12 @@ func (h *handlers) handleSearch(w http.ResponseWriter, r *http.Request) { } if err != nil { h.logger.Error("api: count tracks matching failed", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } trackItems, err := h.resolveTrackRefs(r.Context(), dbQ, tracks) if err != nil { - writeErr(w, http.StatusInternalServerError, "server_error", "search failed") + writeErr(w, apierror.InternalMsg("search failed", err)) return } diff --git a/internal/api/suggestions.go b/internal/api/suggestions.go index c4ab3134..1e511c13 100644 --- a/internal/api/suggestions.go +++ b/internal/api/suggestions.go @@ -6,6 +6,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" + "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/recommendation" ) @@ -36,14 +37,14 @@ type seedContributionView struct { func (h *handlers) handleListSuggestions(w http.ResponseWriter, r *http.Request) { user, ok := auth.UserFromContext(r.Context()) if !ok { - writeErr(w, http.StatusUnauthorized, "unauthorized", "authentication required") + writeErr(w, apierror.ErrUnauthorized) return } limit := 12 if v := r.URL.Query().Get("limit"); v != "" { n, err := strconv.Atoi(v) if err != nil || n < 1 { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid limit") + writeErr(w, apierror.BadRequest("bad_request", "invalid limit")) return } limit = n @@ -52,7 +53,7 @@ func (h *handlers) handleListSuggestions(w http.ResponseWriter, r *http.Request) if v := r.URL.Query().Get("half_life_days"); v != "" { f, err := strconv.ParseFloat(v, 64) if err != nil || f <= 0 { - writeErr(w, http.StatusBadRequest, "bad_request", "invalid half_life_days") + writeErr(w, apierror.BadRequest("bad_request", "invalid half_life_days")) return } halfLife = f @@ -61,7 +62,7 @@ func (h *handlers) handleListSuggestions(w http.ResponseWriter, r *http.Request) suggestions, err := recommendation.SuggestArtists(r.Context(), h.pool, user.ID, halfLife, limit) if err != nil { h.logger.Error("api: list suggestions", "err", err) - writeErr(w, http.StatusInternalServerError, "server_error", "failed to load suggestions") + writeErr(w, apierror.InternalMsg("failed to load suggestions", err)) return }