191 lines
6.2 KiB
Go
191 lines
6.2 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"github.com/google/uuid"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"veza-backend-api/internal/models"
|
|
)
|
|
|
|
var (
|
|
// ErrPlaylistShareNotFound est retourné quand un share de playlist n'est pas trouvé
|
|
ErrPlaylistShareNotFound = errors.New("playlist share not found")
|
|
// ErrPlaylistShareExpired est retourné quand un share de playlist a expiré
|
|
ErrPlaylistShareExpired = errors.New("playlist share link expired")
|
|
)
|
|
|
|
// PlaylistShareService gère le partage de playlists
|
|
// T0488: Create Playlist Public Share Link
|
|
type PlaylistShareService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewPlaylistShareService crée un nouveau service de partage de playlists
|
|
func NewPlaylistShareService(db *gorm.DB) *PlaylistShareService {
|
|
return &PlaylistShareService{db: db}
|
|
}
|
|
|
|
// generateShareToken génère un token unique sécurisé
|
|
func generatePlaylistShareToken() (string, error) {
|
|
bytes := make([]byte, 32)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// CreateShareLink crée un nouveau lien de partage public pour une playlist
|
|
// T0488: Create Playlist Public Share Link
|
|
// MIGRATION UUID: Completée. playlistID et userID sont des UUIDs.
|
|
func (s *PlaylistShareService) CreateShareLink(ctx context.Context, playlistID uuid.UUID, userID uuid.UUID, expiresAt *time.Time) (*models.PlaylistShareLink, error) {
|
|
// Vérifier que la playlist existe et appartient à l'utilisateur
|
|
var playlist models.Playlist
|
|
if err := s.db.First(&playlist, "id = ?", playlistID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("playlist not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est le propriétaire ou a la permission admin
|
|
if playlist.UserID != userID {
|
|
// Vérifier si l'utilisateur est collaborateur avec permission admin
|
|
var collaborator models.PlaylistCollaborator
|
|
if err := s.db.Where("playlist_id = ? AND user_id = ? AND permission = ?", playlistID, userID, models.PlaylistPermissionAdmin).First(&collaborator).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("forbidden: only owner or admin can create share links")
|
|
}
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Vérifier si un lien de partage existe déjà pour cette playlist
|
|
var existingLink models.PlaylistShareLink
|
|
if err := s.db.Where("playlist_id = ? AND deleted_at IS NULL", playlistID).First(&existingLink).Error; err == nil {
|
|
// Un lien existe déjà, vérifier s'il est expiré
|
|
if existingLink.ExpiresAt != nil && existingLink.ExpiresAt.Before(time.Now()) {
|
|
// Le lien est expiré, on le supprime (soft delete) et on en crée un nouveau
|
|
s.db.Delete(&existingLink)
|
|
} else {
|
|
// Le lien existe et est valide, on le retourne
|
|
return &existingLink, nil
|
|
}
|
|
}
|
|
|
|
// Générer un token unique
|
|
token, err := generatePlaylistShareToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'unicité du token (très peu probable mais on vérifie)
|
|
var existingShare models.PlaylistShareLink
|
|
for {
|
|
if err := s.db.Where("share_token = ?", token).First(&existingShare).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
break
|
|
}
|
|
token, err = generatePlaylistShareToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
shareLink := &models.PlaylistShareLink{
|
|
PlaylistID: playlistID,
|
|
UserID: userID,
|
|
ShareToken: token,
|
|
ExpiresAt: expiresAt,
|
|
AccessCount: 0,
|
|
}
|
|
|
|
if err := s.db.Create(shareLink).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return shareLink, nil
|
|
}
|
|
|
|
// ValidateShareToken valide un token de partage et retourne le share link
|
|
// T0488: Create Playlist Public Share Link
|
|
func (s *PlaylistShareService) ValidateShareToken(ctx context.Context, token string) (*models.PlaylistShareLink, error) {
|
|
var shareLink models.PlaylistShareLink
|
|
if err := s.db.Where("share_token = ? AND deleted_at IS NULL", token).First(&shareLink).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrPlaylistShareNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if shareLink.ExpiresAt != nil && shareLink.ExpiresAt.Before(time.Now()) {
|
|
return nil, ErrPlaylistShareExpired
|
|
}
|
|
|
|
// Incrémenter le compteur d'accès
|
|
s.db.Model(&shareLink).Update("access_count", gorm.Expr("access_count + 1"))
|
|
|
|
return &shareLink, nil
|
|
}
|
|
|
|
// GetShareLinkByToken récupère un share link par son token (sans incrémenter le compteur)
|
|
// T0488: Create Playlist Public Share Link
|
|
func (s *PlaylistShareService) GetShareLinkByToken(ctx context.Context, token string) (*models.PlaylistShareLink, error) {
|
|
var shareLink models.PlaylistShareLink
|
|
if err := s.db.Where("share_token = ? AND deleted_at IS NULL", token).First(&shareLink).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrPlaylistShareNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if shareLink.ExpiresAt != nil && shareLink.ExpiresAt.Before(time.Now()) {
|
|
return nil, ErrPlaylistShareExpired
|
|
}
|
|
|
|
return &shareLink, nil
|
|
}
|
|
|
|
// RevokeShareLink révoque un lien de partage
|
|
// T0488: Create Playlist Public Share Link
|
|
func (s *PlaylistShareService) RevokeShareLink(ctx context.Context, shareLinkID, userID uuid.UUID) error {
|
|
var shareLink models.PlaylistShareLink
|
|
if err := s.db.First(&shareLink, "id = ?", shareLinkID).Error; err != nil { // UUID query
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrPlaylistShareNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est le propriétaire
|
|
if shareLink.UserID != userID {
|
|
return errors.New("forbidden")
|
|
}
|
|
|
|
// Soft delete
|
|
return s.db.Delete(&shareLink).Error
|
|
}
|
|
|
|
// GetShareLinkByPlaylistID récupère le lien de partage actif pour une playlist
|
|
// T0488: Create Playlist Public Share Link
|
|
func (s *PlaylistShareService) GetShareLinkByPlaylistID(ctx context.Context, playlistID uuid.UUID) (*models.PlaylistShareLink, error) {
|
|
var shareLink models.PlaylistShareLink
|
|
if err := s.db.Where("playlist_id = ? AND deleted_at IS NULL", playlistID).First(&shareLink).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrPlaylistShareNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if shareLink.ExpiresAt != nil && shareLink.ExpiresAt.Before(time.Now()) {
|
|
return nil, ErrPlaylistShareExpired
|
|
}
|
|
|
|
return &shareLink, nil
|
|
}
|