veza/veza-backend-api/internal/services/playlist_version_service.go
senke b103a09a25 chore: consolidate CI, E2E, backend and frontend updates
- CI: workflows updates (cd, ci), remove playwright.yml
- E2E: global-setup, auth/playlists/profile specs
- Remove playwright-report and test-results artifacts from tracking
- Backend: auth, handlers, services, workers, migrations
- Frontend: components, features, vite config
- Add e2e-results.json to gitignore
- Docs: REMEDIATION_PROGRESS, audit archive
- Rust: chat-server, stream-server updates
2026-02-17 16:43:21 +01:00

223 lines
7.5 KiB
Go

package services
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"veza-backend-api/internal/models"
"veza-backend-api/internal/repositories"
"go.uber.org/zap"
"gorm.io/gorm"
)
// PlaylistVersionService gère les versions de playlists
// T0509: Create Playlist Version History
type PlaylistVersionService struct {
versionRepo repositories.PlaylistVersionRepository
playlistRepo repositories.PlaylistRepository
playlistTrackRepo repositories.PlaylistTrackRepository
logger *zap.Logger
}
// NewPlaylistVersionService crée un nouveau service de versions de playlists
func NewPlaylistVersionService(
versionRepo repositories.PlaylistVersionRepository,
playlistRepo repositories.PlaylistRepository,
playlistTrackRepo repositories.PlaylistTrackRepository,
logger *zap.Logger,
) *PlaylistVersionService {
if logger == nil {
logger = zap.NewNop()
}
return &PlaylistVersionService{
versionRepo: versionRepo,
playlistRepo: playlistRepo,
playlistTrackRepo: playlistTrackRepo,
logger: logger,
}
}
// SaveVersion sauvegarde une version de la playlist
// T0509: Create Playlist Version History
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistVersionService) SaveVersion(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, action models.PlaylistVersionAction) (*models.PlaylistVersion, error) {
// Récupérer la playlist avec ses tracks
playlist, err := s.playlistRepo.GetByIDWithTracks(ctx, playlistID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New("playlist not found")
}
return nil, fmt.Errorf("failed to get playlist: %w", err)
}
// Obtenir le prochain numéro de version
versionNumber, err := s.versionRepo.GetNextVersionNumber(ctx, playlistID)
if err != nil {
return nil, fmt.Errorf("failed to get next version number: %w", err)
}
// Créer un snapshot des tracks
tracksSnapshot, err := s.createTracksSnapshot(ctx, playlistID)
if err != nil {
s.logger.Warn("Failed to create tracks snapshot", zap.Error(err))
// Continuer même si le snapshot échoue
tracksSnapshot = "[]"
}
// Créer la version (PlaylistVersion utilise UUID)
version := &models.PlaylistVersion{
PlaylistID: playlistID,
UserID: userID,
Version: versionNumber,
Action: action,
Title: playlist.Title,
Description: playlist.Description,
IsPublic: playlist.IsPublic,
CoverURL: playlist.CoverURL,
TracksSnapshot: tracksSnapshot,
}
if err := s.versionRepo.Create(ctx, version); err != nil {
return nil, fmt.Errorf("failed to create version: %w", err)
}
s.logger.Info("Playlist version saved",
zap.String("playlist_id", playlistID.String()),
zap.String("user_id", userID.String()),
zap.Int("version", versionNumber),
zap.String("action", string(action)),
)
return version, nil
}
// createTracksSnapshot crée un snapshot JSON des tracks de la playlist
func (s *PlaylistVersionService) createTracksSnapshot(ctx context.Context, playlistID uuid.UUID) (string, error) {
// Récupérer les tracks de la playlist
playlist, err := s.playlistRepo.GetByIDWithTracks(ctx, playlistID)
if err != nil {
return "", err
}
// Créer un snapshot simple avec les IDs et positions
type TrackSnapshot struct {
TrackID uuid.UUID `json:"track_id"`
Position int `json:"position"`
}
snapshots := make([]TrackSnapshot, 0, len(playlist.Tracks))
for _, track := range playlist.Tracks {
snapshots = append(snapshots, TrackSnapshot{
TrackID: track.TrackID,
Position: track.Position,
})
}
// Sérialiser en JSON
data, err := json.Marshal(snapshots)
if err != nil {
return "", fmt.Errorf("failed to marshal tracks snapshot: %w", err)
}
return string(data), nil
}
// GetVersions récupère l'historique des versions d'une playlist
// T0509: Create Playlist Version History
func (s *PlaylistVersionService) GetVersions(ctx context.Context, playlistID uuid.UUID, limit, offset int) ([]*models.PlaylistVersion, int64, error) {
return s.versionRepo.GetByPlaylistID(ctx, playlistID, limit, offset)
}
// GetVersion récupère une version spécifique
// T0509: Create Playlist Version History
func (s *PlaylistVersionService) GetVersion(ctx context.Context, playlistID uuid.UUID, version int) (*models.PlaylistVersion, error) {
return s.versionRepo.GetByVersion(ctx, playlistID, version)
}
// RestoreVersion restaure une playlist à une version spécifique
// T0509: Create Playlist Version History
// MIGRATION UUID: userID en uuid.UUID, playlistID en uuid.UUID
func (s *PlaylistVersionService) RestoreVersion(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, version int) (*models.PlaylistVersion, error) {
// Récupérer la version à restaurer
versionToRestore, err := s.versionRepo.GetByVersion(ctx, playlistID, version)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New("version not found")
}
return nil, fmt.Errorf("failed to get version: %w", err)
}
// Récupérer la playlist actuelle
playlist, err := s.playlistRepo.GetByID(ctx, playlistID)
if err != nil {
return nil, fmt.Errorf("failed to get playlist: %w", err)
}
// Restaurer les propriétés de la playlist
playlist.Title = versionToRestore.Title
playlist.Description = versionToRestore.Description
playlist.IsPublic = versionToRestore.IsPublic
playlist.CoverURL = versionToRestore.CoverURL
if err := s.playlistRepo.Update(ctx, playlist); err != nil {
return nil, fmt.Errorf("failed to update playlist: %w", err)
}
// Restaurer les tracks si le snapshot existe
if versionToRestore.TracksSnapshot != "" {
if err := s.restoreTracksFromSnapshot(ctx, playlistID, versionToRestore.TracksSnapshot); err != nil {
s.logger.Warn("Failed to restore tracks from snapshot", zap.Error(err))
// Ne pas échouer la restauration si les tracks ne peuvent pas être restaurés
}
}
// Créer une nouvelle version pour la restauration
restoredVersion, err := s.SaveVersion(ctx, playlistID, userID, models.PlaylistVersionActionRestored)
if err != nil {
s.logger.Warn("Failed to save restored version", zap.Error(err))
// Retourner quand même la version restaurée
return versionToRestore, nil
}
s.logger.Info("Playlist version restored",
zap.String("playlist_id", playlistID.String()),
zap.String("user_id", userID.String()),
zap.Int("restored_version", version),
zap.Int("new_version", restoredVersion.Version),
)
return restoredVersion, nil
}
// restoreTracksFromSnapshot restaure les tracks depuis un snapshot
func (s *PlaylistVersionService) restoreTracksFromSnapshot(_ context.Context, playlistID uuid.UUID, snapshot string) error {
type TrackSnapshot struct {
TrackID uuid.UUID `json:"track_id"`
Position int `json:"position"`
}
var snapshots []TrackSnapshot
if err := json.Unmarshal([]byte(snapshot), &snapshots); err != nil {
return fmt.Errorf("failed to unmarshal tracks snapshot: %w", err)
}
// Supprimer tous les tracks actuels
// Note: Cette opération peut être coûteuse, mais nécessaire pour une restauration complète
// Dans une implémentation optimisée, on pourrait comparer et ne modifier que ce qui a changé
// Pour l'instant, on ne restaure pas automatiquement les tracks car cela nécessite
// de supprimer tous les tracks existants et de les recréer, ce qui peut être risqué
// Cette fonctionnalité peut être ajoutée plus tard si nécessaire
s.logger.Info("Tracks snapshot restoration skipped (not implemented)",
zap.String("playlist_id", playlistID.String()),
zap.Int("tracks_count", len(snapshots)),
)
return nil
}