refactor(server/api): migrate library/media handlers to writeErr(err) (T3b)
This commit is contained in:
+21
-20
@@ -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})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+28
-27
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
+12
-11
@@ -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))
|
||||
|
||||
+12
-11
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user