veza/veza-backend-api/internal/services/queue_session_service.go

135 lines
4.3 KiB
Go

package services
import (
"context"
"errors"
"strings"
"github.com/google/uuid"
"gorm.io/gorm"
"veza-backend-api/internal/models"
"veza-backend-api/internal/utils"
)
var (
ErrQueueSessionNotFound = errors.New("queue session not found")
ErrQueueSessionForbidden = errors.New("forbidden: only creator can delete session")
)
// QueueSessionService manages collaborative queue sessions (v0.203 Lot D1)
type QueueSessionService struct {
db *gorm.DB
}
// NewQueueSessionService creates a new QueueSessionService
func NewQueueSessionService(db *gorm.DB) *QueueSessionService {
return &QueueSessionService{db: db}
}
// CreateSession creates a new shared queue session and returns it with a unique token
func (s *QueueSessionService) CreateSession(ctx context.Context, userID uuid.UUID) (*models.QueueSession, error) {
// Generate unique token (retry on collision)
for i := 0; i < 5; i++ {
token := utils.GenerateRandomString(21)
session := &models.QueueSession{
ShareToken: token,
CreatorID: userID,
}
if err := s.db.WithContext(ctx).Create(session).Error; err != nil {
if strings.Contains(err.Error(), "duplicate key") || strings.Contains(err.Error(), "unique constraint") {
continue
}
return nil, err
}
return session, nil
}
return nil, errors.New("failed to generate unique token")
}
// GetSessionByToken returns the session and its items for a given token
func (s *QueueSessionService) GetSessionByToken(ctx context.Context, token string) (*models.QueueSession, []models.SharedQueueItem, error) {
var session models.QueueSession
if err := s.db.WithContext(ctx).Where("share_token = ?", token).First(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, ErrQueueSessionNotFound
}
return nil, nil, err
}
var items []models.SharedQueueItem
if err := s.db.WithContext(ctx).Where("session_id = ?", session.ID).
Preload("Track").
Order("position ASC").
Find(&items).Error; err != nil {
return nil, nil, err
}
return &session, items, nil
}
// DeleteSession deletes a session (creator only)
func (s *QueueSessionService) DeleteSession(ctx context.Context, token string, userID uuid.UUID) error {
var session models.QueueSession
if err := s.db.WithContext(ctx).Where("share_token = ?", token).First(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrQueueSessionNotFound
}
return err
}
if session.CreatorID != userID {
return ErrQueueSessionForbidden
}
return s.db.WithContext(ctx).Delete(&session).Error
}
// AddToSession adds a track to a session's queue
func (s *QueueSessionService) AddToSession(ctx context.Context, token string, trackID uuid.UUID) error {
var session models.QueueSession
if err := s.db.WithContext(ctx).Where("share_token = ?", token).First(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrQueueSessionNotFound
}
return err
}
var maxPos int
s.db.WithContext(ctx).Model(&models.SharedQueueItem{}).Where("session_id = ?", session.ID).Select("COALESCE(MAX(position), 0)").Scan(&maxPos)
item := &models.SharedQueueItem{
SessionID: session.ID,
TrackID: trackID,
Position: maxPos + 1,
}
return s.db.WithContext(ctx).Create(item).Error
}
// RemoveFromSession removes an item from a session's queue
func (s *QueueSessionService) RemoveFromSession(ctx context.Context, token string, itemID uuid.UUID) error {
var session models.QueueSession
if err := s.db.WithContext(ctx).Where("share_token = ?", token).First(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrQueueSessionNotFound
}
return err
}
var item models.SharedQueueItem
if err := s.db.WithContext(ctx).Where("session_id = ? AND id = ?", session.ID, itemID).First(&item).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrQueueSessionNotFound
}
return err
}
return s.db.WithContext(ctx).Delete(&item).Error
}
// GetSessionQueue returns the tracks in a session for display
func (s *QueueSessionService) GetSessionQueue(ctx context.Context, token string) ([]*models.Track, error) {
_, items, err := s.GetSessionByToken(ctx, token)
if err != nil {
return nil, err
}
tracks := make([]*models.Track, 0, len(items))
for i := range items {
if items[i].Track.ID != uuid.Nil {
tracks = append(tracks, &items[i].Track)
}
}
return tracks, nil
}