168 lines
5.8 KiB
Go
168 lines
5.8 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"veza-backend-api/internal/models"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// PlaylistFollowService gère les opérations sur les follows de playlists
|
|
// T0489: Create Playlist Follow Feature
|
|
type PlaylistFollowService struct {
|
|
db *gorm.DB
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewPlaylistFollowService crée un nouveau service de follows de playlists
|
|
func NewPlaylistFollowService(db *gorm.DB, logger *zap.Logger) *PlaylistFollowService {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &PlaylistFollowService{
|
|
db: db,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// FollowPlaylist ajoute un follow d'un utilisateur sur une playlist
|
|
// MIGRATION UUID: Completée. userID et playlistID sont des UUIDs.
|
|
func (s *PlaylistFollowService) FollowPlaylist(ctx context.Context, userID uuid.UUID, playlistID uuid.UUID) error {
|
|
// Vérifier si la playlist existe
|
|
var playlist models.Playlist
|
|
if err := s.db.WithContext(ctx).First(&playlist, "id = ?", playlistID).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("playlist not found")
|
|
}
|
|
return fmt.Errorf("failed to check playlist: %w", err)
|
|
}
|
|
|
|
// Vérifier si l'utilisateur est le propriétaire (ne peut pas suivre sa propre playlist)
|
|
if playlist.UserID == userID {
|
|
return errors.New("cannot follow own playlist")
|
|
}
|
|
|
|
// Vérifier si l'utilisateur suit déjà cette playlist
|
|
var existing models.PlaylistFollow
|
|
if err := s.db.WithContext(ctx).Where("user_id = ? AND playlist_id = ? AND deleted_at IS NULL", userID, playlistID).First(&existing).Error; err == nil {
|
|
// Déjà suivi, retourner nil (idempotent)
|
|
return nil
|
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return fmt.Errorf("failed to check existing follow: %w", err)
|
|
}
|
|
|
|
// Créer le follow
|
|
follow := models.PlaylistFollow{
|
|
UserID: userID,
|
|
PlaylistID: playlistID,
|
|
}
|
|
if err := s.db.WithContext(ctx).Create(&follow).Error; err != nil {
|
|
return fmt.Errorf("failed to create follow: %w", err)
|
|
}
|
|
|
|
// Mettre à jour le compteur de followers de la playlist
|
|
if err := s.db.WithContext(ctx).Model(&playlist).UpdateColumn("follower_count", gorm.Expr("follower_count + ?", 1)).Error; err != nil {
|
|
s.logger.Warn("Failed to update playlist follower_count",
|
|
zap.String("playlist_id", playlistID.String()),
|
|
zap.Error(err),
|
|
)
|
|
// Ne pas retourner l'erreur, le follow a été créé avec succès
|
|
}
|
|
|
|
s.logger.Info("Playlist followed",
|
|
zap.String("user_id", userID.String()),
|
|
zap.String("playlist_id", playlistID.String()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnfollowPlaylist supprime un follow d'un utilisateur sur une playlist
|
|
// MIGRATION UUID: Completée. userID et playlistID sont des UUIDs.
|
|
func (s *PlaylistFollowService) UnfollowPlaylist(ctx context.Context, userID uuid.UUID, playlistID uuid.UUID) error {
|
|
// Vérifier si le follow existe
|
|
var follow models.PlaylistFollow
|
|
if err := s.db.WithContext(ctx).Where("user_id = ? AND playlist_id = ? AND deleted_at IS NULL", userID, playlistID).First(&follow).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// Pas de follow à supprimer, retourner nil (idempotent)
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to check follow: %w", err)
|
|
}
|
|
|
|
// Supprimer le follow (soft delete)
|
|
if err := s.db.WithContext(ctx).Delete(&follow).Error; err != nil {
|
|
return fmt.Errorf("failed to delete follow: %w", err)
|
|
}
|
|
|
|
// Mettre à jour le compteur de followers de la playlist
|
|
var playlist models.Playlist
|
|
if err := s.db.WithContext(ctx).First(&playlist, "id = ?", playlistID).Error; err == nil {
|
|
// Use CASE expression for SQLite compatibility (GREATEST is not supported in SQLite)
|
|
if err := s.db.WithContext(ctx).Model(&playlist).UpdateColumn("follower_count", gorm.Expr("CASE WHEN follower_count - 1 < 0 THEN 0 ELSE follower_count - 1 END")).Error; err != nil {
|
|
s.logger.Warn("Failed to update playlist follower_count",
|
|
zap.String("playlist_id", playlistID.String()),
|
|
zap.Error(err),
|
|
)
|
|
// Ne pas retourner l'erreur, le follow a été supprimé avec succès
|
|
}
|
|
}
|
|
|
|
s.logger.Info("Playlist unfollowed",
|
|
zap.String("user_id", userID.String()),
|
|
zap.String("playlist_id", playlistID.String()),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsFollowing vérifie si un utilisateur suit une playlist
|
|
// MIGRATION UUID: Completée. userID et playlistID sont des UUIDs.
|
|
func (s *PlaylistFollowService) IsFollowing(ctx context.Context, userID uuid.UUID, playlistID uuid.UUID) (bool, error) {
|
|
var count int64
|
|
err := s.db.WithContext(ctx).Model(&models.PlaylistFollow{}).
|
|
Where("user_id = ? AND playlist_id = ? AND deleted_at IS NULL", userID, playlistID).
|
|
Count(&count).Error
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check follow: %w", err)
|
|
}
|
|
return count > 0, nil
|
|
}
|
|
|
|
// GetPlaylistFollowersCount retourne le nombre de followers d'une playlist
|
|
func (s *PlaylistFollowService) GetPlaylistFollowersCount(ctx context.Context, playlistID uuid.UUID) (int64, error) {
|
|
var count int64
|
|
err := s.db.WithContext(ctx).Model(&models.PlaylistFollow{}).
|
|
Where("playlist_id = ? AND deleted_at IS NULL", playlistID).
|
|
Count(&count).Error
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get followers count: %w", err)
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// GetFollowedPlaylists retourne toutes les playlists suivies par un utilisateur
|
|
// T0498: Create Playlist Recommendations
|
|
func (s *PlaylistFollowService) GetFollowedPlaylists(ctx context.Context, userID uuid.UUID) ([]*models.Playlist, error) {
|
|
var playlists []*models.Playlist
|
|
|
|
err := s.db.WithContext(ctx).
|
|
Joins("INNER JOIN playlist_follows ON playlist_follows.playlist_id = playlists.id").
|
|
Where("playlist_follows.user_id = ? AND playlist_follows.deleted_at IS NULL", userID).
|
|
Preload("User").
|
|
Preload("Tracks").
|
|
Preload("Tracks.Track").
|
|
Find(&playlists).Error
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get followed playlists: %w", err)
|
|
}
|
|
|
|
return playlists, nil
|
|
}
|