package services import ( "context" "encoding/json" "errors" "fmt" "veza-backend-api/internal/models" "github.com/google/uuid" "go.uber.org/zap" "gorm.io/gorm" ) // TrackHistoryService gère l'historique des modifications de tracks type TrackHistoryService struct { db *gorm.DB logger *zap.Logger } // NewTrackHistoryService crée un nouveau service d'historique de tracks func NewTrackHistoryService(db *gorm.DB, logger *zap.Logger) *TrackHistoryService { if logger == nil { logger = zap.NewNop() } return &TrackHistoryService{ db: db, logger: logger, } } // RecordHistoryParams représente les paramètres pour enregistrer un historique // MIGRATION UUID: UserID et TrackID en UUID type RecordHistoryParams struct { TrackID uuid.UUID UserID uuid.UUID Action models.TrackHistoryAction OldValue interface{} // Peut être n'importe quel type, sera sérialisé en JSON NewValue interface{} // Peut être n'importe quel type, sera sérialisé en JSON } // RecordHistory enregistre une entrée dans l'historique d'un track func (s *TrackHistoryService) RecordHistory(ctx context.Context, params RecordHistoryParams) (*models.TrackHistory, error) { // Vérifier que le track existe var track models.Track if err := s.db.WithContext(ctx).First(&track, "id = ?", params.TrackID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrTrackNotFound } return nil, fmt.Errorf("failed to get track: %w", err) } // Sérialiser old_value et new_value en JSON si nécessaire var oldValueStr string var newValueStr string if params.OldValue != nil { oldValueBytes, err := json.Marshal(params.OldValue) if err != nil { return nil, fmt.Errorf("failed to marshal old_value: %w", err) } oldValueStr = string(oldValueBytes) } if params.NewValue != nil { newValueBytes, err := json.Marshal(params.NewValue) if err != nil { return nil, fmt.Errorf("failed to marshal new_value: %w", err) } newValueStr = string(newValueBytes) } // Créer l'entrée d'historique (TrackHistory utilise UUID) history := &models.TrackHistory{ TrackID: params.TrackID, UserID: params.UserID, Action: params.Action, OldValue: oldValueStr, NewValue: newValueStr, } if err := s.db.WithContext(ctx).Create(history).Error; err != nil { return nil, fmt.Errorf("failed to create track history: %w", err) } s.logger.Info("Track history recorded", zap.String("track_id", params.TrackID.String()), zap.String("user_id", params.UserID.String()), zap.String("action", string(params.Action)), zap.String("history_id", history.ID.String()), ) return history, nil } // GetHistory récupère l'historique d'un track func (s *TrackHistoryService) GetHistory(ctx context.Context, trackID uuid.UUID, limit, offset int) ([]models.TrackHistory, int64, error) { // Vérifier que le track existe var track models.Track if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, 0, ErrTrackNotFound } return nil, 0, fmt.Errorf("failed to get track: %w", err) } // Compter le total d'entrées var total int64 if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}). Where("track_id = ?", trackID). Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to count track history: %w", err) } // Récupérer les entrées avec pagination var histories []models.TrackHistory query := s.db.WithContext(ctx). Where("track_id = ?", trackID). Order("created_at DESC") if limit > 0 { query = query.Limit(limit) } if offset > 0 { query = query.Offset(offset) } if err := query.Find(&histories).Error; err != nil { return nil, 0, fmt.Errorf("failed to get track history: %w", err) } return histories, total, nil } // GetHistoryByUser récupère l'historique des tracks modifiés par un utilisateur func (s *TrackHistoryService) GetHistoryByUser(ctx context.Context, userID uuid.UUID, limit, offset int) ([]models.TrackHistory, int64, error) { // Compter le total d'entrées var total int64 if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}). Where("user_id = ?", userID). Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to count user track history: %w", err) } // Récupérer les entrées avec pagination var histories []models.TrackHistory query := s.db.WithContext(ctx). Where("user_id = ?", userID). Order("created_at DESC") if limit > 0 { query = query.Limit(limit) } if offset > 0 { query = query.Offset(offset) } if err := query.Find(&histories).Error; err != nil { return nil, 0, fmt.Errorf("failed to get user track history: %w", err) } return histories, total, nil } // GetHistoryByAction récupère l'historique filtré par action func (s *TrackHistoryService) GetHistoryByAction(ctx context.Context, trackID uuid.UUID, action models.TrackHistoryAction, limit, offset int) ([]models.TrackHistory, int64, error) { // Vérifier que le track existe var track models.Track if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, 0, ErrTrackNotFound } return nil, 0, fmt.Errorf("failed to get track: %w", err) } // Compter le total d'entrées var total int64 if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}). Where("track_id = ? AND action = ?", trackID, action). Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to count track history by action: %w", err) } // Récupérer les entrées avec pagination var histories []models.TrackHistory query := s.db.WithContext(ctx). Where("track_id = ? AND action = ?", trackID, action). Order("created_at DESC") if limit > 0 { query = query.Limit(limit) } if offset > 0 { query = query.Offset(offset) } if err := query.Find(&histories).Error; err != nil { return nil, 0, fmt.Errorf("failed to get track history by action: %w", err) } return histories, total, nil }