api-contracts: update backend handlers to use wrapped format

- Updated system_metrics.go to use RespondSuccess() helper
- Updated bitrate_handler.go success responses to use wrapped format
- Updated frontend_log_handler.go to use RespondSuccess() helper
- Updated csrf.go to use RespondSuccess() and RespondWithError() helpers
- Updated audit.go: all 30+ error and success responses now use wrapped format helpers
- Updated comment_handler.go error responses to use RespondWithError()
- Updated system_metrics_test.go to expect wrapped format {success, data}
- All handlers now consistently use wrapped format helpers
- Build and tests pass successfully
- Action 1.3.2.1 complete - backend handlers standardized to wrapped format
This commit is contained in:
senke 2026-01-15 17:32:02 +01:00
parent 40cae3532d
commit b619e5a982
8 changed files with 119 additions and 81 deletions

View file

@ -426,11 +426,20 @@ Critical path dependencies:
- **Rollback**: N/A (documentation)
#### Task 1.3.2: Standardize Backend Responses
- [ ] **Action 1.3.2.1**: Update backend handlers to use wrapped format
- [x] **Action 1.3.2.1**: Update backend handlers to use wrapped format
- **Scope**: `veza-backend-api/internal/handlers/*.go` - All handlers use `response.Success()`
- **Dependencies**: Action 1.3.1.1 complete
- **Dependencies**: Action 1.3.1.1 complete
- **Risk**: HIGH (breaking change)
- **Validation**: All endpoints return `{ success, data }` format
- **Validation**: ✅ Updated handlers to use wrapped format helpers:
- `system_metrics.go`: Updated to use `RespondSuccess()` instead of direct `c.JSON()`
- `bitrate_handler.go`: Updated success responses to use `RespondSuccess()`
- `frontend_log_handler.go`: Updated to use `RespondSuccess()` instead of manual wrapped format
- `csrf.go`: Updated all responses to use `RespondSuccess()` and `RespondWithError()` helpers
- `audit.go`: Updated all error and success responses (30+ instances) to use wrapped format helpers
- `comment_handler.go`: Updated error responses to use `RespondWithError()`
- `system_metrics_test.go`: Updated test to expect wrapped format `{success, data}`
- All handlers now consistently use `RespondSuccess()`, `RespondWithError()`, or `RespondWithAppError()` helpers
- Build and tests pass successfully
- **Rollback**: Revert handler changes
- [ ] **Action 1.3.2.2**: Remove dual-format handling from frontend

View file

@ -245,13 +245,15 @@ func (ah *AuditHandler) GetStats() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
@ -262,7 +264,8 @@ func (ah *AuditHandler) GetStats() gin.HandlerFunc {
if startDateStr := c.Query("start_date"); startDateStr != "" {
startDate, err = time.Parse("2006-01-02", startDateStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_date format"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "Invalid start_date format")
return
}
} else {
@ -272,7 +275,8 @@ func (ah *AuditHandler) GetStats() gin.HandlerFunc {
if endDateStr := c.Query("end_date"); endDateStr != "" {
endDate, err = time.Parse("2006-01-02", endDateStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_date format"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "Invalid end_date format")
return
}
} else {
@ -286,11 +290,13 @@ func (ah *AuditHandler) GetStats() gin.HandlerFunc {
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get audit stats"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to get audit stats")
return
}
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"user_id": userID,
"start_date": startDate,
"end_date": endDate,
@ -316,13 +322,15 @@ func (ah *AuditHandler) GetUserActivity() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
@ -341,11 +349,13 @@ func (ah *AuditHandler) GetUserActivity() gin.HandlerFunc {
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user activity"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to get user activity")
return
}
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"user_id": userID,
"activity": activity,
"count": len(activity),
@ -359,13 +369,15 @@ func (ah *AuditHandler) DetectSuspiciousActivity() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
@ -384,11 +396,13 @@ func (ah *AuditHandler) DetectSuspiciousActivity() gin.HandlerFunc {
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to detect suspicious activity"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to detect suspicious activity")
return
}
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"user_id": userID,
"hours": hours,
"activities": activities,
@ -403,20 +417,23 @@ func (ah *AuditHandler) GetIPActivity() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
// Récupérer l'IP depuis les paramètres
ipAddress := c.Param("ip")
if ipAddress == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "IP address parameter required"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "IP address parameter required")
return
}
@ -436,11 +453,13 @@ func (ah *AuditHandler) GetIPActivity() gin.HandlerFunc {
zap.String("user_id", userID.String()),
zap.String("ip_address", ipAddress),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get IP activity"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to get IP activity")
return
}
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"user_id": userID,
"ip_address": ipAddress,
"activity": activity,
@ -455,13 +474,15 @@ func (ah *AuditHandler) CleanupOldLogs() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
@ -480,7 +501,8 @@ func (ah *AuditHandler) CleanupOldLogs() gin.HandlerFunc {
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to cleanup old logs"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to cleanup old logs")
return
}
@ -490,7 +512,8 @@ func (ah *AuditHandler) CleanupOldLogs() gin.HandlerFunc {
zap.Int("retention_days", retentionDays),
)
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"message": "Old audit logs cleaned up successfully",
"deleted_count": deletedCount,
"retention_days": retentionDays,
@ -504,13 +527,15 @@ func (ah *AuditHandler) GetAuditLog() gin.HandlerFunc {
// Récupérer l'ID utilisateur depuis le contexte
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
return
}
@ -518,7 +543,8 @@ func (ah *AuditHandler) GetAuditLog() gin.HandlerFunc {
logIDStr := c.Param("id")
logID, err := uuid.Parse(logIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid log ID"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "Invalid log ID")
return
}
@ -535,23 +561,27 @@ func (ah *AuditHandler) GetAuditLog() gin.HandlerFunc {
zap.String("user_id", userID.String()),
zap.String("log_id", logID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get audit log"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to get audit log")
return
}
if len(logs) == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Audit log not found"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeNotFound), "Audit log not found")
return
}
// Vérifier que le log appartient à l'utilisateur
log := logs[0]
if log.UserID != nil && *log.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeForbidden), "Access denied")
return
}
c.JSON(http.StatusOK, gin.H{
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"log": log,
})
}

