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 }