135 lines
4.3 KiB
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
|
|
}
|