package services import ( "context" "fmt" "veza-backend-api/internal/models" "github.com/google/uuid" "go.uber.org/zap" "gorm.io/gorm" ) // TrackUploadService gère le suivi de progression des uploads de tracks type TrackUploadService struct { db *gorm.DB logger *zap.Logger } // NewTrackUploadService crée un nouveau service de suivi d'upload func NewTrackUploadService(db *gorm.DB, logger *zap.Logger) *TrackUploadService { if logger == nil { logger = zap.NewNop() } return &TrackUploadService{ db: db, logger: logger, } } // GetUploadProgress récupère la progression d'un upload de track func (s *TrackUploadService) GetUploadProgress(ctx context.Context, trackID uuid.UUID) (*models.UploadProgress, error) { // Changed trackID to uuid.UUID var track models.Track if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("track not found") } return nil, fmt.Errorf("failed to get track: %w", err) } // Calculer le pourcentage de progression basé sur le statut progress := s.calculateProgress(track.Status) return &models.UploadProgress{ TrackID: trackID, Status: track.Status, Progress: progress, Message: track.StatusMessage, StreamStatus: track.StreamStatus, StreamManifestURL: track.StreamManifestURL, }, nil } // UpdateUploadStatus met à jour le statut d'un track func (s *TrackUploadService) UpdateUploadStatus(ctx context.Context, trackID uuid.UUID, status models.TrackStatus, message string) error { // Changed trackID to uuid.UUID updates := map[string]interface{}{ "status": status, } if message != "" { updates["status_message"] = message } if err := s.db.WithContext(ctx).Model(&models.Track{}).Where("id = ?", trackID).Updates(updates).Error; err != nil { return fmt.Errorf("failed to update status: %w", err) } s.logger.Info("Track upload status updated", zap.Any("track_id", trackID), // Changed to zap.Any for uuid.UUID zap.String("status", string(status)), zap.String("message", message), ) return nil } // calculateProgress calcule le pourcentage de progression basé sur le statut func (s *TrackUploadService) calculateProgress(status models.TrackStatus) int { switch status { case models.TrackStatusUploading: return 25 // 25% pendant l'upload case models.TrackStatusProcessing: return 50 // 50% pendant le traitement case models.TrackStatusCompleted: return 100 // 100% une fois terminé case models.TrackStatusFailed: return 0 // 0% en cas d'échec default: 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 } // GetUploadOwnerID returns the creator (owner) user ID for a track. // SEC-06: Used for IDOR prevention in GetUploadStatus. func (s *TrackUploadService) GetUploadOwnerID(ctx context.Context, trackID uuid.UUID) (uuid.UUID, error) { var track models.Track if err := s.db.WithContext(ctx).Select("id, creator_id").First(&track, "id = ?", trackID).Error; err != nil { if err == gorm.ErrRecordNotFound { return uuid.Nil, fmt.Errorf("track not found") } return uuid.Nil, fmt.Errorf("failed to get track: %w", err) } return track.UserID, nil }