269 lines
8 KiB
Go
269 lines
8 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
"veza-backend-api/internal/models"
|
|
)
|
|
|
|
var (
|
|
// ErrVersionNotFound est retourné quand une version n'est pas trouvée
|
|
ErrVersionNotFound = errors.New("version not found")
|
|
)
|
|
|
|
// TrackVersionService gère le versioning de tracks
|
|
type TrackVersionService struct {
|
|
db *gorm.DB
|
|
logger *zap.Logger
|
|
uploadDir string
|
|
}
|
|
|
|
// NewTrackVersionService crée un nouveau service de versioning de tracks
|
|
func NewTrackVersionService(db *gorm.DB, logger *zap.Logger, uploadDir string) *TrackVersionService {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &TrackVersionService{
|
|
db: db,
|
|
logger: logger,
|
|
uploadDir: uploadDir,
|
|
}
|
|
}
|
|
|
|
// CreateVersionParams représente les paramètres pour créer une nouvelle version
|
|
type CreateVersionParams struct {
|
|
FilePath string
|
|
FileSize int64
|
|
Changelog string
|
|
}
|
|
|
|
// CreateVersion crée une nouvelle version d'un track
|
|
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
|
|
func (s *TrackVersionService) CreateVersion(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, params CreateVersionParams) (*models.TrackVersion, error) {
|
|
// Vérifier que le track existe et appartient à l'utilisateur
|
|
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, ErrTrackNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get track: %w", err)
|
|
}
|
|
|
|
if track.UserID != userID {
|
|
return nil, ErrForbidden
|
|
}
|
|
|
|
// Trouver le prochain numéro de version
|
|
var maxVersion int
|
|
if err := s.db.WithContext(ctx).Model(&models.TrackVersion{}).
|
|
Where("track_id = ?", trackID).
|
|
Select("COALESCE(MAX(version_number), 0)").
|
|
Scan(&maxVersion).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to get max version number: %w", err)
|
|
}
|
|
|
|
nextVersion := maxVersion + 1
|
|
|
|
// Créer la nouvelle version
|
|
version := &models.TrackVersion{
|
|
TrackID: trackID,
|
|
VersionNumber: nextVersion,
|
|
FilePath: params.FilePath,
|
|
FileSize: params.FileSize,
|
|
Changelog: params.Changelog,
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Create(version).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to create version: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Track version created",
|
|
zap.String("track_id", trackID.String()),
|
|
zap.String("version_id", version.ID.String()),
|
|
zap.Int("version_number", nextVersion),
|
|
zap.String("user_id", userID.String()),
|
|
)
|
|
|
|
return version, nil
|
|
}
|
|
|
|
// GetVersion récupère une version spécifique d'un track
|
|
func (s *TrackVersionService) GetVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID) (*models.TrackVersion, error) {
|
|
var version models.TrackVersion
|
|
if err := s.db.WithContext(ctx).
|
|
Where("id = ? AND track_id = ?", versionID, trackID).
|
|
First(&version).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrVersionNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get version: %w", err)
|
|
}
|
|
|
|
return &version, nil
|
|
}
|
|
|
|
// GetVersionByNumber récupère une version par son numéro
|
|
func (s *TrackVersionService) GetVersionByNumber(ctx context.Context, trackID uuid.UUID, versionNumber int) (*models.TrackVersion, error) {
|
|
var version models.TrackVersion
|
|
if err := s.db.WithContext(ctx).
|
|
Where("track_id = ? AND version_number = ?", trackID, versionNumber).
|
|
First(&version).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrVersionNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get version: %w", err)
|
|
}
|
|
|
|
return &version, nil
|
|
}
|
|
|
|
// ListVersions récupère toutes les versions d'un track
|
|
func (s *TrackVersionService) ListVersions(ctx context.Context, trackID uuid.UUID) ([]models.TrackVersion, error) {
|
|
var versions []models.TrackVersion
|
|
if err := s.db.WithContext(ctx).
|
|
Where("track_id = ?", trackID).
|
|
Order("version_number DESC").
|
|
Find(&versions).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to list versions: %w", err)
|
|
}
|
|
|
|
return versions, nil
|
|
}
|
|
|
|
// RestoreVersion restaure une version spécifique (copie le fichier de la version vers le track actuel)
|
|
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
|
|
func (s *TrackVersionService) RestoreVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID, userID uuid.UUID) error {
|
|
// Vérifier que le track existe et appartient à l'utilisateur
|
|
var track models.Track
|
|
if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrTrackNotFound
|
|
}
|
|
return fmt.Errorf("failed to get track: %w", err)
|
|
}
|
|
|
|
if track.UserID != userID {
|
|
return ErrForbidden
|
|
}
|
|
|
|
// Récupérer la version
|
|
version, err := s.GetVersion(ctx, trackID, versionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Vérifier que le fichier de la version existe
|
|
if _, err := os.Stat(version.FilePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("version file not found: %s", version.FilePath)
|
|
}
|
|
|
|
// Sauvegarder l'ancien fichier du track comme backup (optionnel, on pourrait créer une version automatique)
|
|
// Pour l'instant, on remplace directement
|
|
|
|
// Copier le fichier de la version vers le track
|
|
if err := copyFile(version.FilePath, track.FilePath); err != nil {
|
|
return fmt.Errorf("failed to restore version file: %w", err)
|
|
}
|
|
|
|
// Mettre à jour les métadonnées du track avec les informations de la version
|
|
updates := map[string]interface{}{
|
|
"file_size": version.FileSize,
|
|
}
|
|
|
|
if err := s.db.WithContext(ctx).Model(&track).Updates(updates).Error; err != nil {
|
|
return fmt.Errorf("failed to update track: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Track version restored",
|
|
zap.String("track_id", trackID.String()),
|
|
zap.String("version_id", versionID.String()),
|
|
zap.Int("version_number", version.VersionNumber),
|
|
zap.String("user_id", userID.String()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteVersion supprime une version spécifique
|
|
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
|
|
func (s *TrackVersionService) DeleteVersion(ctx context.Context, trackID uuid.UUID, versionID uuid.UUID, userID uuid.UUID) error {
|
|
// Vérifier que le track existe et appartient à l'utilisateur
|
|
var track models.Track
|
|
if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrTrackNotFound
|
|
}
|
|
return fmt.Errorf("failed to get track: %w", err)
|
|
}
|
|
|
|
if track.UserID != userID {
|
|
return ErrForbidden
|
|
}
|
|
|
|
// Récupérer la version
|
|
version, err := s.GetVersion(ctx, trackID, versionID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Supprimer le fichier de la version si il existe
|
|
if version.FilePath != "" {
|
|
if err := os.Remove(version.FilePath); err != nil && !os.IsNotExist(err) {
|
|
s.logger.Warn("Failed to delete version file",
|
|
zap.String("version_id", versionID.String()),
|
|
zap.String("file_path", version.FilePath),
|
|
zap.Error(err),
|
|
)
|
|
// On continue même si la suppression du fichier échoue
|
|
}
|
|
}
|
|
|
|
// Supprimer la version de la base de données (soft delete)
|
|
if err := s.db.WithContext(ctx).Delete(version).Error; err != nil {
|
|
return fmt.Errorf("failed to delete version: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Track version deleted",
|
|
zap.String("track_id", trackID.String()),
|
|
zap.String("version_id", versionID.String()),
|
|
zap.String("user_id", userID.String()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// copyFile est une fonction utilitaire pour copier un fichier
|
|
func copyFile(src, dst string) error {
|
|
// Créer le répertoire de destination si nécessaire
|
|
dstDir := filepath.Dir(dst)
|
|
if err := os.MkdirAll(dstDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
|
}
|
|
|
|
sourceFile, err := os.Open(src)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open source file: %w", err)
|
|
}
|
|
defer sourceFile.Close()
|
|
|
|
destinationFile, err := os.Create(dst)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create destination file: %w", err)
|
|
}
|
|
defer destinationFile.Close()
|
|
|
|
_, err = io.Copy(destinationFile, sourceFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to copy file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|