refactor(server): final writeErr migration + ErrNotFound aliases (T3c)

This commit is contained in:
2026-05-07 21:19:35 -04:00
parent 91f4f5c18a
commit 754a7955cc
14 changed files with 182 additions and 147 deletions
+32 -31
View File
@@ -15,6 +15,7 @@ import (
"github.com/jackc/pgx/v5"
"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"
"git.fabledsword.com/bvandeusen/minstrel/internal/playlists"
@@ -89,7 +90,7 @@ type listPlaylistsResponse struct {
func (h *handlers) handleListPlaylists(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
@@ -101,7 +102,7 @@ func (h *handlers) handleListPlaylists(w http.ResponseWriter, r *http.Request) {
case "user", "system", "all":
// valid
default:
writeErr(w, http.StatusBadRequest, "bad_kind", "kind must be one of: user, system, all")
writeErr(w, apierror.BadRequest("bad_kind", "kind must be one of: user, system, all"))
return
}
@@ -114,7 +115,7 @@ func (h *handlers) handleListPlaylists(w http.ResponseWriter, r *http.Request) {
rows, err := h.playlists.List(r.Context(), caller.ID)
if err != nil {
h.logger.Error("api: list playlists failed", "err", err)
writeErr(w, http.StatusInternalServerError, "server_error", "list failed")
writeErr(w, apierror.InternalMsg("list failed", err))
return
}
resp := listPlaylistsResponse{Owned: []playlistRowView{}, Public: []playlistRowView{}}
@@ -176,12 +177,12 @@ type createPlaylistBody struct {
func (h *handlers) handleCreatePlaylist(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
var body createPlaylistBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
writeErr(w, apierror.BadRequest("bad_request", "invalid JSON body"))
return
}
row, err := h.playlists.Create(r.Context(), caller.ID, body.Name, body.Description, body.IsPublic)
@@ -198,12 +199,12 @@ func (h *handlers) handleCreatePlaylist(w http.ResponseWriter, r *http.Request)
func (h *handlers) handleGetPlaylist(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
detail, err := h.playlists.Get(r.Context(), caller.ID, playlistID)
@@ -227,17 +228,17 @@ type updatePlaylistBody struct {
func (h *handlers) handleUpdatePlaylist(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
var body updatePlaylistBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
writeErr(w, apierror.BadRequest("bad_request", "invalid JSON body"))
return
}
row, err := h.playlists.Update(r.Context(), caller.ID, playlistID, playlists.UpdateInput{
@@ -257,12 +258,12 @@ func (h *handlers) handleUpdatePlaylist(w http.ResponseWriter, r *http.Request)
func (h *handlers) handleDeletePlaylist(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
if err := h.playlists.Delete(r.Context(), caller.ID, playlistID); err != nil {
@@ -282,24 +283,24 @@ type appendTracksBody struct {
func (h *handlers) handleAppendTracks(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
var body appendTracksBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
writeErr(w, apierror.BadRequest("bad_request", "invalid JSON body"))
return
}
trackIDs := make([]pgtype.UUID, 0, len(body.TrackIDs))
for _, s := range body.TrackIDs {
u, ok := parseUUID(s)
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", fmt.Sprintf("invalid track id %q", s))
writeErr(w, apierror.BadRequest("bad_request", fmt.Sprintf("invalid track id %q", s)))
return
}
trackIDs = append(trackIDs, u)
@@ -321,18 +322,18 @@ func (h *handlers) handleAppendTracks(w http.ResponseWriter, r *http.Request) {
func (h *handlers) handleRemovePlaylistTrack(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
posStr := chi.URLParam(r, "position")
pos64, err := strconv.ParseInt(posStr, 10, 32)
if err != nil || pos64 < 0 {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid position")
writeErr(w, apierror.BadRequest("bad_request", "invalid position"))
return
}
if err := h.playlists.RemoveTrack(r.Context(), caller.ID, playlistID, int32(pos64)); err != nil {
@@ -357,17 +358,17 @@ type reorderTracksBody struct {
func (h *handlers) handleReorderPlaylist(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
var body reorderTracksBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
writeErr(w, apierror.BadRequest("bad_request", "invalid JSON body"))
return
}
if err := h.playlists.Reorder(r.Context(), caller.ID, playlistID, body.OrderedPositions); err != nil {
@@ -389,12 +390,12 @@ func (h *handlers) handleReorderPlaylist(w http.ResponseWriter, r *http.Request)
func (h *handlers) handleGetPlaylistCover(w http.ResponseWriter, r *http.Request) {
caller, ok := auth.UserFromContext(r.Context())
if !ok {
writeErr(w, http.StatusUnauthorized, "unauthenticated", "no session")
writeErr(w, apierror.Unauthorized("unauthenticated", "no session"))
return
}
playlistID, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid playlist id")
writeErr(w, apierror.BadRequest("bad_request", "invalid playlist id"))
return
}
detail, err := h.playlists.Get(r.Context(), caller.ID, playlistID)
@@ -403,7 +404,7 @@ func (h *handlers) handleGetPlaylistCover(w http.ResponseWriter, r *http.Request
return
}
if detail.CoverPath == nil || *detail.CoverPath == "" {
writeErr(w, http.StatusNotFound, "not_found", "no cover")
writeErr(w, &apierror.Error{Status: 404, Code: "not_found", Message: "no cover"})
return
}
full := filepath.Join(h.dataDir, *detail.CoverPath)
@@ -417,18 +418,18 @@ func (h *handlers) handleGetPlaylistCover(w http.ResponseWriter, r *http.Request
func (h *handlers) writePlaylistErr(w http.ResponseWriter, err error, op string) {
switch {
case errors.Is(err, playlists.ErrNotFound):
writeErr(w, http.StatusNotFound, "not_found", "playlist not found")
writeErr(w, apierror.NotFound("playlist"))
case errors.Is(err, playlists.ErrForbidden):
writeErr(w, http.StatusForbidden, "not_authorized", "you don't own this playlist")
writeErr(w, apierror.Forbidden("not_authorized", "you don't own this playlist"))
case errors.Is(err, playlists.ErrSystemPlaylistReadonly):
writeErr(w, http.StatusForbidden, "system_playlist_readonly", "system-generated playlists cannot be edited")
writeErr(w, apierror.Forbidden("system_playlist_readonly", "system-generated playlists cannot be edited"))
case errors.Is(err, playlists.ErrInvalidInput):
writeErr(w, http.StatusBadRequest, "bad_request", err.Error())
writeErr(w, apierror.BadRequest("bad_request", err.Error()))
case errors.Is(err, playlists.ErrTrackNotFound):
writeErr(w, http.StatusBadRequest, "bad_request", "one of the supplied track ids does not exist")
writeErr(w, apierror.BadRequest("bad_request", "one of the supplied track ids does not exist"))
default:
h.logger.Error("api: playlist op failed", "op", op, "err", err)
writeErr(w, http.StatusInternalServerError, "server_error", op+" failed")
writeErr(w, apierror.InternalMsg(op+" failed", err))
}
}