veza/veza-backend-api/internal/core/feed/handler.go
senke 4a422fc4c3 feat(v0.10.1): Tags & Genres discover - F351-F355
- Tags déclaratifs (max 10, 30 chars) via track_tags + tags
- Genres normalisés (max 3) via track_genres + taxonomy
- GET /api/v1/discover/genre/:genre, tag/:tag (browse chrono)
- POST/DELETE follow genre/tag
- Section feed "Nouvelles sorties dans vos genres"
- Track update: SyncTrackTags, SyncTrackGenres via discover service
- Frontend: discoverService, FeedPage by_genres, DiscoverPage
- Migration 126_tags_genres_discover
- MSW handlers for discover
2026-03-09 01:52:56 +01:00

71 lines
2 KiB
Go

package feed
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/handlers"
)
// Handler handles the chronological tracks feed (v0.10.0 F210)
type Handler struct {
service *Service
}
// NewHandler creates a new feed handler
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
// GetTracksFeed returns tracks from followed users, chronological, cursor-paginated.
// v0.10.1 F355: Adds by_genres section "Nouvelles sorties dans vos genres" when user follows genres.
// GET /api/v1/feed?cursor=...&limit=20&by_genres_cursor=...&by_genres_limit=10
// Requires authentication.
func (h *Handler) GetTracksFeed(c *gin.Context) {
userID, ok := handlers.GetUserIDUUID(c)
if !ok {
return
}
limitStr := c.DefaultQuery("limit", "20")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit < 1 || limit > 50 {
handlers.RespondWithAppError(c, apperrors.NewValidationError("limit must be between 1 and 50"))
return
}
cursor := c.Query("cursor")
byGenresCursor := c.Query("by_genres_cursor")
byGenresLimit := 10
if l, err := strconv.Atoi(c.DefaultQuery("by_genres_limit", "10")); err == nil && l > 0 && l <= 50 {
byGenresLimit = l
}
tracks, nextCursor, err := h.service.GetTracksFeed(c.Request.Context(), userID, limit, cursor)
if err != nil {
handlers.RespondWithAppError(c, apperrors.NewInternalErrorWrap("failed to get feed", err))
return
}
resp := gin.H{"items": tracks}
if nextCursor != "" {
resp["next_cursor"] = nextCursor
}
// v0.10.1 F355: Section "Nouvelles sorties dans vos genres"
if ds := h.service.GetDiscoverService(); ds != nil {
byGenresTracks, byGenresNext, err := ds.GetTracksFromFollowedGenres(c.Request.Context(), userID, byGenresLimit, byGenresCursor)
if err == nil {
section := gin.H{"items": byGenresTracks}
if byGenresNext != "" {
section["next_cursor"] = byGenresNext
}
resp["by_genres"] = section
}
}
handlers.RespondSuccess(c, http.StatusOK, resp)
}