2025-12-03 19:29:37 +00:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
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
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2025-12-03 19:29:37 +00:00
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"veza-backend-api/internal/dto"
|
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
|
|
|
apperrors "veza-backend-api/internal/errors"
|
2025-12-03 19:29:37 +00:00
|
|
|
"veza-backend-api/internal/validators"
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ResponseData représente la structure standardisée des réponses API
|
|
|
|
|
type ResponseData struct {
|
|
|
|
|
Success bool `json:"success"`
|
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
|
RequestID string `json:"request_id,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PaginationData représente les données de pagination
|
|
|
|
|
type PaginationData struct {
|
|
|
|
|
Page int `json:"page"`
|
|
|
|
|
Limit int `json:"limit"`
|
|
|
|
|
Total int64 `json:"total"`
|
|
|
|
|
TotalPages int `json:"total_pages"`
|
|
|
|
|
HasNext bool `json:"has_next"`
|
|
|
|
|
HasPrevious bool `json:"has_previous"`
|
|
|
|
|
NextCursor string `json:"next_cursor,omitempty"`
|
|
|
|
|
PreviousCursor string `json:"previous_cursor,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PaginatedResponse représente une réponse paginée
|
|
|
|
|
type PaginatedResponse struct {
|
|
|
|
|
ResponseData
|
|
|
|
|
Pagination PaginationData `json:"pagination"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidationError et ValidationErrors sont maintenant dans internal/dto/validation.go
|
|
|
|
|
// pour éviter les cycles d'import. Utiliser dto.ValidationError et dto.ValidationErrors
|
|
|
|
|
|
|
|
|
|
// CommonHandler contient les dépendances communes aux handlers
|
|
|
|
|
type CommonHandler struct {
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
validator *validators.Validator // GO-013: Validator centralisé
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewCommonHandler crée une nouvelle instance de CommonHandler
|
|
|
|
|
// GO-013: Initialise le validator centralisé
|
|
|
|
|
func NewCommonHandler(logger *zap.Logger) *CommonHandler {
|
|
|
|
|
return &CommonHandler{
|
|
|
|
|
logger: logger,
|
|
|
|
|
validator: validators.NewValidator(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateRequest valide une requête avec le validator centralisé
|
|
|
|
|
// GO-013: Helper pour valider les requêtes et retourner des erreurs formatées
|
|
|
|
|
func (h *CommonHandler) ValidateRequest(c *gin.Context, req interface{}) bool {
|
|
|
|
|
validationErrors := h.validator.Validate(req)
|
|
|
|
|
if len(validationErrors) > 0 {
|
|
|
|
|
h.RespondWithValidationError(c, validationErrors)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RespondWithSuccess répond avec une réponse de succès
|
|
|
|
|
func (h *CommonHandler) RespondWithSuccess(c *gin.Context, data interface{}, message string) {
|
|
|
|
|
response := ResponseData{
|
|
|
|
|
Success: true,
|
|
|
|
|
Message: message,
|
|
|
|
|
Data: data,
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
RequestID: c.GetString("request_id"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RespondWithError répond avec une erreur
|
|
|
|
|
func (h *CommonHandler) RespondWithError(c *gin.Context, statusCode int, message string, err error) {
|
|
|
|
|
response := ResponseData{
|
|
|
|
|
Success: false,
|
|
|
|
|
Error: message,
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
RequestID: c.GetString("request_id"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
h.logger.Error("Handler error",
|
|
|
|
|
zap.String("error", err.Error()),
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(statusCode, response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RespondWithValidationError répond avec des erreurs de validation
|
|
|
|
|
// GO-013: Utilise dto.ValidationError pour éviter les cycles d'import
|
|
|
|
|
func (h *CommonHandler) RespondWithValidationError(c *gin.Context, errors []dto.ValidationError) {
|
|
|
|
|
response := ResponseData{
|
|
|
|
|
Success: false,
|
|
|
|
|
Error: "Validation failed",
|
|
|
|
|
Data: dto.ValidationErrors{Errors: errors},
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
RequestID: c.GetString("request_id"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusBadRequest, response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RespondWithPaginatedData répond avec des données paginées
|
|
|
|
|
func (h *CommonHandler) RespondWithPaginatedData(c *gin.Context, data interface{}, pagination PaginationData, message string) {
|
|
|
|
|
response := PaginatedResponse{
|
|
|
|
|
ResponseData: ResponseData{
|
|
|
|
|
Success: true,
|
|
|
|
|
Message: message,
|
|
|
|
|
Data: data,
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
RequestID: c.GetString("request_id"),
|
|
|
|
|
},
|
|
|
|
|
Pagination: pagination,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BindJSON lie les données JSON de la requête à une structure
|
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
|
|
|
// DEPRECATED: Utiliser BindAndValidateJSON à la place pour une gestion d'erreurs robuste
|
2025-12-03 19:29:37 +00:00
|
|
|
func (h *CommonHandler) BindJSON(c *gin.Context, obj interface{}) error {
|
|
|
|
|
if err := c.ShouldBindJSON(obj); err != nil {
|
|
|
|
|
h.logger.Warn("Failed to bind JSON",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")),
|
|
|
|
|
)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return 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
|
|
|
// MaxJSONBodySize définit la taille maximale du body JSON (10MB par défaut)
|
|
|
|
|
const MaxJSONBodySize = 10 * 1024 * 1024 // 10MB
|
|
|
|
|
|
|
|
|
|
// BindAndValidateJSON lie et valide les données JSON de la requête de manière robuste
|
|
|
|
|
// P0: JSON Hardening - Garantit qu'aucune erreur de parsing/validation ne passe silencieusement
|
|
|
|
|
//
|
|
|
|
|
// Comportement:
|
|
|
|
|
// - Vérifie la taille du body (max 10MB par défaut)
|
|
|
|
|
// - Parse le JSON avec ShouldBindJSON (Gin)
|
|
|
|
|
// - Valide avec le validator centralisé
|
|
|
|
|
// - Retourne une AppError avec code approprié (400 pour JSON malformé, 422 pour validation)
|
|
|
|
|
//
|
|
|
|
|
// Usage:
|
|
|
|
|
//
|
|
|
|
|
// var req MyRequest
|
|
|
|
|
// if appErr := h.BindAndValidateJSON(c, &req); appErr != nil {
|
|
|
|
|
// RespondWithAppError(c, appErr)
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
func (h *CommonHandler) BindAndValidateJSON(c *gin.Context, obj interface{}) *apperrors.AppError {
|
|
|
|
|
requestID := c.GetString("request_id")
|
|
|
|
|
|
|
|
|
|
// 1. Vérifier la taille du body
|
|
|
|
|
if c.Request.ContentLength > MaxJSONBodySize {
|
|
|
|
|
h.logger.Warn("Request body too large",
|
|
|
|
|
zap.Int64("content_length", c.Request.ContentLength),
|
|
|
|
|
zap.Int64("max_size", MaxJSONBodySize),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Limiter la lecture du body pour éviter les attaques par body trop gros
|
|
|
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, MaxJSONBodySize)
|
|
|
|
|
|
|
|
|
|
// 3. Parser le JSON avec ShouldBindJSON
|
|
|
|
|
if err := c.ShouldBindJSON(obj); err != nil {
|
|
|
|
|
// Analyser le type d'erreur pour retourner le bon code
|
|
|
|
|
var jsonSyntaxError *json.SyntaxError
|
|
|
|
|
var jsonUnmarshalTypeError *json.UnmarshalTypeError
|
|
|
|
|
var maxBytesError *http.MaxBytesError
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case errors.As(err, &maxBytesError):
|
|
|
|
|
// Body trop gros (dépassement de la limite)
|
|
|
|
|
h.logger.Warn("Request body exceeds maximum size",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
fmt.Sprintf("Request body too large: maximum size is %d bytes", MaxJSONBodySize),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
case errors.As(err, &jsonSyntaxError):
|
|
|
|
|
// JSON syntaxiquement invalide
|
|
|
|
|
h.logger.Warn("Invalid JSON syntax",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.Int64("offset", jsonSyntaxError.Offset),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
fmt.Sprintf("Invalid JSON syntax at offset %d: %s", jsonSyntaxError.Offset, jsonSyntaxError.Error()),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
case errors.As(err, &jsonUnmarshalTypeError):
|
|
|
|
|
// Type incorrect pour un champ
|
|
|
|
|
h.logger.Warn("Invalid JSON type",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("field", jsonUnmarshalTypeError.Field),
|
|
|
|
|
zap.String("type", jsonUnmarshalTypeError.Type.String()),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeInvalidFormat,
|
|
|
|
|
fmt.Sprintf("Invalid type for field '%s': expected %s", jsonUnmarshalTypeError.Field, jsonUnmarshalTypeError.Type.String()),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
case errors.Is(err, io.EOF):
|
|
|
|
|
// Body vide
|
|
|
|
|
h.logger.Warn("Empty request body",
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
"Request body is empty or invalid JSON",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
case errors.Is(err, io.ErrUnexpectedEOF):
|
|
|
|
|
// JSON incomplet
|
|
|
|
|
h.logger.Warn("Incomplete JSON",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
"Incomplete or malformed JSON",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// Erreur générique de binding (peut inclure des erreurs de validation Gin)
|
|
|
|
|
// On va laisser le validator gérer les erreurs de validation
|
|
|
|
|
// Si c'est une erreur de binding Gin (ex: unknown field), on la traite ici
|
|
|
|
|
errStr := err.Error()
|
|
|
|
|
if strings.Contains(errStr, "unknown field") || strings.Contains(errStr, "unknown") {
|
|
|
|
|
h.logger.Warn("Unknown fields in JSON",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
return apperrors.New(
|
|
|
|
|
apperrors.ErrCodeValidation,
|
|
|
|
|
"Unknown fields in JSON payload",
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pour les autres erreurs de binding, on considère que c'est une erreur de validation
|
|
|
|
|
// et on va laisser le validator s'en occuper
|
|
|
|
|
h.logger.Debug("JSON binding error (will be handled by validator)",
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Valider avec le validator centralisé
|
|
|
|
|
validationErrors := h.validator.Validate(obj)
|
|
|
|
|
if len(validationErrors) > 0 {
|
|
|
|
|
// Convertir dto.ValidationError en errors.ErrorDetail
|
|
|
|
|
details := make([]apperrors.ErrorDetail, 0, len(validationErrors))
|
|
|
|
|
for _, ve := range validationErrors {
|
|
|
|
|
details = append(details, apperrors.ErrorDetail{
|
|
|
|
|
Field: ve.Field,
|
|
|
|
|
Message: ve.Message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.logger.Warn("Validation failed",
|
|
|
|
|
zap.Int("error_count", len(validationErrors)),
|
|
|
|
|
zap.String("request_id", requestID),
|
|
|
|
|
zap.String("endpoint", c.Request.URL.Path),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return apperrors.NewValidationError("Validation failed", details...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 19:29:37 +00:00
|
|
|
// GetUserIDFromContext extrait l'ID utilisateur du contexte
|
|
|
|
|
func (h *CommonHandler) GetUserIDFromContext(c *gin.Context) (string, error) {
|
|
|
|
|
userID, exists := c.Get("user_id")
|
|
|
|
|
if !exists {
|
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
|
|
|
return "", apperrors.NewUnauthorizedError("User not authenticated")
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userIDStr, ok := userID.(string)
|
|
|
|
|
if !ok {
|
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
|
|
|
return "", apperrors.New(apperrors.ErrCodeValidation, "Invalid user ID type")
|
2025-12-03 19:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return userIDStr, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetPaginationParams extrait les paramètres de pagination de la requête
|
|
|
|
|
func (h *CommonHandler) GetPaginationParams(c *gin.Context) (page, limit int, cursor string) {
|
|
|
|
|
page = 1
|
|
|
|
|
limit = 20
|
|
|
|
|
|
|
|
|
|
if pageStr := c.Query("page"); pageStr != "" {
|
|
|
|
|
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
|
|
|
|
page = p
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
|
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
|
|
|
|
|
limit = l
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursor = c.Query("cursor")
|
|
|
|
|
return page, limit, cursor
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidatePagination valide les paramètres de pagination
|
|
|
|
|
// GO-013: Utilise dto.ValidationError
|
|
|
|
|
func (h *CommonHandler) ValidatePagination(page, limit int) []dto.ValidationError {
|
|
|
|
|
var validationErrors []dto.ValidationError
|
|
|
|
|
|
|
|
|
|
if page < 1 {
|
|
|
|
|
validationErrors = append(validationErrors, dto.ValidationError{
|
|
|
|
|
Field: "page",
|
|
|
|
|
Message: "Page must be greater than 0",
|
|
|
|
|
Value: strconv.Itoa(page),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if limit < 1 || limit > 100 {
|
|
|
|
|
validationErrors = append(validationErrors, dto.ValidationError{
|
|
|
|
|
Field: "limit",
|
|
|
|
|
Message: "Limit must be between 1 and 100",
|
|
|
|
|
Value: strconv.Itoa(limit),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return validationErrors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LogRequest log une requête entrante
|
|
|
|
|
func (h *CommonHandler) LogRequest(c *gin.Context, operation string) {
|
|
|
|
|
h.logger.Info("Request received",
|
|
|
|
|
zap.String("method", c.Request.Method),
|
|
|
|
|
zap.String("path", c.Request.URL.Path),
|
|
|
|
|
zap.String("operation", operation),
|
|
|
|
|
zap.String("user_id", c.GetString("user_id")),
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")),
|
|
|
|
|
zap.String("ip", c.ClientIP()),
|
|
|
|
|
zap.String("user_agent", c.Request.UserAgent()),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LogResponse log une réponse sortante
|
|
|
|
|
func (h *CommonHandler) LogResponse(c *gin.Context, statusCode int, duration time.Duration) {
|
|
|
|
|
h.logger.Info("Response sent",
|
|
|
|
|
zap.Int("status_code", statusCode),
|
|
|
|
|
zap.Duration("duration", duration),
|
|
|
|
|
zap.String("request_id", c.GetString("request_id")),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetRequestID middleware pour ajouter un ID de requête
|
|
|
|
|
func (h *CommonHandler) SetRequestID() gin.HandlerFunc {
|
|
|
|
|
return func(c *gin.Context) {
|
|
|
|
|
requestID := c.GetHeader("X-Request-ID")
|
|
|
|
|
if requestID == "" {
|
|
|
|
|
requestID = generateRequestID()
|
|
|
|
|
}
|
|
|
|
|
c.Set("request_id", requestID)
|
|
|
|
|
c.Header("X-Request-ID", requestID)
|
|
|
|
|
c.Next()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// generateRequestID génère un ID de requête unique
|
|
|
|
|
func generateRequestID() string {
|
|
|
|
|
return strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ValidateRequiredFields valide que les champs requis sont présents
|
|
|
|
|
// GO-013: Utilise dto.ValidationError
|
|
|
|
|
func (h *CommonHandler) ValidateRequiredFields(fields map[string]interface{}) []dto.ValidationError {
|
|
|
|
|
var validationErrors []dto.ValidationError
|
|
|
|
|
|
|
|
|
|
for field, value := range fields {
|
|
|
|
|
if value == nil || value == "" {
|
|
|
|
|
validationErrors = append(validationErrors, dto.ValidationError{
|
|
|
|
|
Field: field,
|
|
|
|
|
Message: "This field is required",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return validationErrors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SanitizeString nettoie une chaîne de caractères
|
|
|
|
|
func (h *CommonHandler) SanitizeString(input string) string {
|
|
|
|
|
// Supprimer les caractères de contrôle et les espaces en début/fin
|
|
|
|
|
cleaned := strings.TrimSpace(input)
|
|
|
|
|
|
|
|
|
|
// Limiter la longueur
|
|
|
|
|
if len(cleaned) > 1000 {
|
|
|
|
|
cleaned = cleaned[:1000]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cleaned
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseJSON parse du JSON de manière sécurisée
|
|
|
|
|
func (h *CommonHandler) ParseJSON(data []byte, v interface{}) error {
|
|
|
|
|
if err := json.Unmarshal(data, v); err != nil {
|
|
|
|
|
h.logger.Error("Failed to parse JSON", zap.Error(err))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MarshalJSON sérialise en JSON de manière sécurisée
|
|
|
|
|
func (h *CommonHandler) MarshalJSON(v interface{}) ([]byte, error) {
|
|
|
|
|
data, err := json.Marshal(v)
|
|
|
|
|
if err != nil {
|
|
|
|
|
h.logger.Error("Failed to marshal JSON", zap.Error(err))
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return data, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetClientIP obtient l'IP réelle du client
|
|
|
|
|
func (h *CommonHandler) GetClientIP(c *gin.Context) string {
|
|
|
|
|
// Vérifier les headers de proxy
|
|
|
|
|
if ip := c.GetHeader("X-Forwarded-For"); ip != "" {
|
|
|
|
|
return strings.Split(ip, ",")[0]
|
|
|
|
|
}
|
|
|
|
|
if ip := c.GetHeader("X-Real-IP"); ip != "" {
|
|
|
|
|
return ip
|
|
|
|
|
}
|
|
|
|
|
return c.ClientIP()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RateLimitKey génère une clé pour le rate limiting
|
|
|
|
|
func (h *CommonHandler) RateLimitKey(c *gin.Context, prefix string) string {
|
|
|
|
|
userID := c.GetString("user_id")
|
|
|
|
|
if userID != "" {
|
|
|
|
|
return prefix + ":user:" + userID
|
|
|
|
|
}
|
|
|
|
|
return prefix + ":ip:" + h.GetClientIP(c)
|
|
|
|
|
}
|