veza/veza-backend-api/internal/middleware/general.go
senke 1b743b0be0 [INT-020] int: Add API endpoint deprecation strategy
- Created DeprecationInfo structure for managing deprecation metadata
- Enhanced DeprecationWarning middleware with custom deprecation information support
- Added standardized deprecation headers (Deprecated, Sunset, Link per RFC 8594)
- Added X-API-* custom headers for compatibility
- Created MarkEndpointDeprecated helper for easy endpoint deprecation
- System provides clear warnings, sunset dates, and migration guidance

Files modified:
- veza-backend-api/internal/middleware/general.go
- VEZA_COMPLETE_MVP_TODOLIST.json
2025-12-25 15:51:14 +01:00

109 lines
3.9 KiB
Go

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)
}
}