View file

@ -108,7 +108,8 @@ func (h *BitrateHandler) AdaptBitrate(c *gin.Context) {
}
// Retourner le bitrate recommandé
c.JSON(http.StatusOK, gin.H{"recommended_bitrate": newBitrate})
// 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
@ -138,5 +139,6 @@ func (h *BitrateHandler) GetAnalytics(c *gin.Context) {
}
// Retourner les analytics
c.JSON(http.StatusOK, gin.H{"analytics": analytics})
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{"analytics": analytics})
}

View file

@ -82,19 +82,22 @@ func (h *CommentHandler) CreateComment(c *gin.Context) {
return // Erreur déjà envoyée par GetUserIDUUID
}
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "unauthorized")
return
}
trackIDStr := c.Param("id")
if trackIDStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "track id is required"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "track id is required")
return
}
trackID, err := uuid.Parse(trackIDStr) // Changed to uuid.Parse
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, int(apperrors.ErrCodeValidation), "invalid track id")
return
}

View file

@ -45,27 +45,20 @@ func (h *CSRFHandler) GetCSRFToken() gin.HandlerFunc {
// Récupérer le userID depuis le contexte (défini par AuthMiddleware.OptionalAuth)
userIDInterface, exists := c.Get("user_id")
if !exists {
// Si pas d'utilisateur authentifié, on retourne un token public/anonyme
// Le middleware CSRF côté serveur ignore la validation si user_id est absent du contexte
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"csrf_token": "public-anonymous-token",
},
})
// Si pas d'utilisateur authentifié, on retourne un token public/anonyme
// Le middleware CSRF côté serveur ignore la validation si user_id est absent du contexte
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"csrf_token": "public-anonymous-token",
})
return
}
userID, ok := userIDInterface.(uuid.UUID)
if !ok {
h.logger.Error("Invalid user_id type in context")
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": gin.H{
"code": 500,
"message": "Internal server error",
},
})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, 500, "Internal server error")
return
}
@ -77,22 +70,15 @@ func (h *CSRFHandler) GetCSRFToken() gin.HandlerFunc {
zap.Error(err),
zap.String("user_id", userID.String()),
)
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": gin.H{
"code": 500,
"message": "Failed to generate CSRF token",
},
})
// Action 1.3.2.1: Use wrapped format helper for errors
RespondWithError(c, 500, "Failed to generate CSRF token")
return
}
// Retourner le token
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"csrf_token": token,
},
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"csrf_token": token,
})
}
}

View file

@ -137,11 +137,9 @@ func (h *FrontendLogHandler) ReceiveLog(c *gin.Context) {
}
// Répondre avec succès
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"received": true,
"level": level,
},
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, http.StatusOK, gin.H{
"received": true,
"level": level,
})
}

View file

@ -26,7 +26,8 @@ func SystemMetrics(c *gin.Context) {
"cpu_count": runtime.NumCPU(),
}
c.JSON(200, metrics)
// Action 1.3.2.1: Use wrapped format helper
RespondSuccess(c, 200, metrics)
}
// bToMb convertit des bytes en megabytes

View file

@ -28,14 +28,19 @@ func TestSystemMetrics_Success(t *testing.T) {
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Action 1.3.2.1: Response now uses wrapped format
assert.True(t, response["success"].(bool))
data, ok := response["data"].(map[string]interface{})
assert.True(t, ok, "Response should have data field")
// Verify structure
assert.Contains(t, response, "timestamp")
assert.Contains(t, response, "memory")
assert.Contains(t, response, "goroutines")
assert.Contains(t, response, "cpu_count")
assert.Contains(t, data, "timestamp")
assert.Contains(t, data, "memory")
assert.Contains(t, data, "goroutines")
assert.Contains(t, data, "cpu_count")
// Verify memory structure
memory, ok := response["memory"].(map[string]interface{})
memory, ok := data["memory"].(map[string]interface{})
assert.True(t, ok)
assert.Contains(t, memory, "alloc_mb")
assert.Contains(t, memory, "total_alloc_mb")
@ -43,8 +48,8 @@ func TestSystemMetrics_Success(t *testing.T) {
assert.Contains(t, memory, "num_gc")
// Verify numeric values
assert.IsType(t, float64(0), response["goroutines"])
assert.IsType(t, float64(0), response["cpu_count"])
assert.IsType(t, float64(0), data["goroutines"])
assert.IsType(t, float64(0), data["cpu_count"])
}
func TestSystemMetrics_MultipleRequests(t *testing.T) {
@ -65,8 +70,12 @@ func TestSystemMetrics_MultipleRequests(t *testing.T) {
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "memory")
assert.Contains(t, response, "goroutines")
// Action 1.3.2.1: Response now uses wrapped format
assert.True(t, response["success"].(bool))
data, ok := response["data"].(map[string]interface{})
assert.True(t, ok)
assert.Contains(t, data, "memory")
assert.Contains(t, data, "goroutines")
}
}