feat(api): GET /api/albums/{id}/cover with sidecar fallback
This commit is contained in:
@@ -8,10 +8,15 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
@@ -96,3 +101,49 @@ func imageContentType(path string) string {
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
// handleGetCover implements GET /api/albums/{id}/cover. Resolves the cover
|
||||
// path (explicit column, else sidecar next to first track), then delegates
|
||||
// byte-serving to http.ServeContent so clients get Range / If-Modified-Since
|
||||
// / ETag for free.
|
||||
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")
|
||||
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")
|
||||
return
|
||||
}
|
||||
h.logger.Error("api: get album for cover failed", "err", err)
|
||||
writeErr(w, http.StatusInternalServerError, "server_error", "server error")
|
||||
return
|
||||
}
|
||||
path := resolveAlbumCoverPath(r.Context(), q, album)
|
||||
if path == "" {
|
||||
writeErr(w, http.StatusNotFound, "not_found", "cover not found")
|
||||
return
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
// resolveAlbumCoverPath saw the file a moment ago; if it vanished
|
||||
// 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")
|
||||
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")
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", imageContentType(path))
|
||||
http.ServeContent(w, r, filepath.Base(path), info.ModTime(), f)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user