101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/playlists"
|
|
)
|
|
|
|
// foryouRefreshResp is the wire shape for POST
|
|
// /api/playlists/system/for-you/refresh. track_ids is the playlist's
|
|
// tracks in playlist position order — the frontend enqueues this
|
|
// list directly to start playback without a second roundtrip.
|
|
//
|
|
// playlist_id is null + track_ids empty when the build succeeded but
|
|
// the user's library yielded no eligible candidates (degenerate
|
|
// empty-library case).
|
|
type foryouRefreshResp struct {
|
|
PlaylistID *string `json:"playlist_id"`
|
|
TrackCount int32 `json:"track_count"`
|
|
TrackIDs []string `json:"track_ids"`
|
|
}
|
|
|
|
// handleForYouRefresh re-runs BuildSystemPlaylists synchronously for
|
|
// the calling user and returns their freshly-built For-You playlist.
|
|
// Used by the home Playlists row's For-You tile play button: click
|
|
// triggers refresh, the response carries the track IDs, the
|
|
// frontend enqueues + auto-plays.
|
|
//
|
|
// Authenticated user only — each user refreshes only their own
|
|
// For-You.
|
|
func (h *handlers) handleForYouRefresh(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := auth.UserFromContext(r.Context())
|
|
if !ok {
|
|
writeErr(w, apierror.Unauthorized("auth_required", ""))
|
|
return
|
|
}
|
|
if err := playlists.BuildSystemPlaylists(r.Context(), h.pool, h.logger, user.ID, time.Now(), h.dataDir); err != nil {
|
|
h.logger.Error("for-you refresh: build failed",
|
|
"user_id", uuidToString(user.ID), "err", err)
|
|
writeErr(w, apierror.InternalMsg("build failed", err))
|
|
return
|
|
}
|
|
|
|
q := dbq.New(h.pool)
|
|
sysVariant := "for_you"
|
|
pl, err := q.GetSystemPlaylistByVariantForUser(r.Context(),
|
|
dbq.GetSystemPlaylistByVariantForUserParams{
|
|
UserID: user.ID,
|
|
SystemVariant: &sysVariant,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
// Build succeeded but no For-You row — empty library.
|
|
writeJSON(w, http.StatusOK, foryouRefreshResp{
|
|
PlaylistID: nil,
|
|
TrackCount: 0,
|
|
TrackIDs: []string{},
|
|
})
|
|
return
|
|
}
|
|
h.logger.Error("for-you refresh: lookup failed",
|
|
"user_id", uuidToString(user.ID), "err", err)
|
|
writeErr(w, apierror.InternalMsg("lookup failed", err))
|
|
return
|
|
}
|
|
|
|
// Fetch tracks in playlist position order so the frontend can
|
|
// enqueue them directly.
|
|
rows, err := q.ListPlaylistTracks(r.Context(), pl.ID)
|
|
if err != nil {
|
|
h.logger.Error("for-you refresh: list tracks failed",
|
|
"playlist_id", uuidToString(pl.ID), "err", err)
|
|
writeErr(w, apierror.InternalMsg("list tracks failed", err))
|
|
return
|
|
}
|
|
trackIDs := make([]string, 0, len(rows))
|
|
for _, row := range rows {
|
|
// Skip rows where the track was removed from the library
|
|
// (LiveTrackID won't be Valid). The snapshot row still
|
|
// exists in playlist_tracks, but there's nothing to enqueue.
|
|
if !row.LiveTrackID.Valid {
|
|
continue
|
|
}
|
|
trackIDs = append(trackIDs, uuidToString(row.LiveTrackID))
|
|
}
|
|
|
|
idStr := uuidToString(pl.ID)
|
|
writeJSON(w, http.StatusOK, foryouRefreshResp{
|
|
PlaylistID: &idStr,
|
|
TrackCount: pl.TrackCount,
|
|
TrackIDs: trackIDs,
|
|
})
|
|
}
|