package middleware import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // TTL_LEGACY_ROUTES defines the time-to-live for legacy routes before they are removed. const TTL_LEGACY_ROUTES = 30 * 24 * time.Hour // 30 days // DeprecationInfo contient les informations de dépréciation d'un endpoint // INT-020: Structure pour gérer la dépréciation des endpoints type DeprecationInfo struct { Deprecated bool // Si l'endpoint est déprécié SunsetDate time.Time // Date de fin de support (quand l'endpoint sera supprimé) Replacement string // Endpoint de remplacement recommandé (ex: "/api/v1/health") MigrationURL string // URL vers le guide de migration Reason string // Raison de la dépréciation } // DeprecationWarning returns a Gin middleware that adds deprecation headers // and logs a warning for requests to legacy routes. // INT-020: Amélioration du middleware de dépréciation avec support de dates personnalisées func DeprecationWarning(logger *zap.Logger) gin.HandlerFunc { return DeprecationWarningWithInfo(logger, DeprecationInfo{ Deprecated: true, SunsetDate: time.Now().Add(TTL_LEGACY_ROUTES), Replacement: "/api/v1/*", MigrationURL: "https://www.veza.app/api/v1/migration-guide", Reason: "Legacy route - please migrate to /api/v1/* equivalent", }) } // DeprecationWarningWithInfo returns a Gin middleware with custom deprecation information // INT-020: Permet de spécifier des informations de dépréciation personnalisées func DeprecationWarningWithInfo(logger *zap.Logger, info DeprecationInfo) gin.HandlerFunc { if !info.Deprecated { // Si l'endpoint n'est pas déprécié, retourner un middleware no-op return func(c *gin.Context) { c.Next() } } sunsetDateStr := info.SunsetDate.Format(time.RFC1123) deprecationDateStr := time.Now().Format(time.RFC1123) return func(c *gin.Context) { // INT-020: Log un warning pour chaque accès à un endpoint déprécié logger.Warn( "Access to deprecated endpoint", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.String("deprecation_date", deprecationDateStr), zap.String("sunset_date", sunsetDateStr), zap.String("replacement", info.Replacement), zap.String("reason", info.Reason), zap.String("action", fmt.Sprintf("Please migrate to %s before %s", info.Replacement, sunsetDateStr)), ) // INT-020: Ajouter les headers de dépréciation standardisés (RFC 8594) // Deprecated header (RFC 8594) c.Header("Deprecated", "true") // Sunset header (RFC 8594) - date de fin de support c.Header("Sunset", sunsetDateStr) // Link header pour le guide de migration if info.MigrationURL != "" { c.Header("Link", fmt.Sprintf("<%s>; rel=\"deprecation\"", info.MigrationURL)) } // X-API-Deprecated header (custom, pour compatibilité) c.Header("X-API-Deprecated", "true") // X-API-Sunset header (custom) c.Header("X-API-Sunset", sunsetDateStr) // X-API-Replacement header (custom) - endpoint de remplacement if info.Replacement != "" { c.Header("X-API-Replacement", info.Replacement) } c.Next() } } // MarkEndpointDeprecated est un helper pour marquer un endpoint comme déprécié dans le router // INT-020: Helper pour appliquer facilement la dépréciation aux endpoints func MarkEndpointDeprecated(router *gin.Engine, method, path string, handler gin.HandlerFunc, logger *zap.Logger, info DeprecationInfo) { deprecationMW := DeprecationWarningWithInfo(logger, info) switch method { case http.MethodGet: router.GET(path, deprecationMW, handler) case http.MethodPost: router.POST(path, deprecationMW, handler) case http.MethodPut: router.PUT(path, deprecationMW, handler) case http.MethodDelete: router.DELETE(path, deprecationMW, handler) case http.MethodPatch: router.PATCH(path, deprecationMW, handler) default: router.Handle(method, path, deprecationMW, handler) } }