179 lines
4.8 KiB
Go
179 lines
4.8 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var (
|
|
// ErrShareNotFound est retourné quand un share n'est pas trouvé
|
|
ErrShareNotFound = errors.New("share not found")
|
|
// ErrShareExpired est retourné quand un share a expiré
|
|
ErrShareExpired = errors.New("share link expired")
|
|
// ErrSharePermissionDenied est retourné quand la permission demandée n'est pas accordée
|
|
ErrSharePermissionDenied = errors.New("permission denied")
|
|
)
|
|
|
|
// TrackShareService gère le partage de tracks
|
|
type TrackShareService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewTrackShareService crée un nouveau service de partage de tracks
|
|
func NewTrackShareService(db *gorm.DB) *TrackShareService {
|
|
return &TrackShareService{db: db}
|
|
}
|
|
|
|
// generateShareToken génère un token unique sécurisé
|
|
func generateShareToken() (string, error) {
|
|
bytes := make([]byte, 32)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// CreateShare crée un nouveau lien de partage pour un track
|
|
// MIGRATION UUID: Completée. UserID et TrackID en UUID.
|
|
func (s *TrackShareService) CreateShare(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, permissions string, expiresAt *time.Time) (*models.TrackShare, error) {
|
|
// Vérifier que le track existe et appartient à l'utilisateur
|
|
var track models.Track
|
|
if err := s.db.First(&track, "id = ?", trackID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrTrackNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
if track.UserID != userID {
|
|
return nil, ErrForbidden
|
|
}
|
|
|
|
// Générer un token unique
|
|
token, err := generateShareToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'unicité du token (très peu probable mais on vérifie)
|
|
var existingShare models.TrackShare
|
|
for {
|
|
if err := s.db.Where("share_token = ?", token).First(&existingShare).Error; errors.Is(err, gorm.ErrRecordNotFound) {
|
|
break
|
|
}
|
|
token, err = generateShareToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
share := &models.TrackShare{
|
|
TrackID: trackID,
|
|
UserID: userID,
|
|
ShareToken: token,
|
|
Permissions: permissions,
|
|
ExpiresAt: expiresAt,
|
|
AccessCount: 0,
|
|
}
|
|
|
|
if err := s.db.Create(share).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return share, nil
|
|
}
|
|
|
|
// ValidateShareToken valide un token de partage et retourne le share
|
|
func (s *TrackShareService) ValidateShareToken(ctx context.Context, token string) (*models.TrackShare, error) {
|
|
var share models.TrackShare
|
|
if err := s.db.Where("share_token = ? AND deleted_at IS NULL", token).First(&share).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrShareNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if share.ExpiresAt != nil && share.ExpiresAt.Before(time.Now()) {
|
|
return nil, ErrShareExpired
|
|
}
|
|
|
|
// Incrémenter le compteur d'accès
|
|
s.db.Model(&share).Update("access_count", gorm.Expr("access_count + 1"))
|
|
|
|
// Recharger l'objet pour obtenir la valeur mise à jour
|
|
if err := s.db.First(&share, share.ID).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to reload share: %w", err)
|
|
}
|
|
|
|
return &share, nil
|
|
}
|
|
|
|
// CheckPermission vérifie si un share a une permission spécifique
|
|
func (s *TrackShareService) CheckPermission(share *models.TrackShare, permission string) bool {
|
|
if share == nil {
|
|
return false
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if share.ExpiresAt != nil && share.ExpiresAt.Before(time.Now()) {
|
|
return false
|
|
}
|
|
|
|
// Vérifier les permissions
|
|
permissions := strings.Split(share.Permissions, ",")
|
|
for _, p := range permissions {
|
|
if strings.TrimSpace(strings.ToLower(p)) == strings.ToLower(permission) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetShareByToken récupère un share par son token (sans incrémenter le compteur)
|
|
func (s *TrackShareService) GetShareByToken(ctx context.Context, token string) (*models.TrackShare, error) {
|
|
var share models.TrackShare
|
|
if err := s.db.Where("share_token = ? AND deleted_at IS NULL", token).First(&share).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrShareNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Vérifier l'expiration
|
|
if share.ExpiresAt != nil && share.ExpiresAt.Before(time.Now()) {
|
|
return nil, ErrShareExpired
|
|
}
|
|
|
|
return &share, nil
|
|
}
|
|
|
|
// RevokeShare révoque un lien de partage
|
|
// MIGRATION UUID: Completée. UserID et ShareID en UUID.
|
|
func (s *TrackShareService) RevokeShare(ctx context.Context, shareID uuid.UUID, userID uuid.UUID) error {
|
|
var share models.TrackShare
|
|
if err := s.db.First(&share, "id = ?", shareID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return ErrShareNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Vérifier que l'utilisateur est le propriétaire
|
|
if share.UserID != userID {
|
|
return ErrForbidden
|
|
}
|
|
|
|
// Soft delete
|
|
return s.db.Delete(&share).Error
|
|
}
|