refactor(track): enforce unified api response envelope

This commit is contained in:
okinrev 2025-12-06 17:37:00 +01:00
parent 02cad8db4d
commit e7ae13736b

View file

@ -16,7 +16,6 @@ import (
"gorm.io/gorm"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"veza-backend-api/internal/services"
"veza-backend-api/internal/validators"
"veza-backend-api/internal/response"
)
@ -88,13 +87,13 @@ func (h *TrackHandler) SetHistoryService(historyService *services.TrackHistorySe
func (h *TrackHandler) UploadTrack(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
fileHeader, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "no file provided"})
response.BadRequest(c, "no file provided")
return
}
@ -104,7 +103,7 @@ func (h *TrackHandler) UploadTrack(c *gin.Context) {
// Mapper les erreurs vers des messages utilisateur spécifiques
errorMessage := h.mapTrackError(err)
statusCode := h.getErrorStatusCode(err)
c.JSON(statusCode, gin.H{"error": errorMessage})
response.Error(c, statusCode, errorMessage)
return
}
@ -118,7 +117,7 @@ func (h *TrackHandler) UploadTrack(c *gin.Context) {
}
}
c.JSON(http.StatusCreated, gin.H{"track": track})
response.Created(c, gin.H{"track": track})
}
// GetUploadStatus récupère le statut d'upload d'un track
@ -137,7 +136,7 @@ func (h *TrackHandler) UploadTrack(c *gin.Context) {
func (h *TrackHandler) GetUploadStatus(c *gin.Context) {
trackIDStr := c.Param("id")
if trackIDStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "track id is required"})
response.BadRequest(c, "track id is required")
return
}
@ -148,14 +147,14 @@ func (h *TrackHandler) GetUploadStatus(c *gin.Context) {
trackID, err := uuid.Parse(trackIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
response.BadRequest(c, "invalid track id")
return
}
// Vérifier que l'utilisateur est autorisé à voir ce track
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
@ -180,11 +179,11 @@ func (h *TrackHandler) GetUploadStatus(c *gin.Context) {
progress, err := h.trackUploadService.GetUploadProgress(c.Request.Context(), trackID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get upload progress"})
response.InternalServerError(c, "failed to get upload progress")
return
}
c.JSON(http.StatusOK, gin.H{"progress": progress})
response.Success(c, gin.H{"progress": progress})
}
// InitiateChunkedUploadRequest représente la requête pour initialiser un upload par chunks
@ -209,7 +208,7 @@ type InitiateChunkedUploadRequest struct {
func (h *TrackHandler) InitiateChunkedUpload(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
@ -218,13 +217,11 @@ func (h *TrackHandler) InitiateChunkedUpload(c *gin.Context) {
// GO-013: Utiliser validator pour messages d'erreur plus clairs
validator := validators.NewValidator()
if validationErrs := validator.Validate(&req); len(validationErrs) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Validation failed",
"errors": validationErrs,
})
// Using BadRequest for validation errors
response.Error(c, http.StatusBadRequest, fmt.Sprintf("Validation failed: %v", validationErrs))
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
@ -232,11 +229,11 @@ func (h *TrackHandler) InitiateChunkedUpload(c *gin.Context) {
// InitiateChunkedUpload retourne un string (uploadID) donc pas de souci d'int64
uploadID, err := h.chunkService.InitiateChunkedUpload(userID, req.TotalChunks, req.TotalSize, req.Filename)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
response.InternalServerError(c, err.Error())
return
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"upload_id": uploadID,
"message": "upload initiated successfully",
})
@ -271,36 +268,36 @@ type UploadChunkRequest struct {
func (h *TrackHandler) UploadChunk(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
var req UploadChunkRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
fileHeader, err := c.FormFile("chunk")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "no chunk file provided"})
response.BadRequest(c, "no chunk file provided")
return
}
// Sauvegarder le chunk
if err := h.chunkService.SaveChunk(c.Request.Context(), req.UploadID, req.ChunkNumber, req.TotalChunks, fileHeader); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
// Récupérer la progression
receivedChunks, progress, err := h.chunkService.GetUploadProgress(req.UploadID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
response.InternalServerError(c, err.Error())
return
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"message": "chunk uploaded successfully",
"upload_id": req.UploadID,
"received_chunks": receivedChunks,
@ -329,7 +326,7 @@ type CompleteChunkedUploadRequest struct {
func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
@ -338,20 +335,17 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
// GO-013: Utiliser validator pour messages d'erreur plus clairs
validator := validators.NewValidator()
if validationErrs := validator.Validate(&req); len(validationErrs) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Validation failed",
"errors": validationErrs,
})
response.Error(c, http.StatusBadRequest, fmt.Sprintf("Validation failed: %v", validationErrs))
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
// Récupérer les informations de l'upload pour obtenir le filename
uploadInfo, err := h.chunkService.GetUploadInfo(req.UploadID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
@ -366,7 +360,7 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
// Assurer que le répertoire existe
if err := os.MkdirAll(filepath.Dir(finalPath), 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create directory"})
response.InternalServerError(c, "failed to create directory")
return
}
@ -375,7 +369,7 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
if err != nil {
errorMessage := h.mapTrackError(err)
statusCode := h.getErrorStatusCode(err)
c.JSON(statusCode, gin.H{"error": errorMessage})
response.Error(c, statusCode, errorMessage)
return
}
@ -385,7 +379,7 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
statusCode := h.getErrorStatusCode(err)
// Nettoyer le fichier assemblé
os.Remove(finalPath)
c.JSON(statusCode, gin.H{"error": errorMessage})
response.Error(c, statusCode, errorMessage)
return
}
@ -403,7 +397,7 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
os.Remove(finalPath)
errorMessage := h.mapTrackError(err)
statusCode := h.getErrorStatusCode(err)
c.JSON(statusCode, gin.H{"error": errorMessage})
response.Error(c, statusCode, errorMessage)
return
}
@ -422,7 +416,7 @@ func (h *TrackHandler) CompleteChunkedUpload(c *gin.Context) {
}
}
c.JSON(http.StatusCreated, gin.H{
response.Created(c, gin.H{
"message": "upload completed successfully",
"track": track,
"md5": md5,
@ -527,14 +521,14 @@ func (h *TrackHandler) GetUploadQuota(c *gin.Context) {
// Si "me" ou vide, utiliser l'utilisateur authentifié
userID = c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
} else {
// Parse UUID
userID, err = uuid.Parse(userIDParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"})
response.BadRequest(c, "invalid user id")
return
}
}
@ -542,24 +536,24 @@ func (h *TrackHandler) GetUploadQuota(c *gin.Context) {
// Vérifier que l'utilisateur peut accéder à ces informations (soit lui-même, soit admin)
authenticatedUserID := c.MustGet("user_id").(uuid.UUID)
if authenticatedUserID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
// Un utilisateur ne peut voir que son propre quota (sauf admin, mais on simplifie pour l'instant)
if authenticatedUserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden: you can only view your own quota"})
response.Forbidden(c, "forbidden: you can only view your own quota")
return
}
// Récupérer le quota
quota, err := h.trackService.GetUserQuota(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get quota"})
response.InternalServerError(c, "failed to get quota")
return
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"quota": quota,
})
}
@ -578,30 +572,30 @@ func (h *TrackHandler) GetUploadQuota(c *gin.Context) {
func (h *TrackHandler) ResumeUpload(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
uploadID := c.Param("uploadId")
if uploadID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "upload_id is required"})
response.BadRequest(c, "upload_id is required")
return
}
// Récupérer l'état de l'upload
state, err := h.chunkService.GetUploadState(uploadID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "upload not found"})
response.NotFound(c, "upload not found")
return
}
// Vérifier que l'upload appartient à l'utilisateur authentifié
if state.UserID != userID {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden: you can only resume your own uploads"})
response.Forbidden(c, "forbidden: you can only resume your own uploads")
return
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"upload_id": state.UploadID,
"user_id": state.UserID,
"total_chunks": state.TotalChunks,
@ -679,7 +673,7 @@ func (h *TrackHandler) ListTracks(c *gin.Context) {
// Appeler le service
tracks, total, err := h.trackService.ListTracks(c.Request.Context(), params)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list tracks"})
response.InternalServerError(c, "failed to list tracks")
return
}
@ -697,7 +691,7 @@ func (h *TrackHandler) ListTracks(c *gin.Context) {
}
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"tracks": tracks,
"pagination": gin.H{
"page": pageInt,
@ -722,24 +716,24 @@ func (h *TrackHandler) ListTracks(c *gin.Context) {
func (h *TrackHandler) GetTrack(c *gin.Context) {
trackIDStr := c.Param("id")
if trackIDStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "track id is required"})
response.BadRequest(c, "track id is required")
return
}
// MIGRATION UUID: TrackID is UUID
trackID, err := uuid.Parse(trackIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
response.BadRequest(c, "invalid track id")
return
}
track, err := h.trackService.GetTrackByID(c.Request.Context(), trackID)
if err != nil {
if errors.Is(err, ErrTrackNotFound) || errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
response.NotFound(c, "track not found")
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get track"})
response.InternalServerError(c, "failed to get track")
return
}
@ -749,7 +743,7 @@ func (h *TrackHandler) GetTrack(c *gin.Context) {
track.StreamManifestURL = ""
}
c.JSON(http.StatusOK, gin.H{"track": track})
response.Success(c, gin.H{"track": track})
}
// UpdateTrackRequest représente la requête de mise à jour d'un track
@ -780,26 +774,26 @@ type UpdateTrackRequest struct {
func (h *TrackHandler) UpdateTrack(c *gin.Context) {
userID := c.MustGet("user_id").(uuid.UUID)
if userID == uuid.Nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
response.Unauthorized(c, "unauthorized")
return
}
trackIDStr := c.Param("id")
if trackIDStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "track id is required"})
response.BadRequest(c, "track id is required")
return
}
// MIGRATION UUID: TrackID is UUID
trackID, err := uuid.Parse(trackIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
response.BadRequest(c, "invalid track id")
return
}
var req UpdateTrackRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
@ -816,23 +810,23 @@ func (h *TrackHandler) UpdateTrack(c *gin.Context) {
track, err := h.trackService.UpdateTrack(c.Request.Context(), trackID, userID, params)
if err != nil {
if errors.Is(err, ErrTrackNotFound) || errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
response.NotFound(c, "track not found")
return
}
if errors.Is(err, ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
response.Forbidden(c, "forbidden")
return
}
// Erreur de validation (title empty, year negative, etc.)
if strings.Contains(err.Error(), "cannot be") {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update track"})
response.InternalServerError(c, "failed to update track")
return
}
c.JSON(http.StatusOK, gin.H{"track": track})
response.Success(c, gin.H{"track": track})
}
// DeleteTrack gère la suppression d'un track
@ -864,25 +858,25 @@ func (h *TrackHandler) DeleteTrack(c *gin.Context) {
// MIGRATION UUID: TrackID is UUID
trackID, err := uuid.Parse(trackIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid track id"})
response.BadRequest(c, "invalid track id")
return
}
err = h.trackService.DeleteTrack(c.Request.Context(), trackID, userID)
if err != nil {
if errors.Is(err, ErrTrackNotFound) || errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "track not found"})
response.NotFound(c, "track not found")
return
}
if errors.Is(err, ErrForbidden) {
c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
response.Forbidden(c, "forbidden")
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete track"})
response.InternalServerError(c, "failed to delete track")
return
}
c.JSON(http.StatusOK, gin.H{"message": "track deleted successfully"})
response.Success(c, gin.H{"message": "track deleted successfully"})
}
// BatchDeleteRequest représente la requête pour supprimer plusieurs tracks
@ -911,13 +905,13 @@ func (h *TrackHandler) BatchDeleteTracks(c *gin.Context) {
var req BatchDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
// Valider que la liste n'est pas vide
if len(req.TrackIDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "track_ids cannot be empty"})
response.BadRequest(c, "track_ids cannot be empty")
return
}
@ -933,14 +927,14 @@ func (h *TrackHandler) BatchDeleteTracks(c *gin.Context) {
if err != nil {
// Vérifier si c'est une erreur de taille de batch
if strings.Contains(err.Error(), "batch size exceeds maximum") {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
response.BadRequest(c, err.Error())
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete tracks"})
response.InternalServerError(c, "failed to delete tracks")
return
}
c.JSON(http.StatusOK, gin.H{
response.Success(c, gin.H{
"deleted": result.Deleted,
"failed": result.Failed,
})