package api import ( "net/http" "strconv" "github.com/jackc/pgx/v5/pgtype" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "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 := auth.UserFromContext(r.Context()) if !ok { writeErr(w, http.StatusUnauthorized, "unauthorized", "authentication required") return } limit := 12 if v := r.URL.Query().Get("limit"); v != "" { n, err := strconv.Atoi(v) if err != nil || n < 1 { writeErr(w, http.StatusBadRequest, "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, http.StatusBadRequest, "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, http.StatusInternalServerError, "server_error", "failed to load suggestions") 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) }