package handlers import ( "fmt" "net/http" "time" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // UploadRequest requête pour upload de fichier type UploadRequest struct { TrackID uuid.UUID `form:"track_id" binding:"required"` FileType string `form:"file_type" binding:"required,oneof=audio image video"` Title string `form:"title" binding:"required,min=1,max=255"` Artist string `form:"artist" binding:"required,min=1,max=255"` Duration int `form:"duration" binding:"min=0"` Metadata string `form:"metadata"` } // UploadResponse réponse pour upload type UploadResponse struct { ID uuid.UUID `json:"id"` TrackID uuid.UUID `json:"track_id"` FileName string `json:"file_name"` FileSize int64 `json:"file_size"` FileType string `json:"file_type"` Checksum string `json:"checksum"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` } // UploadHandler gère les uploads de fichiers type UploadHandler struct { uploadValidator *services.UploadValidator auditService *services.AuditService logger *zap.Logger } // NewUploadHandler crée un nouveau handler d'upload func NewUploadHandler( uploadValidator *services.UploadValidator, auditService *services.AuditService, logger *zap.Logger, ) *UploadHandler { return &UploadHandler{ uploadValidator: uploadValidator, auditService: auditService, logger: logger, } } // UploadFile gère l'upload d'un fichier func (uh *UploadHandler) UploadFile() gin.HandlerFunc { return func(c *gin.Context) { // 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"}) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) return } // Parser la requête multipart var req UploadRequest if err := c.ShouldBind(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Récupérer le fichier fileHeader, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "No file provided"}) return } // Valider le fichier validationResult, err := uh.uploadValidator.ValidateFile(fileHeader, req.FileType) if err != nil { uh.logger.Error("File validation failed", zap.Error(err), zap.String("user_id", userID.String()), zap.String("file_name", fileHeader.Filename), ) c.JSON(http.StatusInternalServerError, gin.H{"error": "File validation failed"}) return } // Vérifier si le fichier est valide if !validationResult.Valid { uh.logger.Warn("Invalid file uploaded", zap.String("user_id", userID.String()), zap.String("file_name", fileHeader.Filename), zap.String("error", validationResult.Error), ) c.JSON(http.StatusBadRequest, gin.H{"error": validationResult.Error}) return } // Vérifier si le fichier a été mis en quarantaine if validationResult.Quarantined { uh.logger.Warn("File quarantined", zap.String("user_id", userID.String()), zap.String("file_name", fileHeader.Filename), zap.String("reason", validationResult.Error), ) c.JSON(http.StatusBadRequest, gin.H{ "error": "File rejected for security reasons", "details": validationResult.Error, }) return } // Créer l'enregistrement en base de données // Note: Dans un vrai environnement, il faudrait sauvegarder le fichier // et créer l'enregistrement dans la table tracks uploadID := uuid.New() // Log l'upload dans l'audit err = uh.auditService.LogUpload( c.Request.Context(), userID, req.TrackID, fileHeader.Filename, validationResult.FileSize, c.ClientIP(), c.GetHeader("User-Agent"), ) if err != nil { uh.logger.Error("Failed to log upload audit", zap.Error(err), zap.String("user_id", userID.String()), ) // Ne pas faire échouer l'upload pour une erreur d'audit } uh.logger.Info("File uploaded successfully", zap.String("user_id", userID.String()), zap.String("upload_id", uploadID.String()), zap.String("file_name", fileHeader.Filename), zap.Int64("file_size", validationResult.FileSize), zap.String("file_type", validationResult.FileType), ) // Retourner la réponse response := &UploadResponse{ ID: uploadID, TrackID: req.TrackID, FileName: fileHeader.Filename, FileSize: validationResult.FileSize, FileType: validationResult.FileType, Checksum: validationResult.Checksum, Status: "uploaded", CreatedAt: time.Now(), } c.JSON(http.StatusCreated, gin.H{ "message": "File uploaded successfully", "data": response, }) } } // GetUploadStatus récupère le statut d'un upload func (uh *UploadHandler) GetUploadStatus() gin.HandlerFunc { return func(c *gin.Context) { uploadIDStr := c.Param("id") uploadID, err := uuid.Parse(uploadIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid upload ID"}) return } // Récupérer le statut depuis la base de données // Note: Dans un vrai environnement, il faudrait interroger la DB c.JSON(http.StatusOK, gin.H{ "id": uploadID, "status": "completed", "progress": 100, }) } } // DeleteUpload supprime un upload func (uh *UploadHandler) DeleteUpload() gin.HandlerFunc { return func(c *gin.Context) { // 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"}) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) return } uploadIDStr := c.Param("id") uploadID, err := uuid.Parse(uploadIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid upload ID"}) return } // Log la suppression dans l'audit err = uh.auditService.LogDeletion( c.Request.Context(), userID, "upload", uploadID, c.ClientIP(), c.GetHeader("User-Agent"), ) if err != nil { uh.logger.Error("Failed to log deletion audit", zap.Error(err), zap.String("user_id", userID.String()), ) } uh.logger.Info("Upload deleted", zap.String("user_id", userID.String()), zap.String("upload_id", uploadID.String()), ) c.JSON(http.StatusOK, gin.H{ "message": "Upload deleted successfully", }) } } // GetUploadStats récupère les statistiques d'upload func (uh *UploadHandler) GetUploadStats() gin.HandlerFunc { return func(c *gin.Context) { // 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"}) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) return } // Récupérer les statistiques depuis la base de données // Note: Dans un vrai environnement, il faudrait interroger la DB stats := map[string]interface{}{ "total_uploads": 0, "total_size": 0, "audio_files": 0, "image_files": 0, "video_files": 0, } c.JSON(http.StatusOK, gin.H{ "user_id": userID, "stats": stats, }) } } // ValidateFileType valide le type de fichier func (uh *UploadHandler) ValidateFileType() gin.HandlerFunc { return func(c *gin.Context) { fileType := c.Query("type") if fileType == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "File type parameter required"}) return } // Vérifier si le type est supporté supportedTypes := []string{"audio", "image", "video"} isSupported := false for _, supportedType := range supportedTypes { if fileType == supportedType { isSupported = true break } } if !isSupported { c.JSON(http.StatusBadRequest, gin.H{ "error": "Unsupported file type", "supported_types": supportedTypes, }) return } c.JSON(http.StatusOK, gin.H{ "type": fileType, "supported": true, "supported_types": supportedTypes, }) } } // GetUploadLimits récupère les limites d'upload func (uh *UploadHandler) GetUploadLimits() gin.HandlerFunc { return func(c *gin.Context) { limits := map[string]interface{}{ "audio": map[string]interface{}{ "max_size": "100MB", "max_size_bytes": 100 * 1024 * 1024, "allowed_types": []string{ "audio/mpeg", "audio/mp3", "audio/wav", "audio/flac", "audio/aac", "audio/ogg", "audio/m4a", }, }, "image": map[string]interface{}{ "max_size": "10MB", "max_size_bytes": 10 * 1024 * 1024, "allowed_types": []string{ "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", }, }, "video": map[string]interface{}{ "max_size": "500MB", "max_size_bytes": 500 * 1024 * 1024, "allowed_types": []string{ "video/mp4", "video/webm", "video/ogg", "video/avi", }, }, } c.JSON(http.StatusOK, gin.H{ "limits": limits, }) } } // UploadProgress gère le suivi de progression d'upload func (uh *UploadHandler) UploadProgress() gin.HandlerFunc { return func(c *gin.Context) { uploadIDStr := c.Param("id") uploadID, err := uuid.Parse(uploadIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid upload ID"}) return } // Récupérer la progression depuis la base de données // Note: Dans un vrai environnement, il faudrait interroger la DB progress := map[string]interface{}{ "upload_id": uploadID, "status": "completed", "progress": 100, "bytes_uploaded": 0, "total_bytes": 0, "estimated_time_remaining": 0, } c.JSON(http.StatusOK, progress) } } // BatchUpload gère les uploads multiples func (uh *UploadHandler) BatchUpload() gin.HandlerFunc { return func(c *gin.Context) { // 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"}) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) return } // Parser le formulaire multipart form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid multipart form"}) return } files := form.File["files"] if len(files) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "No files provided"}) return } // Limiter le nombre de fichiers par batch maxFiles := 10 if len(files) > maxFiles { c.JSON(http.StatusBadRequest, gin.H{ "error": fmt.Sprintf("Too many files. Maximum %d files per batch", maxFiles), }) return } var results []map[string]interface{} var errors []string for i, fileHeader := range files { // Déterminer le type de fichier à partir de l'extension fileType := uh.uploadValidator.GetFileTypeFromPath(fileHeader.Filename) if fileType == "unknown" { errors = append(errors, fmt.Sprintf("File %d (%s): Unknown file type", i+1, fileHeader.Filename)) continue } // Valider le fichier validationResult, err := uh.uploadValidator.ValidateFile(fileHeader, fileType) if err != nil { errors = append(errors, fmt.Sprintf("File %d (%s): Validation error", i+1, fileHeader.Filename)) continue } if !validationResult.Valid { errors = append(errors, fmt.Sprintf("File %d (%s): %s", i+1, fileHeader.Filename, validationResult.Error)) continue } // Créer le résultat result := map[string]interface{}{ "index": i + 1, "file_name": fileHeader.Filename, "file_size": validationResult.FileSize, "file_type": validationResult.FileType, "checksum": validationResult.Checksum, "status": "validated", "upload_id": uuid.New(), } results = append(results, result) } uh.logger.Info("Batch upload processed", zap.String("user_id", userID.String()), zap.Int("total_files", len(files)), zap.Int("successful", len(results)), zap.Int("errors", len(errors)), ) c.JSON(http.StatusOK, gin.H{ "message": "Batch upload processed", "results": results, "errors": errors, "summary": map[string]interface{}{ "total_files": len(files), "successful": len(results), "errors": len(errors), }, }) } }