veza/veza-backend-api/internal/handlers/playback_analytics_handler.go

809 lines
27 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package handlers
import (
"context"
"fmt"
"github.com/google/uuid"
"math"
"net/http"
"strconv"
"time"
"veza-backend-api/internal/dto"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"github.com/gin-gonic/gin"
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
"go.uber.org/zap"
2025-12-03 19:29:37 +00:00
)
// PlaybackAnalyticsHandler gère les requêtes pour les analytics de lecture
// T0358: Create Playback Analytics Endpoint
type PlaybackAnalyticsHandler struct {
analyticsService *services.PlaybackAnalyticsService
heatmapService *services.PlaybackHeatmapService
rateLimiter *services.PlaybackAnalyticsRateLimiter // T0389: Create Playback Analytics Rate Limiting
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
commonHandler *CommonHandler
2025-12-03 19:29:37 +00:00
}
// NewPlaybackAnalyticsHandler crée un nouveau handler d'analytics de lecture
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
func NewPlaybackAnalyticsHandler(analyticsService *services.PlaybackAnalyticsService, logger *zap.Logger) *PlaybackAnalyticsHandler {
2025-12-03 19:29:37 +00:00
return &PlaybackAnalyticsHandler{
analyticsService: analyticsService,
heatmapService: nil,
rateLimiter: nil, // Rate limiter optionnel
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
commonHandler: NewCommonHandler(logger),
2025-12-03 19:29:37 +00:00
}
}
// NewPlaybackAnalyticsHandlerWithRateLimiter crée un nouveau handler avec rate limiter
// T0389: Create Playback Analytics Rate Limiting
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
func NewPlaybackAnalyticsHandlerWithRateLimiter(analyticsService *services.PlaybackAnalyticsService, rateLimiter *services.PlaybackAnalyticsRateLimiter, logger *zap.Logger) *PlaybackAnalyticsHandler {
2025-12-03 19:29:37 +00:00
return &PlaybackAnalyticsHandler{
analyticsService: analyticsService,
heatmapService: nil,
rateLimiter: rateLimiter,
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
commonHandler: NewCommonHandler(logger),
2025-12-03 19:29:37 +00:00
}
}
// NewPlaybackAnalyticsHandlerWithHeatmap crée un nouveau handler avec service heatmap
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
func NewPlaybackAnalyticsHandlerWithHeatmap(analyticsService *services.PlaybackAnalyticsService, heatmapService *services.PlaybackHeatmapService, logger *zap.Logger) *PlaybackAnalyticsHandler {
2025-12-03 19:29:37 +00:00
return &PlaybackAnalyticsHandler{
analyticsService: analyticsService,
heatmapService: heatmapService,
rateLimiter: nil,
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
commonHandler: NewCommonHandler(logger),
2025-12-03 19:29:37 +00:00
}
}
// NewPlaybackAnalyticsHandlerFull crée un nouveau handler avec tous les services
// T0389: Create Playback Analytics Rate Limiting
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
func NewPlaybackAnalyticsHandlerFull(analyticsService *services.PlaybackAnalyticsService, heatmapService *services.PlaybackHeatmapService, rateLimiter *services.PlaybackAnalyticsRateLimiter, logger *zap.Logger) *PlaybackAnalyticsHandler {
2025-12-03 19:29:37 +00:00
return &PlaybackAnalyticsHandler{
analyticsService: analyticsService,
heatmapService: heatmapService,
rateLimiter: rateLimiter,
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
commonHandler: NewCommonHandler(logger),
2025-12-03 19:29:37 +00:00
}
}
// RecordAnalyticsRequest représente la requête pour enregistrer des analytics de lecture
// T0388: Create Playback Analytics Validation - Amélioré avec validation
type RecordAnalyticsRequest struct {
PlayTime int `json:"play_time" binding:"required,min=0"` // seconds
PauseCount int `json:"pause_count" binding:"min=0"` // optional, default 0
SeekCount int `json:"seek_count" binding:"min=0"` // optional, default 0
CompletionRate *float64 `json:"completion_rate,omitempty"` // optional, will be calculated if not provided
StartedAt time.Time `json:"started_at" binding:"required"` // ISO 8601 format
EndedAt *time.Time `json:"ended_at,omitempty"` // optional
}
// ValidationResult représente le résultat d'une validation
// T0388: Create Playback Analytics Validation
// GO-013: Utilise dto.ValidationError pour éviter les cycles d'import
type ValidationResult struct {
Valid bool
Errors []dto.ValidationError
Sanitized *RecordAnalyticsRequest
}
// RecordAnalytics gère la requête POST /api/v1/tracks/:id/playback/analytics
// Enregistre les analytics de lecture pour un track
// T0358: Create Playback Analytics Endpoint
func (h *PlaybackAnalyticsHandler) RecordAnalytics(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "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 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
// Valider et parser le body de la requête
var req RecordAnalyticsRequest
P0: stabilisation backend/chat/stream + nouvelle base migrations v1 Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 10:14:38 +00:00
if appErr := h.commonHandler.BindAndValidateJSON(c, &req); appErr != nil {
RespondWithAppError(c, appErr)
2025-12-03 19:29:37 +00:00
return
}
// T0388: Create Playback Analytics Validation
// Valider et sanitizer les données
validationResult := h.validateAndSanitizeAnalyticsRequest(&req, trackID)
if !validationResult.Valid {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Validation failed",
"errors": validationResult.Errors,
})
return
}
// Utiliser les données sanitizées
req = *validationResult.Sanitized
// T0389: Create Playback Analytics Rate Limiting
// Vérifier le rate limiting si activé
if h.rateLimiter != nil {
rateLimitResult, err := h.rateLimiter.CheckRateLimit(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to check rate limit"})
return
}
if !rateLimitResult.Allowed {
// Ajouter les headers de rate limiting
c.Header("X-RateLimit-Remaining", "0")
c.Header("X-RateLimit-Retry-After", strconv.FormatInt(int64(rateLimitResult.RetryAfter.Seconds()), 10))
c.Header("X-RateLimit-Reason", rateLimitResult.Reason)
c.JSON(http.StatusTooManyRequests, gin.H{
"error": "Rate limit exceeded",
"reason": rateLimitResult.Reason,
"retry_after": int(rateLimitResult.RetryAfter.Seconds()),
"quota_used": rateLimitResult.QuotaUsed,
"quota_limit": rateLimitResult.QuotaLimit,
})
return
}
// Ajouter les headers de rate limiting
c.Header("X-RateLimit-Remaining", strconv.Itoa(rateLimitResult.Remaining))
}
// Créer le modèle PlaybackAnalytics
analytics := &models.PlaybackAnalytics{
TrackID: trackID,
UserID: userID,
PlayTime: req.PlayTime,
PauseCount: req.PauseCount,
SeekCount: req.SeekCount,
StartedAt: req.StartedAt,
EndedAt: req.EndedAt,
}
// Définir le completion_rate si fourni
if req.CompletionRate != nil {
analytics.CompletionRate = *req.CompletionRate
}
// Enregistrer les analytics via le service
err = h.analyticsService.RecordPlayback(c.Request.Context(), analytics)
if err != nil {
// Gérer les erreurs spécifiques
if err.Error() == "invalid track ID: 0" ||
err.Error() == "invalid user ID: 0" ||
err.Error()[:14] == "invalid play time" ||
err.Error()[:14] == "invalid pause" ||
err.Error()[:14] == "invalid seek" ||
err.Error()[:14] == "invalid completion" ||
err.Error() == "started_at is required" {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err.Error()[:13] == "track not found" {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// T0389: Create Playback Analytics Rate Limiting
// Enregistrer la requête dans le rate limiter si activé
if h.rateLimiter != nil {
if err := h.rateLimiter.RecordRequest(c.Request.Context(), userID); err != nil {
// Logger l'erreur mais ne pas échouer la requête
// Le rate limiting est une fonctionnalité de protection, pas critique
}
}
// Retourner le succès
RespondSuccess(c, http.StatusOK, gin.H{
2025-12-03 19:29:37 +00:00
"status": "recorded",
"id": analytics.ID,
})
}
// GetQuotaInfo gère la requête GET /api/v1/playback/analytics/quota
// Retourne les informations de quota pour l'utilisateur actuel
// T0389: Create Playback Analytics Rate Limiting
func (h *PlaybackAnalyticsHandler) GetQuotaInfo(c *gin.Context) {
// Récupérer l'ID de l'utilisateur depuis le contexte
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
if h.rateLimiter == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "rate limiting not enabled"})
return
}
quotaInfo, err := h.rateLimiter.GetQuotaInfo(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get quota info"})
return
}
RespondSuccess(c, http.StatusOK, gin.H{
2025-12-03 19:29:37 +00:00
"quota": quotaInfo,
})
}
// DashboardData représente les données du dashboard d'analytics
// T0363: Create Playback Analytics Dashboard Endpoint
type DashboardData struct {
Stats *services.PlaybackStats `json:"stats"`
Trends *TrendsData `json:"trends"`
TimeSeries []TimeSeriesPoint `json:"time_series"`
}
// TrendsData représente les tendances d'analytics
type TrendsData struct {
PlayTimeTrend float64 `json:"play_time_trend"` // % de changement sur 7 jours
CompletionTrend float64 `json:"completion_trend"` // % de changement sur 7 jours
SessionsTrend float64 `json:"sessions_trend"` // % de changement sur 7 jours
AveragePlayTime float64 `json:"average_play_time"` // Moyenne sur 7 jours
AverageCompletion float64 `json:"average_completion"` // Moyenne sur 7 jours
TotalSessions7Days int64 `json:"total_sessions_7days"` // Total sur 7 jours
TotalSessions30Days int64 `json:"total_sessions_30days"` // Total sur 30 jours
}
// TimeSeriesPoint représente un point dans une série temporelle
type TimeSeriesPoint struct {
Date string `json:"date"` // Format: YYYY-MM-DD
Sessions int64 `json:"sessions"`
TotalPlayTime int64 `json:"total_play_time"` // seconds
AveragePlayTime float64 `json:"average_play_time"` // seconds
AverageCompletion float64 `json:"average_completion"` // percentage
}
// GetDashboard gère la requête GET /api/v1/tracks/:id/playback/dashboard
// Retourne les statistiques agrégées, graphiques et tendances pour un track
// T0363: Create Playback Analytics Dashboard Endpoint
func (h *PlaybackAnalyticsHandler) GetDashboard(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 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
if trackID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
// Récupérer les statistiques globales
stats, err := h.analyticsService.GetTrackStats(c.Request.Context(), trackID)
if err != nil {
errMsg := err.Error()
if len(errMsg) >= 13 && errMsg[:13] == "track not found" {
c.JSON(http.StatusNotFound, gin.H{"error": errMsg})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
// Calculer les tendances (comparaison 7 jours vs 14-7 jours)
trends, err := h.calculateTrends(c.Request.Context(), trackID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to calculate trends: " + err.Error()})
return
}
// Calculer les séries temporelles (30 derniers jours)
timeSeries, err := h.calculateTimeSeries(c.Request.Context(), trackID, 30)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to calculate time series: " + err.Error()})
return
}
// Construire la réponse
dashboard := DashboardData{
Stats: stats,
Trends: trends,
TimeSeries: timeSeries,
}
RespondSuccess(c, http.StatusOK, gin.H{
2025-12-03 19:29:37 +00:00
"dashboard": dashboard,
})
}
// calculateTrends calcule les tendances d'analytics
func (h *PlaybackAnalyticsHandler) calculateTrends(ctx context.Context, trackID uuid.UUID) (*TrendsData, error) {
now := time.Now()
sevenDaysAgo := now.AddDate(0, 0, -7)
fourteenDaysAgo := now.AddDate(0, 0, -14)
thirtyDaysAgo := now.AddDate(0, 0, -30)
// Statistiques sur les 7 derniers jours
stats7Days, err := h.getStatsForDateRange(ctx, trackID, sevenDaysAgo, now)
if err != nil {
return nil, err
}
// Statistiques sur les 7 jours précédents (14-7 jours)
statsPrev7Days, err := h.getStatsForDateRange(ctx, trackID, fourteenDaysAgo, sevenDaysAgo)
if err != nil {
return nil, err
}
// Statistiques sur les 30 derniers jours
stats30Days, err := h.getStatsForDateRange(ctx, trackID, thirtyDaysAgo, now)
if err != nil {
return nil, err
}
trends := &TrendsData{
TotalSessions7Days: stats7Days.TotalSessions,
TotalSessions30Days: stats30Days.TotalSessions,
AveragePlayTime: stats7Days.AveragePlayTime,
AverageCompletion: stats7Days.AverageCompletion,
}
// Calculer les tendances en pourcentage
if statsPrev7Days.TotalSessions > 0 {
// Tendance des sessions
trends.SessionsTrend = float64(stats7Days.TotalSessions-statsPrev7Days.TotalSessions) / float64(statsPrev7Days.TotalSessions) * 100.0
} else if stats7Days.TotalSessions > 0 {
trends.SessionsTrend = 100.0 // Nouvelle donnée
}
if statsPrev7Days.AveragePlayTime > 0 {
// Tendance du temps de lecture
trends.PlayTimeTrend = (stats7Days.AveragePlayTime - statsPrev7Days.AveragePlayTime) / statsPrev7Days.AveragePlayTime * 100.0
} else if stats7Days.AveragePlayTime > 0 {
trends.PlayTimeTrend = 100.0 // Nouvelle donnée
}
if statsPrev7Days.AverageCompletion > 0 {
// Tendance du taux de complétion
trends.CompletionTrend = (stats7Days.AverageCompletion - statsPrev7Days.AverageCompletion) / statsPrev7Days.AverageCompletion * 100.0
} else if stats7Days.AverageCompletion > 0 {
trends.CompletionTrend = 100.0 // Nouvelle donnée
}
return trends, nil
}
// getStatsForDateRange récupère les statistiques pour une plage de dates
func (h *PlaybackAnalyticsHandler) getStatsForDateRange(ctx context.Context, trackID uuid.UUID, startDate, endDate time.Time) (*services.PlaybackStats, error) {
sessions, err := h.analyticsService.GetSessionsByDateRange(ctx, trackID, startDate, endDate)
if err != nil {
return nil, err
}
if len(sessions) == 0 {
return &services.PlaybackStats{}, nil
}
var totalPlayTime int64
var totalPauses int64
var totalSeeks int64
var totalCompletion float64
for _, session := range sessions {
totalPlayTime += int64(session.PlayTime)
totalPauses += int64(session.PauseCount)
totalSeeks += int64(session.SeekCount)
totalCompletion += session.CompletionRate
}
totalSessions := int64(len(sessions))
avgPlayTime := float64(totalPlayTime) / float64(totalSessions)
avgPauses := float64(totalPauses) / float64(totalSessions)
avgSeeks := float64(totalSeeks) / float64(totalSessions)
avgCompletion := totalCompletion / float64(totalSessions)
// Compter les sessions complétées (>90%)
var completedSessions int64
for _, session := range sessions {
if session.CompletionRate >= 90 {
completedSessions++
}
}
completionRate := float64(completedSessions) / float64(totalSessions) * 100.0
return &services.PlaybackStats{
TotalSessions: totalSessions,
TotalPlayTime: totalPlayTime,
AveragePlayTime: avgPlayTime,
TotalPauses: totalPauses,
AveragePauses: avgPauses,
TotalSeeks: totalSeeks,
AverageSeeks: avgSeeks,
AverageCompletion: avgCompletion,
CompletionRate: completionRate,
}, nil
}
// calculateTimeSeries calcule les séries temporelles pour les N derniers jours
func (h *PlaybackAnalyticsHandler) calculateTimeSeries(ctx context.Context, trackID uuid.UUID, days int) ([]TimeSeriesPoint, error) {
now := time.Now()
startDate := now.AddDate(0, 0, -days)
// Récupérer toutes les sessions dans la plage
sessions, err := h.analyticsService.GetSessionsByDateRange(ctx, trackID, startDate, now)
if err != nil {
return nil, err
}
// Grouper par jour
dailyStats := make(map[string]*dailyStat)
for _, session := range sessions {
dateKey := session.CreatedAt.Format("2006-01-02")
if dailyStats[dateKey] == nil {
dailyStats[dateKey] = &dailyStat{}
}
stat := dailyStats[dateKey]
stat.sessions++
stat.totalPlayTime += int64(session.PlayTime)
stat.totalCompletion += session.CompletionRate
}
// Créer les points de série temporelle pour tous les jours
var timeSeries []TimeSeriesPoint
for i := days - 1; i >= 0; i-- {
date := now.AddDate(0, 0, -i)
dateKey := date.Format("2006-01-02")
stat := dailyStats[dateKey]
if stat == nil {
stat = &dailyStat{}
}
var avgPlayTime float64
var avgCompletion float64
if stat.sessions > 0 {
avgPlayTime = float64(stat.totalPlayTime) / float64(stat.sessions)
avgCompletion = stat.totalCompletion / float64(stat.sessions)
}
timeSeries = append(timeSeries, TimeSeriesPoint{
Date: dateKey,
Sessions: stat.sessions,
TotalPlayTime: stat.totalPlayTime,
AveragePlayTime: avgPlayTime,
AverageCompletion: avgCompletion,
})
}
return timeSeries, nil
}
// dailyStat représente les statistiques d'un jour
type dailyStat struct {
sessions int64
totalPlayTime int64
totalCompletion float64
}
// SummaryData représente le résumé des analytics de lecture
// T0370: Create Playback Analytics Summary Endpoint
type SummaryData struct {
TotalPlays int64 `json:"total_plays"` // Nombre total de lectures
CompletionRate float64 `json:"completion_rate"` // Taux de complétion moyen (%)
AveragePlayTime float64 `json:"average_play_time"` // Temps de lecture moyen (secondes)
}
// GetSummary gère la requête GET /api/v1/tracks/:id/playback/summary
// Retourne un résumé des analytics de lecture pour un track
// T0370: Create Playback Analytics Summary Endpoint
func (h *PlaybackAnalyticsHandler) GetSummary(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 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
if trackID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
// Récupérer les statistiques via le service
stats, err := h.analyticsService.GetTrackStats(c.Request.Context(), trackID)
if err != nil {
errMsg := err.Error()
if len(errMsg) >= 13 && errMsg[:13] == "track not found" {
c.JSON(http.StatusNotFound, gin.H{"error": errMsg})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
// Construire le résumé
summary := SummaryData{
TotalPlays: stats.TotalSessions,
CompletionRate: stats.CompletionRate,
AveragePlayTime: stats.AveragePlayTime,
}
RespondSuccess(c, http.StatusOK, gin.H{
2025-12-03 19:29:37 +00:00
"summary": summary,
})
}
// GetHeatmap gère la requête GET /api/v1/tracks/:id/playback/heatmap
// Retourne les données de heatmap pour un track
// T0376: Create Playback Analytics Heatmap Generation
func (h *PlaybackAnalyticsHandler) GetHeatmap(c *gin.Context) {
if h.heatmapService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "heatmap service not available"})
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 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
if trackID == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
return
}
// Récupérer la taille de segment depuis les query params (optionnel, défaut: 5)
segmentSize := 5
if segmentSizeStr := c.Query("segment_size"); segmentSizeStr != "" {
if parsed, err := strconv.Atoi(segmentSizeStr); err == nil && parsed > 0 {
segmentSize = parsed
}
}
// Générer la heatmap via le service
heatmap, err := h.heatmapService.GenerateHeatmap(c.Request.Context(), trackID, segmentSize)
if err != nil {
errMsg := err.Error()
if len(errMsg) >= 13 && errMsg[:13] == "track not found" {
c.JSON(http.StatusNotFound, gin.H{"error": errMsg})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": errMsg})
return
}
RespondSuccess(c, http.StatusOK, gin.H{
2025-12-03 19:29:37 +00:00
"heatmap": heatmap,
})
}
// validateAndSanitizeAnalyticsRequest valide et sanitize une requête d'analytics
// T0388: Create Playback Analytics Validation
func (h *PlaybackAnalyticsHandler) validateAndSanitizeAnalyticsRequest(req *RecordAnalyticsRequest, trackID uuid.UUID) ValidationResult {
result := ValidationResult{
Valid: true,
Errors: make([]dto.ValidationError, 0),
Sanitized: &RecordAnalyticsRequest{},
}
// Copier les données pour la sanitization
sanitized := *req
// 1. Validation du schéma - PlayTime
if req.PlayTime < 0 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "play_time",
Message: "play_time must be greater than or equal to 0",
Value: fmt.Sprintf("%d", req.PlayTime),
})
} else {
// Limiter play_time à une valeur raisonnable (max 24 heures = 86400 secondes)
if req.PlayTime > 86400 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "play_time",
Message: "play_time cannot exceed 86400 seconds (24 hours)",
Value: fmt.Sprintf("%d", req.PlayTime),
})
}
sanitized.PlayTime = req.PlayTime
}
// 2. Validation du schéma - PauseCount
if req.PauseCount < 0 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "pause_count",
Message: "pause_count must be greater than or equal to 0",
Value: fmt.Sprintf("%d", req.PauseCount),
})
} else {
// Limiter pause_count à une valeur raisonnable (max 1000)
if req.PauseCount > 1000 {
sanitized.PauseCount = 1000
} else {
sanitized.PauseCount = req.PauseCount
}
}
// 3. Validation du schéma - SeekCount
if req.SeekCount < 0 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "seek_count",
Message: "seek_count must be greater than or equal to 0",
Value: fmt.Sprintf("%d", req.SeekCount),
})
} else {
// Limiter seek_count à une valeur raisonnable (max 1000)
if req.SeekCount > 1000 {
sanitized.SeekCount = 1000
} else {
sanitized.SeekCount = req.SeekCount
}
}
// 4. Validation du schéma - CompletionRate
if req.CompletionRate != nil {
rate := *req.CompletionRate
if math.IsNaN(rate) || math.IsInf(rate, 0) {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "completion_rate",
Message: "completion_rate must be a valid number",
Value: fmt.Sprintf("%f", rate),
})
} else if rate < 0 || rate > 100 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "completion_rate",
Message: "completion_rate must be between 0 and 100",
Value: fmt.Sprintf("%f", rate),
})
} else {
// Arrondir à 2 décimales
roundedRate := math.Round(rate*100) / 100
sanitized.CompletionRate = &roundedRate
}
}
// 5. Validation du schéma - StartedAt
if req.StartedAt.IsZero() {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "started_at",
Message: "started_at is required",
})
} else {
now := time.Now()
// Vérifier que started_at n'est pas dans le futur (avec une marge de 1 minute pour les décalages d'horloge)
if req.StartedAt.After(now.Add(1 * time.Minute)) {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "started_at",
Message: "started_at cannot be in the future",
Value: req.StartedAt.Format(time.RFC3339),
})
} else {
// Vérifier que started_at n'est pas trop ancien (max 30 jours)
thirtyDaysAgo := now.AddDate(0, 0, -30)
if req.StartedAt.Before(thirtyDaysAgo) {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "started_at",
Message: "started_at cannot be older than 30 days",
Value: req.StartedAt.Format(time.RFC3339),
})
} else {
sanitized.StartedAt = req.StartedAt
}
}
}
// 6. Validation du schéma - EndedAt
if req.EndedAt != nil {
endedAt := *req.EndedAt
if endedAt.IsZero() {
// Si ended_at est fourni mais est zero, le traiter comme nil
sanitized.EndedAt = nil
} else {
// Vérifier que ended_at n'est pas dans le futur
now := time.Now()
if endedAt.After(now.Add(1 * time.Minute)) {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "ended_at",
Message: "ended_at cannot be in the future",
Value: endedAt.Format(time.RFC3339),
})
} else {
sanitized.EndedAt = &endedAt
}
}
}
// 7. Vérification de cohérence - EndedAt doit être après StartedAt
if !req.StartedAt.IsZero() && req.EndedAt != nil && !req.EndedAt.IsZero() {
if req.EndedAt.Before(req.StartedAt) {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "ended_at",
Message: "ended_at must be after started_at",
Value: req.EndedAt.Format(time.RFC3339),
})
}
}
// 8. Vérification de cohérence - PlayTime doit être cohérent avec les dates
if !req.StartedAt.IsZero() && req.EndedAt != nil && !req.EndedAt.IsZero() {
duration := req.EndedAt.Sub(req.StartedAt).Seconds()
// Le play_time ne devrait pas être significativement supérieur à la durée entre started_at et ended_at
// (avec une marge de 10% pour les pauses)
maxExpectedPlayTime := duration * 1.1
if float64(req.PlayTime) > maxExpectedPlayTime && maxExpectedPlayTime > 0 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "play_time",
Message: fmt.Sprintf("play_time (%.0f seconds) is inconsistent with session duration (%.0f seconds)", float64(req.PlayTime), duration),
Value: fmt.Sprintf("%d", req.PlayTime),
})
}
}
// 9. Vérification de cohérence - CompletionRate doit être cohérent avec PlayTime si fourni
// Cette vérification nécessite la durée du track, donc elle sera faite après la récupération du track
// Pour l'instant, on valide juste que le completion_rate est dans une plage raisonnable
// 10. Vérification de cohérence - PauseCount et SeekCount doivent être raisonnables par rapport à PlayTime
if req.PlayTime > 0 {
// Si play_time est très court (< 10 secondes), pause_count et seek_count devraient être faibles
if req.PlayTime < 10 {
if req.PauseCount > 5 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "pause_count",
Message: "pause_count is too high for such a short play_time",
Value: fmt.Sprintf("%d", req.PauseCount),
})
}
if req.SeekCount > 10 {
result.Valid = false
result.Errors = append(result.Errors, dto.ValidationError{
Field: "seek_count",
Message: "seek_count is too high for such a short play_time",
Value: fmt.Sprintf("%d", req.SeekCount),
})
}
}
}
result.Sanitized = &sanitized
return result
}
// validateAnalyticsConsistencyWithTrack valide la cohérence des analytics avec le track
// T0388: Create Playback Analytics Validation
func (h *PlaybackAnalyticsHandler) validateAnalyticsConsistencyWithTrack(ctx context.Context, req *RecordAnalyticsRequest, trackID uuid.UUID) []dto.ValidationError {
errors := make([]dto.ValidationError, 0)
// Récupérer le track pour valider la cohérence
// Note: Cette validation nécessite un accès à la base de données
// Pour l'instant, on retourne une liste vide car la validation du track
// est déjà faite dans le service RecordPlayback
// Cette fonction peut être étendue pour des validations plus spécifiques
// Vérifier que completion_rate est cohérent avec play_time et track duration
// Cette vérification sera faite dans le service car elle nécessite la durée du track
return errors
}