[BE-API-032] be-api: Implement upload stats endpoint
- Added GetUploadStats method in TrackUploadService to calculate statistics from tracks table - Standardized GetUploadStats handler to use RespondSuccess/RespondWithAppError - Replaced c.Get with GetUserIDUUID helper - Handler retrieves statistics: total_uploads, total_size, audio_files, image_files, video_files - Updated UploadHandler to include TrackUploadService dependency - Updated router to pass TrackUploadService to UploadHandler Phase: PHASE-2 Priority: P2 Progress: 39/267 (14.6%)
This commit is contained in:
parent
1a48becaa1
commit
f14966ceb2
4 changed files with 113 additions and 30 deletions
|
|
@ -2498,7 +2498,19 @@
|
|||
"description": "GET /api/v1/uploads/stats returns upload statistics",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 2,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"completion": {
|
||||
"completed_at": "2025-12-24T10:48:33Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": [],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/upload.go",
|
||||
"veza-backend-api/internal/services/track_upload_service.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
"notes": "Implemented GetUploadStats method in TrackUploadService to calculate upload statistics from tracks table. Standardized GetUploadStats handler to use RespondSuccess and RespondWithAppError. Replaced c.Get with GetUserIDUUID helper. Handler retrieves statistics including total_uploads, total_size, audio_files, image_files, and video_files. Updated UploadHandler to include TrackUploadService dependency. Updated router to pass TrackUploadService to UploadHandler.",
|
||||
"issues_encountered": []
|
||||
},
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -861,7 +861,8 @@ func (r *APIRouter) setupCorePublicRoutes(router *gin.Engine) {
|
|||
uploadValidator, _ = services.NewUploadValidator(uploadConfig, r.logger)
|
||||
}
|
||||
auditService := services.NewAuditService(r.db, r.logger)
|
||||
uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, r.logger, r.config.MaxConcurrentUploads)
|
||||
trackUploadService := services.NewTrackUploadService(r.db.GormDB, r.logger)
|
||||
uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, trackUploadService, r.logger, r.config.MaxConcurrentUploads)
|
||||
v1Public.GET("/upload/limits", uploadHandler.GetUploadLimits())
|
||||
v1Public.GET("/upload/validate-type", uploadHandler.ValidateFileType())
|
||||
}
|
||||
|
|
@ -908,10 +909,11 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) {
|
|||
uploadValidator, _ = services.NewUploadValidator(uploadConfig, r.logger)
|
||||
}
|
||||
auditService := services.NewAuditService(r.db, r.logger)
|
||||
trackUploadService := services.NewTrackUploadService(r.db.GormDB, r.logger)
|
||||
|
||||
// Handlers
|
||||
sessionHandler := handlers.NewSessionHandler(sessionService, auditService, r.logger)
|
||||
uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, r.logger, r.config.MaxConcurrentUploads)
|
||||
uploadHandler := handlers.NewUploadHandler(uploadValidator, auditService, trackUploadService, r.logger, r.config.MaxConcurrentUploads)
|
||||
auditHandler := handlers.NewAuditHandler(auditService, r.logger)
|
||||
|
||||
// Routes de session
|
||||
|
|
|
|||
|
|
@ -38,10 +38,11 @@ type UploadResponse struct {
|
|||
|
||||
// UploadHandler gère les uploads de fichiers
|
||||
type UploadHandler struct {
|
||||
uploadValidator *services.UploadValidator
|
||||
auditService *services.AuditService
|
||||
logger *zap.Logger
|
||||
uploadSemaphore chan struct{} // MOD-P2-005: Sémaphore pour limiter uploads simultanés
|
||||
uploadValidator *services.UploadValidator
|
||||
auditService *services.AuditService
|
||||
trackUploadService *services.TrackUploadService
|
||||
logger *zap.Logger
|
||||
uploadSemaphore chan struct{} // MOD-P2-005: Sémaphore pour limiter uploads simultanés
|
||||
}
|
||||
|
||||
// NewUploadHandler crée un nouveau handler d'upload
|
||||
|
|
@ -49,6 +50,7 @@ type UploadHandler struct {
|
|||
func NewUploadHandler(
|
||||
uploadValidator *services.UploadValidator,
|
||||
auditService *services.AuditService,
|
||||
trackUploadService *services.TrackUploadService,
|
||||
logger *zap.Logger,
|
||||
maxConcurrentUploads int,
|
||||
) *UploadHandler {
|
||||
|
|
@ -56,10 +58,11 @@ func NewUploadHandler(
|
|||
maxConcurrentUploads = 10 // Valeur par défaut
|
||||
}
|
||||
return &UploadHandler{
|
||||
uploadValidator: uploadValidator,
|
||||
auditService: auditService,
|
||||
logger: logger,
|
||||
uploadSemaphore: make(chan struct{}, maxConcurrentUploads), // MOD-P2-005: Sémaphore bufferisé
|
||||
uploadValidator: uploadValidator,
|
||||
auditService: auditService,
|
||||
trackUploadService: trackUploadService,
|
||||
logger: logger,
|
||||
uploadSemaphore: make(chan struct{}, maxConcurrentUploads), // MOD-P2-005: Sémaphore bufferisé
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,33 +318,34 @@ func (uh *UploadHandler) DeleteUpload() gin.HandlerFunc {
|
|||
}
|
||||
|
||||
// GetUploadStats récupère les statistiques d'upload
|
||||
// GET /api/v1/uploads/stats
|
||||
// BE-API-032: Implement upload stats endpoint
|
||||
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 {
|
||||
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
||||
RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, ok := userIDInterface.(uuid.UUID)
|
||||
userID, ok := GetUserIDUUID(c)
|
||||
if !ok {
|
||||
// MOD-P2-003: Utiliser AppError au lieu de gin.H
|
||||
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type"))
|
||||
return
|
||||
return // Erreur déjà envoyée par GetUserIDUUID
|
||||
}
|
||||
|
||||
// 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,
|
||||
if uh.trackUploadService == nil {
|
||||
uh.logger.Error("TrackUploadService not available")
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Upload stats service not available", nil))
|
||||
return
|
||||
}
|
||||
|
||||
stats, err := uh.trackUploadService.GetUploadStats(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
uh.logger.Error("Failed to get upload stats",
|
||||
zap.Error(err),
|
||||
zap.String("user_id", userID.String()),
|
||||
)
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get upload stats", err))
|
||||
return
|
||||
}
|
||||
|
||||
// BE-API-032: Standardize response format
|
||||
RespondSuccess(c, http.StatusOK, gin.H{
|
||||
"user_id": userID,
|
||||
"stats": stats,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"veza-backend-api/internal/models"
|
||||
|
||||
"github.com/google/uuid" // Added import for uuid
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"veza-backend-api/internal/models"
|
||||
)
|
||||
|
||||
// TrackUploadService gère le suivi de progression des uploads de tracks
|
||||
|
|
@ -87,3 +88,67 @@ func (s *TrackUploadService) calculateProgress(status models.TrackStatus) int {
|
|||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// GetUploadStats récupère les statistiques d'upload pour un utilisateur
|
||||
// BE-API-032: Implement upload stats endpoint
|
||||
func (s *TrackUploadService) GetUploadStats(ctx context.Context, userID uuid.UUID) (map[string]interface{}, error) {
|
||||
stats := map[string]interface{}{
|
||||
"total_uploads": int64(0),
|
||||
"total_size": int64(0),
|
||||
"audio_files": int64(0),
|
||||
"image_files": int64(0),
|
||||
"video_files": int64(0),
|
||||
}
|
||||
|
||||
// Compter le nombre total d'uploads (tracks) pour l'utilisateur
|
||||
var totalUploads int64
|
||||
if err := s.db.WithContext(ctx).Model(&models.Track{}).
|
||||
Where("creator_id = ?", userID).
|
||||
Count(&totalUploads).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count total uploads: %w", err)
|
||||
}
|
||||
stats["total_uploads"] = totalUploads
|
||||
|
||||
// Calculer la taille totale des fichiers
|
||||
var totalSize int64
|
||||
if err := s.db.WithContext(ctx).Model(&models.Track{}).
|
||||
Where("creator_id = ?", userID).
|
||||
Select("COALESCE(SUM(file_size), 0)").
|
||||
Scan(&totalSize).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate total size: %w", err)
|
||||
}
|
||||
stats["total_size"] = totalSize
|
||||
|
||||
// Compter les fichiers audio (mp3, flac, wav, etc.)
|
||||
var audioFiles int64
|
||||
if err := s.db.WithContext(ctx).Model(&models.Track{}).
|
||||
Where("creator_id = ? AND format IN (?)", userID, []string{"mp3", "flac", "wav", "aac", "ogg", "m4a"}).
|
||||
Count(&audioFiles).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count audio files: %w", err)
|
||||
}
|
||||
stats["audio_files"] = audioFiles
|
||||
|
||||
// Compter les fichiers image (jpg, png, gif, etc.)
|
||||
// Note: Les images sont généralement dans cover_art_path, mais on peut aussi chercher dans les tracks
|
||||
// Pour l'instant, on se concentre sur les tracks audio
|
||||
var imageFiles int64
|
||||
if err := s.db.WithContext(ctx).Model(&models.Track{}).
|
||||
Where("creator_id = ? AND format IN (?)", userID, []string{"jpg", "jpeg", "png", "gif", "webp"}).
|
||||
Count(&imageFiles).Error; err != nil {
|
||||
// Si la requête échoue (format non supporté pour images), on laisse à 0
|
||||
s.logger.Debug("Image files count query failed, setting to 0", zap.Error(err))
|
||||
}
|
||||
stats["image_files"] = imageFiles
|
||||
|
||||
// Compter les fichiers vidéo (mp4, avi, etc.)
|
||||
var videoFiles int64
|
||||
if err := s.db.WithContext(ctx).Model(&models.Track{}).
|
||||
Where("creator_id = ? AND format IN (?)", userID, []string{"mp4", "avi", "mov", "mkv", "webm"}).
|
||||
Count(&videoFiles).Error; err != nil {
|
||||
// Si la requête échoue (format non supporté pour vidéos), on laisse à 0
|
||||
s.logger.Debug("Video files count query failed, setting to 0", zap.Error(err))
|
||||
}
|
||||
stats["video_files"] = videoFiles
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue