85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/recommendation"
|
|
)
|
|
|
|
// suggestionView is the wire shape returned by GET /api/discover/suggestions.
|
|
type suggestionView struct {
|
|
MBID string `json:"mbid"`
|
|
Name string `json:"name"`
|
|
Score float64 `json:"score"`
|
|
Attribution []seedContributionView `json:"attribution"`
|
|
}
|
|
|
|
type seedContributionView struct {
|
|
ArtistID pgtype.UUID `json:"artist_id"`
|
|
Name string `json:"name"`
|
|
Contribution float64 `json:"contribution"`
|
|
IsLiked bool `json:"is_liked"`
|
|
PlayCount int64 `json:"play_count"`
|
|
}
|
|
|
|
// handleListSuggestions implements GET /api/discover/suggestions.
|
|
//
|
|
// Query params:
|
|
// - limit (default 12, capped at 50)
|
|
// - half_life_days (default 30)
|
|
//
|
|
// Returns 200 with a JSON array (possibly empty). Read-only; no admin gate.
|
|
func (h *handlers) handleListSuggestions(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := requireUser(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
limit := 12
|
|
if v := r.URL.Query().Get("limit"); v != "" {
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil || n < 1 {
|
|
writeErr(w, apierror.BadRequest("bad_request", "invalid limit"))
|
|
return
|
|
}
|
|
limit = n
|
|
}
|
|
halfLife := 30.0
|
|
if v := r.URL.Query().Get("half_life_days"); v != "" {
|
|
f, err := strconv.ParseFloat(v, 64)
|
|
if err != nil || f <= 0 {
|
|
writeErr(w, apierror.BadRequest("bad_request", "invalid half_life_days"))
|
|
return
|
|
}
|
|
halfLife = f
|
|
}
|
|
|
|
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, apierror.InternalMsg("failed to load suggestions", err))
|
|
return
|
|
}
|
|
|
|
out := make([]suggestionView, 0, len(suggestions))
|
|
for _, s := range suggestions {
|
|
attr := make([]seedContributionView, 0, len(s.Attribution))
|
|
for _, a := range s.Attribution {
|
|
attr = append(attr, seedContributionView{
|
|
ArtistID: a.ArtistID,
|
|
Name: a.Name,
|
|
Contribution: a.Contribution,
|
|
IsLiked: a.IsLiked,
|
|
PlayCount: a.PlayCount,
|
|
})
|
|
}
|
|
out = append(out, suggestionView{
|
|
MBID: s.MBID, Name: s.Name, Score: s.Score, Attribution: attr,
|
|
})
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|