feat(api): add GET /api/library/albums handler
Implements the paged alpha-sorted album list endpoint used by the M6a wrapping-grid SPA page. Mirrors handleListArtists alpha branch using ListAlbumsAlphaWithArtist + CountAlbums; 2/2 integration tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
// handleListLibraryAlbums implements GET /api/library/albums. Mirrors
|
||||
// /api/artists?sort=alpha but for albums. The new wrapping-grid page on
|
||||
// the SPA infinite-scrolls against this endpoint via TanStack
|
||||
// createInfiniteQuery.
|
||||
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())
|
||||
return
|
||||
}
|
||||
q := dbq.New(h.pool)
|
||||
rows, err := q.ListAlbumsAlphaWithArtist(r.Context(), dbq.ListAlbumsAlphaWithArtistParams{
|
||||
Limit: int32(limit), Offset: int32(offset),
|
||||
})
|
||||
if err != nil {
|
||||
h.logger.Error("api: list library albums", "err", err)
|
||||
writeErr(w, http.StatusInternalServerError, "server_error", "lookup failed")
|
||||
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")
|
||||
return
|
||||
}
|
||||
items := make([]AlbumRef, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, albumRefFrom(row.Album, row.ArtistName, 0, 0))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, Page[AlbumRef]{
|
||||
Items: items, Total: int(total), Limit: limit, Offset: offset,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
func newLibraryAlbumsRouter(h *handlers) chi.Router {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/api/library/albums", h.handleListLibraryAlbums)
|
||||
return r
|
||||
}
|
||||
|
||||
func doListLibraryAlbums(h *handlers, user dbq.User, query string) *httptest.ResponseRecorder {
|
||||
url := "/api/library/albums"
|
||||
if query != "" {
|
||||
url += "?" + query
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
req = withUser(req, user)
|
||||
w := httptest.NewRecorder()
|
||||
newLibraryAlbumsRouter(h).ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func TestLibraryAlbums_AlphaSorted(t *testing.T) {
|
||||
h, pool := testHandlers(t)
|
||||
user := seedUser(t, pool, "alice", "pw", false)
|
||||
q := dbq.New(pool)
|
||||
artist, _ := q.UpsertArtist(context.Background(), dbq.UpsertArtistParams{
|
||||
Name: "ArtistA", SortName: "ArtistA",
|
||||
})
|
||||
_, _ = q.UpsertAlbum(context.Background(), dbq.UpsertAlbumParams{
|
||||
Title: "Zoo", SortTitle: "Zoo", ArtistID: artist.ID,
|
||||
})
|
||||
_, _ = q.UpsertAlbum(context.Background(), dbq.UpsertAlbumParams{
|
||||
Title: "Apple", SortTitle: "Apple", ArtistID: artist.ID,
|
||||
})
|
||||
_, _ = q.UpsertAlbum(context.Background(), dbq.UpsertAlbumParams{
|
||||
Title: "Mango", SortTitle: "Mango", ArtistID: artist.ID,
|
||||
})
|
||||
|
||||
w := doListLibraryAlbums(h, user, "limit=10")
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
|
||||
}
|
||||
var got Page[AlbumRef]
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if got.Total != 3 {
|
||||
t.Errorf("total = %d, want 3", got.Total)
|
||||
}
|
||||
titles := []string{got.Items[0].Title, got.Items[1].Title, got.Items[2].Title}
|
||||
if titles[0] != "Apple" || titles[1] != "Mango" || titles[2] != "Zoo" {
|
||||
t.Errorf("titles = %v, want [Apple Mango Zoo]", titles)
|
||||
}
|
||||
if got.Items[0].SortTitle != "Apple" {
|
||||
t.Errorf("sort_title = %q, want Apple", got.Items[0].SortTitle)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibraryAlbums_Paging(t *testing.T) {
|
||||
h, pool := testHandlers(t)
|
||||
user := seedUser(t, pool, "alice", "pw", false)
|
||||
q := dbq.New(pool)
|
||||
artist, _ := q.UpsertArtist(context.Background(), dbq.UpsertArtistParams{
|
||||
Name: "ArtistA", SortName: "ArtistA",
|
||||
})
|
||||
for _, title := range []string{"A", "B", "C", "D"} {
|
||||
_, _ = q.UpsertAlbum(context.Background(), dbq.UpsertAlbumParams{
|
||||
Title: title, SortTitle: title, ArtistID: artist.ID,
|
||||
})
|
||||
}
|
||||
|
||||
w := doListLibraryAlbums(h, user, "limit=2&offset=2")
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d", w.Code)
|
||||
}
|
||||
var got Page[AlbumRef]
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &got)
|
||||
if got.Total != 4 || got.Limit != 2 || got.Offset != 2 {
|
||||
t.Errorf("envelope = %+v, want total=4 limit=2 offset=2", got)
|
||||
}
|
||||
if len(got.Items) != 2 || got.Items[0].Title != "C" || got.Items[1].Title != "D" {
|
||||
t.Errorf("items = %v, want [C D]", got.Items)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user