Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
5.1 KiB
Go
118 lines
5.1 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/recommendation"
|
|
)
|
|
|
|
// handleGetHome implements GET /api/home. Returns five sections in one
|
|
// payload; sections that have no rows render as []. 500 on any underlying
|
|
// DB failure — the SPA should render either empty states or full content,
|
|
// not a half-broken page.
|
|
func (h *handlers) handleGetHome(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := requireUser(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := recommendation.HomeData(r.Context(), h.pool, user.ID)
|
|
if err != nil {
|
|
h.logger.Error("api: home data", "err", err)
|
|
writeErr(w, apierror.InternalMsg("failed to load home", err))
|
|
return
|
|
}
|
|
|
|
out := HomePayload{
|
|
RecentlyAddedAlbums: make([]AlbumRef, 0, len(data.RecentlyAddedAlbums)),
|
|
RediscoverAlbums: make([]AlbumRef, 0, len(data.RediscoverAlbums)),
|
|
RediscoverArtists: make([]ArtistRef, 0, len(data.RediscoverArtists)),
|
|
MostPlayedTracks: make([]TrackRef, 0, len(data.MostPlayedTracks)),
|
|
LastPlayedArtists: make([]ArtistRef, 0, len(data.LastPlayedArtists)),
|
|
YouMightLikeAlbums: make([]AlbumRef, 0, len(data.YouMightLikeAlbums)),
|
|
YouMightLikeArtists: make([]ArtistRef, 0, len(data.YouMightLikeArtists)),
|
|
}
|
|
for _, row := range data.RecentlyAddedAlbums {
|
|
out.RecentlyAddedAlbums = append(out.RecentlyAddedAlbums, albumRefFrom(row.Album, row.ArtistName, 0, 0))
|
|
}
|
|
for _, row := range data.RediscoverAlbums {
|
|
out.RediscoverAlbums = append(out.RediscoverAlbums, albumRefFrom(row.Album, row.ArtistName, 0, 0))
|
|
}
|
|
for _, row := range data.RediscoverArtists {
|
|
out.RediscoverArtists = append(out.RediscoverArtists, artistRefFromCovered(row.Artist, int(row.AlbumCount), row.CoverAlbumID))
|
|
}
|
|
for _, row := range data.MostPlayedTracks {
|
|
out.MostPlayedTracks = append(out.MostPlayedTracks, trackRefFrom(row.Track, row.AlbumTitle, row.ArtistName))
|
|
}
|
|
for _, row := range data.LastPlayedArtists {
|
|
out.LastPlayedArtists = append(out.LastPlayedArtists, artistRefFromCovered(row.Artist, int(row.AlbumCount), row.CoverAlbumID))
|
|
}
|
|
for _, row := range data.YouMightLikeAlbums {
|
|
out.YouMightLikeAlbums = append(out.YouMightLikeAlbums, albumRefFrom(row.Album, row.ArtistName, 0, 0))
|
|
}
|
|
for _, row := range data.YouMightLikeArtists {
|
|
out.YouMightLikeArtists = append(out.YouMightLikeArtists, artistRefFromCovered(row.Artist, int(row.AlbumCount), row.CoverAlbumID))
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
|
|
// handleGetHomeIndex implements GET /api/home/index. Returns the same
|
|
// five sections /api/home computes, but as flat slices of entity IDs
|
|
// rather than denormalized objects. Drives the Android per-item
|
|
// rendering path — the client hydrates each tile against the
|
|
// per-entity endpoints, so this response is tiny (a few KB at most)
|
|
// and the cold-visit round-trip is correspondingly short.
|
|
//
|
|
// Shares the recommendation.HomeData computation with /api/home; the
|
|
// DB cost is identical, and the wire payload is roughly an order of
|
|
// magnitude smaller on populated libraries.
|
|
func (h *handlers) handleGetHomeIndex(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := requireUser(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := recommendation.HomeData(r.Context(), h.pool, user.ID)
|
|
if err != nil {
|
|
h.logger.Error("api: home index data", "err", err)
|
|
writeErr(w, apierror.InternalMsg("failed to load home", err))
|
|
return
|
|
}
|
|
|
|
out := HomeIndexPayload{
|
|
RecentlyAddedAlbums: make([]string, 0, len(data.RecentlyAddedAlbums)),
|
|
RediscoverAlbums: make([]string, 0, len(data.RediscoverAlbums)),
|
|
RediscoverArtists: make([]string, 0, len(data.RediscoverArtists)),
|
|
MostPlayedTracks: make([]string, 0, len(data.MostPlayedTracks)),
|
|
LastPlayedArtists: make([]string, 0, len(data.LastPlayedArtists)),
|
|
YouMightLikeAlbums: make([]string, 0, len(data.YouMightLikeAlbums)),
|
|
YouMightLikeArtists: make([]string, 0, len(data.YouMightLikeArtists)),
|
|
}
|
|
for _, row := range data.RecentlyAddedAlbums {
|
|
out.RecentlyAddedAlbums = append(out.RecentlyAddedAlbums, uuidToString(row.Album.ID))
|
|
}
|
|
for _, row := range data.RediscoverAlbums {
|
|
out.RediscoverAlbums = append(out.RediscoverAlbums, uuidToString(row.Album.ID))
|
|
}
|
|
for _, row := range data.RediscoverArtists {
|
|
out.RediscoverArtists = append(out.RediscoverArtists, uuidToString(row.Artist.ID))
|
|
}
|
|
for _, row := range data.MostPlayedTracks {
|
|
out.MostPlayedTracks = append(out.MostPlayedTracks, uuidToString(row.Track.ID))
|
|
}
|
|
for _, row := range data.LastPlayedArtists {
|
|
out.LastPlayedArtists = append(out.LastPlayedArtists, uuidToString(row.Artist.ID))
|
|
}
|
|
for _, row := range data.YouMightLikeAlbums {
|
|
out.YouMightLikeAlbums = append(out.YouMightLikeAlbums, uuidToString(row.Album.ID))
|
|
}
|
|
for _, row := range data.YouMightLikeArtists {
|
|
out.YouMightLikeArtists = append(out.YouMightLikeArtists, uuidToString(row.Artist.ID))
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
|
|
// Compile-time guard that dbq has the row types we need; if the SQL is
|
|
// regenerated with a different name, this fails fast at build time.
|
|
var _ = dbq.ListMostPlayedTracksForUserRow{}.Track
|