package handlers import ( "net/http" "strconv" "veza-backend-api/internal/models" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "gorm.io/gorm" ) // PlaylistHandler gère les opérations sur les playlists type PlaylistHandler struct { playlistService *services.PlaylistService playlistAnalyticsService *services.PlaylistAnalyticsService playlistFollowService *services.PlaylistFollowService db *gorm.DB commonHandler *CommonHandler } // NewPlaylistHandler crée un nouveau handler de playlists func NewPlaylistHandler(playlistService *services.PlaylistService, db *gorm.DB, logger *zap.Logger) *PlaylistHandler { return &PlaylistHandler{ playlistService: playlistService, db: db, commonHandler: NewCommonHandler(logger), } } // 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) 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 appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } // Créer le service de duplication duplicateService := services.NewPlaylistDuplicateService(h.playlistService, h.db, 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), }) }