476 lines
13 KiB
Go
476 lines
13 KiB
Go
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(),
|
|
}
|
|
|
|
RespondSuccess(c, 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
|
|
RespondSuccess(c, 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()),
|
|
)
|
|
|
|
RespondSuccess(c, 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,
|
|
}
|
|
|
|
RespondSuccess(c, 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
|
|
}
|
|
|
|
RespondSuccess(c, 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",
|
|
},
|
|
},
|
|
}
|
|
|
|
RespondSuccess(c, 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,
|
|
}
|
|
|
|
RespondSuccess(c, 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)),
|
|
)
|
|
|
|
RespondSuccess(c, 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),
|
|
},
|
|
})
|
|
}
|
|
}
|