949 lines
No EOL
28 KiB
Go
949 lines
No EOL
28 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"veza-backend-api/internal/models"
|
|
"veza-backend-api/internal/services"
|
|
"veza-backend-api/internal/validators"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// PlaylistHandler gère les opérations sur les playlists
|
|
type PlaylistHandler struct {
|
|
playlistService *services.PlaylistService
|
|
playlistAnalyticsService *services.PlaylistAnalyticsService
|
|
playlistFollowService *services.PlaylistFollowService
|
|
}
|
|
|
|
// NewPlaylistHandler crée un nouveau handler de playlists
|
|
func NewPlaylistHandler(playlistService *services.PlaylistService) *PlaylistHandler {
|
|
return &PlaylistHandler{playlistService: playlistService}
|
|
}
|
|
|
|
// SetPlaylistAnalyticsService définit le service d'analytics de playlist
|
|
// T0491: Create Playlist Analytics Backend
|
|
func (h *PlaylistHandler) SetPlaylistAnalyticsService(analyticsService *services.PlaylistAnalyticsService) {
|
|
h.playlistAnalyticsService = analyticsService
|
|
}
|
|
|
|
// SetPlaylistFollowService définit le service de follow de playlist
|
|
// T0498: Create Playlist Recommendations
|
|
func (h *PlaylistHandler) SetPlaylistFollowService(followService *services.PlaylistFollowService) {
|
|
h.playlistFollowService = followService
|
|
}
|
|
|
|
// CreatePlaylistRequest représente la requête pour créer une playlist
|
|
type CreatePlaylistRequest struct {
|
|
Title string `json:"title" binding:"required,min=1,max=200"`
|
|
Description string `json:"description,omitempty"`
|
|
IsPublic bool `json:"is_public"`
|
|
}
|
|
|
|
// UpdatePlaylistRequest représente la requête pour mettre à jour une playlist
|
|
type UpdatePlaylistRequest struct {
|
|
Title *string `json:"title,omitempty" binding:"omitempty,min=1,max=200"`
|
|
Description *string `json:"description,omitempty"`
|
|
IsPublic *bool `json:"is_public,omitempty"`
|
|
}
|
|
|
|
// ReorderTracksRequest représente la requête pour réorganiser les tracks
|
|
type ReorderTracksRequest struct {
|
|
TrackIDs []uuid.UUID `json:"track_ids" binding:"required,min=1"` // Changed to []uuid.UUID
|
|
}
|
|
|
|
// CreatePlaylist gère la création d'une playlist
|
|
// GO-013: Utilise validator centralisé pour validation améliorée
|
|
func (h *PlaylistHandler) CreatePlaylist(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
var req CreatePlaylistRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// GO-013: Utiliser validator pour messages d'erreur plus clairs
|
|
validator := validators.NewValidator()
|
|
if validationErrs := validator.Validate(&req); len(validationErrs) > 0 {
|
|
// Utiliser le format standardisé d'erreur de validation
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Validation failed",
|
|
"errors": validationErrs,
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
playlist, err := h.playlistService.CreatePlaylist(c.Request.Context(), userID, req.Title, req.Description, req.IsPublic)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{"playlist": playlist})
|
|
}
|
|
|
|
// GetPlaylists gère la récupération des playlists avec pagination
|
|
func (h *PlaylistHandler) GetPlaylists(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if limit < 1 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
|
|
// Filtres optionnels
|
|
var filterUserID *uuid.UUID
|
|
if filterUserIDStr := c.Query("user_id"); filterUserIDStr != "" {
|
|
if uid, err := uuid.Parse(filterUserIDStr); err == nil {
|
|
filterUserID = &uid
|
|
}
|
|
}
|
|
|
|
// Get current user ID
|
|
var currentUserID *uuid.UUID
|
|
if uidInterface, exists := c.Get("user_id"); exists {
|
|
if uid, ok := uidInterface.(uuid.UUID); ok {
|
|
currentUserID = &uid
|
|
}
|
|
}
|
|
|
|
playlists, total, err := h.playlistService.GetPlaylists(c.Request.Context(), currentUserID, filterUserID, page, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"playlists": playlists,
|
|
"total": total,
|
|
"page": page,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// GetPlaylist gère la récupération d'une playlist
|
|
func (h *PlaylistHandler) GetPlaylist(c *gin.Context) {
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
var currentUserID *uuid.UUID
|
|
if uidInterface, exists := c.Get("user_id"); exists {
|
|
if uid, ok := uidInterface.(uuid.UUID); ok {
|
|
currentUserID = &uid
|
|
}
|
|
}
|
|
|
|
playlist, err := h.playlistService.GetPlaylist(c.Request.Context(), playlistID, currentUserID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"playlist": playlist})
|
|
}
|
|
|
|
// UpdatePlaylist gère la mise à jour d'une playlist
|
|
func (h *PlaylistHandler) UpdatePlaylist(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
var req UpdatePlaylistRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
// GO-013: Utiliser validator pour messages d'erreur plus clairs
|
|
validator := validators.NewValidator()
|
|
if validationErrs := validator.Validate(&req); len(validationErrs) > 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Validation failed",
|
|
"errors": validationErrs,
|
|
})
|
|
return
|
|
}
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
playlist, err := h.playlistService.UpdatePlaylist(c.Request.Context(), playlistID, userID, req.Title, req.Description, req.IsPublic)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"playlist": playlist})
|
|
}
|
|
|
|
// DeletePlaylist gère la suppression d'une playlist
|
|
func (h *PlaylistHandler) DeletePlaylist(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.DeletePlaylist(c.Request.Context(), playlistID, userID); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "playlist deleted"})
|
|
}
|
|
|
|
// AddTrack gère l'ajout d'un track à une playlist
|
|
func (h *PlaylistHandler) AddTrack(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// Track IDs are uuid.UUID
|
|
trackID, err := uuid.Parse(c.Param("trackId")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.AddTrack(c.Request.Context(), playlistID, trackID, userID); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "track not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
|
|
return
|
|
}
|
|
if err.Error() == "track already in playlist" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "track already in playlist"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "track added to playlist"})
|
|
}
|
|
|
|
// RemoveTrack gère la suppression d'un track d'une playlist
|
|
func (h *PlaylistHandler) RemoveTrack(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// Track IDs are uuid.UUID
|
|
trackID, err := uuid.Parse(c.Param("trackId")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.RemoveTrack(c.Request.Context(), playlistID, trackID, userID); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "track not in playlist" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "track not in playlist"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "track removed from playlist"})
|
|
}
|
|
|
|
// ReorderTracks gère la réorganisation des tracks d'une playlist
|
|
func (h *PlaylistHandler) ReorderTracks(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
var req ReorderTracksRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.ReorderTracks(c.Request.Context(), playlistID, userID, req.TrackIDs); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "some tracks are not in the playlist" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "some tracks are not in the playlist"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "tracks reordered"})
|
|
}
|
|
|
|
// AddCollaboratorRequest représente la requête pour ajouter un collaborateur
|
|
type AddCollaboratorRequest struct {
|
|
UserID uuid.UUID `json:"user_id" binding:"required"`
|
|
Permission string `json:"permission" binding:"required,oneof=read write admin"`
|
|
}
|
|
|
|
// UpdateCollaboratorPermissionRequest représente la requête pour mettre à jour la permission d'un collaborateur
|
|
type UpdateCollaboratorPermissionRequest struct {
|
|
Permission string `json:"permission" binding:"required,oneof=read write admin"`
|
|
}
|
|
|
|
// AddCollaborator gère l'ajout d'un collaborateur à une playlist
|
|
// T0479: POST /api/v1/playlists/:id/collaborators
|
|
func (h *PlaylistHandler) AddCollaborator(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
var req AddCollaboratorRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Convertir la permission string en PlaylistPermission
|
|
var permission models.PlaylistPermission
|
|
switch req.Permission {
|
|
case "read":
|
|
permission = models.PlaylistPermissionRead
|
|
case "write":
|
|
permission = models.PlaylistPermissionWrite
|
|
case "admin":
|
|
permission = models.PlaylistPermissionAdmin
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission"})
|
|
return
|
|
}
|
|
|
|
collaborator, err := h.playlistService.AddCollaborator(c.Request.Context(), playlistID, userID, req.UserID, permission)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "user not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
if err.Error() == "user is already a collaborator" {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "user is already a collaborator"})
|
|
return
|
|
}
|
|
if err.Error() == "cannot add playlist owner as collaborator" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot add playlist owner as collaborator"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: only playlist owner can add collaborators" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{"collaborator": collaborator})
|
|
}
|
|
|
|
// RemoveCollaborator gère la suppression d'un collaborateur d'une playlist
|
|
// T0479: DELETE /api/v1/playlists/:id/collaborators/:userId
|
|
func (h *PlaylistHandler) RemoveCollaborator(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// User IDs are UUID
|
|
collaboratorUserID, err := uuid.Parse(c.Param("userId"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.RemoveCollaborator(c.Request.Context(), playlistID, userID, collaboratorUserID); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "collaborator not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "collaborator not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: only playlist owner can remove collaborators" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "collaborator removed"})
|
|
}
|
|
|
|
// UpdateCollaboratorPermission gère la mise à jour de la permission d'un collaborateur
|
|
// T0479: PUT /api/v1/playlists/:id/collaborators/:userId
|
|
func (h *PlaylistHandler) UpdateCollaboratorPermission(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// User IDs are UUID
|
|
collaboratorUserID, err := uuid.Parse(c.Param("userId"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
|
|
return
|
|
}
|
|
|
|
var req UpdateCollaboratorPermissionRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Convertir la permission string en PlaylistPermission
|
|
var permission models.PlaylistPermission
|
|
switch req.Permission {
|
|
case "read":
|
|
permission = models.PlaylistPermissionRead
|
|
case "write":
|
|
permission = models.PlaylistPermissionWrite
|
|
case "admin":
|
|
permission = models.PlaylistPermissionAdmin
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission"})
|
|
return
|
|
}
|
|
|
|
if err := h.playlistService.UpdateCollaboratorPermission(c.Request.Context(), playlistID, userID, collaboratorUserID, permission); err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "collaborator not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "collaborator not found"})
|
|
return
|
|
}
|
|
if err.Error() == "invalid permission" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid permission"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: only playlist owner can update collaborator permissions" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "collaborator permission updated"})
|
|
}
|
|
|
|
// GetCollaborators gère la récupération des collaborateurs d'une playlist
|
|
// T0479: GET /api/v1/playlists/:id/collaborators
|
|
func (h *PlaylistHandler) GetCollaborators(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
collaborators, err := h.playlistService.GetCollaborators(c.Request.Context(), playlistID, userID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: access denied" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"collaborators": collaborators})
|
|
}
|
|
|
|
// CreateShareLink gère la création d'un lien de partage public pour une playlist
|
|
// T0488: Create Playlist Public Share Link
|
|
func (h *PlaylistHandler) CreateShareLink(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// Créer le lien de partage via le service
|
|
// La vérification des permissions (owner ou admin) est faite dans PlaylistService.CreateShareLink
|
|
shareLink, err := h.playlistService.CreateShareLink(c.Request.Context(), playlistID, userID, nil)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: only owner or admin can create share links" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"share_link": shareLink})
|
|
}
|
|
|
|
// FollowPlaylist gère le follow d'une playlist
|
|
// T0489: Create Playlist Follow Feature
|
|
func (h *PlaylistHandler) FollowPlaylist(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
err = h.playlistService.FollowPlaylist(c.Request.Context(), playlistID, userID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "cannot follow own playlist" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot follow own playlist"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "playlist followed"})
|
|
}
|
|
|
|
// UnfollowPlaylist gère l'unfollow d'une playlist
|
|
// T0489: Create Playlist Follow Feature
|
|
func (h *PlaylistHandler) UnfollowPlaylist(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
err = h.playlistService.UnfollowPlaylist(c.Request.Context(), playlistID, userID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "playlist unfollowed"})
|
|
}
|
|
|
|
// GetPlaylistStats gère la récupération des statistiques d'une playlist
|
|
// T0491: Create Playlist Analytics Backend
|
|
func (h *PlaylistHandler) GetPlaylistStats(c *gin.Context) {
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
// Vérifier que la playlist existe et que l'utilisateur a accès
|
|
var userID *uuid.UUID
|
|
if uidInterface, exists := c.Get("user_id"); exists {
|
|
if uid, ok := uidInterface.(uuid.UUID); ok {
|
|
userID = &uid
|
|
}
|
|
}
|
|
|
|
playlist, err := h.playlistService.GetPlaylist(c.Request.Context(), playlistID, userID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Vérifier que l'utilisateur a accès (propriétaire, collaborateur ou playlist publique)
|
|
// Use uuid.Nil for comparison if userID is nil
|
|
currentUserID := uuid.Nil
|
|
if userID != nil {
|
|
currentUserID = *userID
|
|
}
|
|
|
|
if playlist.UserID != currentUserID && !playlist.IsPublic {
|
|
// Vérifier si l'utilisateur est collaborateur
|
|
if userID != nil {
|
|
hasAccess, err := h.playlistService.CheckPermission(c.Request.Context(), playlistID, *userID, models.PlaylistPermissionRead)
|
|
if err != nil || !hasAccess {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
} else {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Récupérer les statistiques via le service d'analytics
|
|
if h.playlistAnalyticsService == nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "analytics service not available"})
|
|
return
|
|
}
|
|
|
|
stats, err := h.playlistAnalyticsService.GetPlaylistStats(c.Request.Context(), playlistID)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
|
}
|
|
|
|
// DuplicatePlaylistRequest représente la requête pour dupliquer une playlist
|
|
type DuplicatePlaylistRequest struct {
|
|
NewTitle string `json:"new_title"`
|
|
NewDescription string `json:"new_description,omitempty"`
|
|
IsPublic *bool `json:"is_public,omitempty"`
|
|
}
|
|
|
|
// DuplicatePlaylist gère la duplication d'une playlist
|
|
// T0495: Create Playlist Duplicate Feature
|
|
func (h *PlaylistHandler) DuplicatePlaylist(c *gin.Context) {
|
|
// Playlist IDs are uuid.UUID
|
|
playlistID, err := uuid.Parse(c.Param("id")) // Changed to uuid.Parse
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid playlist id"})
|
|
return
|
|
}
|
|
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
var req DuplicatePlaylistRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Créer le service de duplication
|
|
duplicateService := services.NewPlaylistDuplicateService(h.playlistService, nil)
|
|
|
|
// Dupliquer la playlist
|
|
newPlaylist, err := duplicateService.DuplicatePlaylist(
|
|
c.Request.Context(),
|
|
playlistID,
|
|
userID,
|
|
services.DuplicatePlaylistRequest{
|
|
NewTitle: req.NewTitle,
|
|
NewDescription: req.NewDescription,
|
|
IsPublic: req.IsPublic,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if err.Error() == "playlist not found" {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "playlist not found"})
|
|
return
|
|
}
|
|
if err.Error() == "forbidden: you don't have access to this playlist" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "playlist duplicated successfully",
|
|
"playlist": newPlaylist,
|
|
})
|
|
}
|
|
|
|
// SearchPlaylists gère la recherche de playlists
|
|
// T0496: Create Playlist Search Backend
|
|
func (h *PlaylistHandler) SearchPlaylists(c *gin.Context) {
|
|
// Get current user ID
|
|
var currentUserID *uuid.UUID
|
|
if uidInterface, exists := c.Get("user_id"); exists {
|
|
if uid, ok := uidInterface.(uuid.UUID); ok {
|
|
currentUserID = &uid
|
|
}
|
|
}
|
|
|
|
// Récupérer les paramètres de recherche
|
|
query := c.Query("q")
|
|
userIDParam := c.Query("user_id")
|
|
isPublicParam := c.Query("is_public")
|
|
pageParam := c.DefaultQuery("page", "1")
|
|
limitParam := c.DefaultQuery("limit", "20")
|
|
|
|
// Parser les paramètres
|
|
var filterUserID *uuid.UUID
|
|
if userIDParam != "" {
|
|
if parsed, err := uuid.Parse(userIDParam); err == nil {
|
|
filterUserID = &parsed
|
|
}
|
|
}
|
|
|
|
var filterIsPublic *bool
|
|
if isPublicParam != "" {
|
|
if parsed, err := strconv.ParseBool(isPublicParam); err == nil {
|
|
filterIsPublic = &parsed
|
|
}
|
|
}
|
|
|
|
page, err := strconv.Atoi(pageParam)
|
|
if err != nil || page < 1 {
|
|
page = 1
|
|
}
|
|
|
|
limit, err := strconv.Atoi(limitParam)
|
|
if err != nil || limit < 1 {
|
|
limit = 20
|
|
}
|
|
|
|
// Rechercher les playlists
|
|
playlists, total, err := h.playlistService.SearchPlaylists(c.Request.Context(), services.SearchPlaylistsParams{
|
|
Query: query,
|
|
UserID: filterUserID,
|
|
IsPublic: filterIsPublic,
|
|
Page: page,
|
|
Limit: limit,
|
|
CurrentUserID: currentUserID,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"playlists": playlists,
|
|
"total": total,
|
|
"page": page,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// GetRecommendations gère la récupération des recommandations de playlists
|
|
// T0498: Create Playlist Recommendations
|
|
func (h *PlaylistHandler) GetRecommendations(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(uuid.UUID)
|
|
if userID == uuid.Nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
|
|
// Parser les paramètres de requête
|
|
limitParam := c.DefaultQuery("limit", "20")
|
|
limit, err := strconv.Atoi(limitParam)
|
|
if err != nil || limit < 1 {
|
|
limit = 20
|
|
}
|
|
if limit > 100 {
|
|
limit = 100
|
|
}
|
|
|
|
minScoreParam := c.DefaultQuery("min_score", "0.1")
|
|
minScore, err := strconv.ParseFloat(minScoreParam, 64)
|
|
if err != nil || minScore < 0 {
|
|
minScore = 0.1
|
|
}
|
|
|
|
includeOwnParam := c.DefaultQuery("include_own", "false")
|
|
includeOwn := includeOwnParam == "true"
|
|
|
|
// Créer le service de recommandations
|
|
recommendationService := services.NewPlaylistRecommendationService(
|
|
nil, // Le service utilisera les services injectés via les interfaces
|
|
h.playlistService,
|
|
h.playlistFollowService,
|
|
nil, // logger
|
|
)
|
|
|
|
// Obtenir les recommandations
|
|
recommendations, err := recommendationService.GetRecommendations(
|
|
c.Request.Context(),
|
|
services.GetRecommendationsParams{
|
|
UserID: userID,
|
|
Limit: limit,
|
|
MinScore: minScore,
|
|
IncludeOwn: includeOwn,
|
|
},
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Formater la réponse
|
|
response := make([]gin.H, 0, len(recommendations))
|
|
for _, rec := range recommendations {
|
|
response = append(response, gin.H{
|
|
"playlist": rec.Playlist,
|
|
"score": rec.Score,
|
|
"reason": rec.Reason,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"recommendations": response,
|
|
"count": len(response),
|
|
})
|
|
} |