package handlers import ( "context" "errors" "net/http" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // BitrateAdaptationServiceInterface defines methods needed for bitrate handler type BitrateAdaptationServiceInterface interface { AdaptBitrate(ctx context.Context, trackID, userID uuid.UUID, currentBitrate int, bandwidth int64, bufferLevel float64) (int, error) GetAnalytics(ctx context.Context, trackID uuid.UUID) (*services.BitrateAnalytics, error) } // BitrateHandler gère les requêtes pour l'adaptation de bitrate // T0349: Create Bitrate Adaptation Endpoint type BitrateHandler struct { adaptationService BitrateAdaptationServiceInterface commonHandler *CommonHandler } // NewBitrateHandler crée un nouveau handler de bitrate func NewBitrateHandler(adaptationService *services.BitrateAdaptationService, logger *zap.Logger) *BitrateHandler { return &BitrateHandler{ adaptationService: adaptationService, commonHandler: NewCommonHandler(logger), } } // NewBitrateHandlerWithInterface creates a new bitrate handler with interface (for testing) func NewBitrateHandlerWithInterface(adaptationService BitrateAdaptationServiceInterface, logger *zap.Logger) *BitrateHandler { return &BitrateHandler{ adaptationService: adaptationService, commonHandler: NewCommonHandler(logger), } } // AdaptBitrateRequest représente la requête pour adapter le bitrate type AdaptBitrateRequest struct { CurrentBitrate int `json:"current_bitrate" binding:"required" validate:"required"` Bandwidth int64 `json:"bandwidth" binding:"required" validate:"required"` BufferLevel float64 `json:"buffer_level" binding:"required" validate:"required"` } // AdaptBitrate gère la requête POST /api/v1/tracks/:id/bitrate/adapt // Reçoit les métriques de streaming et retourne le bitrate recommandé func (h *BitrateHandler) AdaptBitrate(c *gin.Context) { // Récupérer l'ID de l'utilisateur depuis le contexte (défini par le middleware d'authentification) userIDVal, exists := c.Get("user_id") if !exists { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } userID, ok := userIDVal.(uuid.UUID) if !ok || userID == uuid.Nil { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.NewUnauthorizedError("unauthorized")) return } // Récupérer l'ID du track depuis les paramètres de l'URL trackIDStr := c.Param("id") trackID, err := uuid.Parse(trackIDStr) if err != nil { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid track id")) return } // Valider et parser le body de la requête var req AdaptBitrateRequest if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil { RespondWithAppError(c, appErr) return } // Appeler le service d'adaptation de bitrate newBitrate, err := h.adaptationService.AdaptBitrate( c.Request.Context(), trackID, userID, req.CurrentBitrate, req.Bandwidth, req.BufferLevel, ) if err != nil { // Le service retourne des erreurs de validation avec des messages spécifiques // On peut distinguer les erreurs de validation des erreurs internes if errors.Is(err, services.ErrInvalidTrackID) || errors.Is(err, services.ErrInvalidUserID) || errors.Is(err, services.ErrInvalidBitrate) || errors.Is(err, services.ErrInvalidBufferLevel) { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, err.Error())) return } // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, err.Error())) return } // Retourner le bitrate recommandé // Action 1.3.2.1: Use wrapped format helper RespondSuccess(c, http.StatusOK, gin.H{"recommended_bitrate": newBitrate}) } // GetAnalytics gère la requête GET /api/v1/tracks/:id/bitrate/analytics // Retourne les statistiques d'adaptation de bitrate pour un track // T0354: Create Bitrate Adaptation Analytics Endpoint func (h *BitrateHandler) GetAnalytics(c *gin.Context) { // Récupérer l'ID du track depuis les paramètres de l'URL trackIDStr := c.Param("id") trackID, err := uuid.Parse(trackIDStr) if err != nil { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid track id")) return } // Récupérer les analytics depuis le service analytics, err := h.adaptationService.GetAnalytics(c.Request.Context(), trackID) if err != nil { if errors.Is(err, services.ErrInvalidTrackID) { // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid track id")) return } // MOD-P2-003: Utiliser AppError au lieu de gin.H RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, err.Error())) return } // Retourner les analytics // Action 1.3.2.1: Use wrapped format helper RespondSuccess(c, http.StatusOK, gin.H{"analytics": analytics}) }