- 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
223 lines
7.5 KiB
Go
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
|
|
}
|