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

139 lines
4.1 KiB
Go

package services
import (
"context"
"errors"
"github.com/google/uuid"
"go.uber.org/zap"
"gorm.io/gorm"
"veza-backend-api/internal/models"
)
// QueueService handles user playback queue operations
type QueueService struct {
db *gorm.DB
logger *zap.Logger
}
// NewQueueService creates a new QueueService
func NewQueueService(db *gorm.DB, logger *zap.Logger) *QueueService {
if logger == nil {
logger = zap.NewNop()
}
return &QueueService{db: db, logger: logger}
}
// GetOrCreateQueue returns the user's queue, creating it if it doesn't exist
func (s *QueueService) GetOrCreateQueue(ctx context.Context, userID uuid.UUID) (*models.Queue, error) {
var q models.Queue
err := s.db.WithContext(ctx).Where("user_id = ?", userID).First(&q).Error
if err == nil {
return s.loadQueueWithItems(ctx, &q)
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
q = models.Queue{UserID: userID}
if err := s.db.WithContext(ctx).Create(&q).Error; err != nil {
return nil, err
}
return s.loadQueueWithItems(ctx, &q)
}
func (s *QueueService) loadQueueWithItems(ctx context.Context, q *models.Queue) (*models.Queue, error) {
var items []models.QueueItem
if err := s.db.WithContext(ctx).Where("queue_id = ?", q.ID).Order("position ASC").Preload("Track").Find(&items).Error; err != nil {
return nil, err
}
q.Items = items
return q, nil
}
// UpdateQueue updates queue metadata (order, current position, etc.)
func (s *QueueService) UpdateQueue(ctx context.Context, userID uuid.UUID, req *UpdateQueueRequest) (*models.Queue, error) {
q, err := s.GetOrCreateQueue(ctx, userID)
if err != nil {
return nil, err
}
if req.CurrentTrackID != nil {
q.CurrentTrackID = req.CurrentTrackID
}
if req.CurrentPosition != nil {
q.CurrentPosition = *req.CurrentPosition
}
if req.IsPlaying != nil {
q.IsPlaying = *req.IsPlaying
}
if req.Shuffle != nil {
q.Shuffle = *req.Shuffle
}
if req.RepeatMode != nil {
q.RepeatMode = *req.RepeatMode
}
if req.Volume != nil {
q.Volume = *req.Volume
}
if req.ItemOrder != nil {
for i, itemID := range req.ItemOrder {
s.db.WithContext(ctx).Model(&models.QueueItem{}).Where("id = ? AND queue_id = ?", itemID, q.ID).Update("position", i)
}
}
if err := s.db.WithContext(ctx).Save(q).Error; err != nil {
return nil, err
}
return s.loadQueueWithItems(ctx, q)
}
// UpdateQueueRequest represents the request body for updating a queue
type UpdateQueueRequest struct {
CurrentTrackID *uuid.UUID `json:"current_track_id"`
CurrentPosition *int `json:"current_position"`
IsPlaying *bool `json:"is_playing"`
Shuffle *bool `json:"shuffle"`
RepeatMode *string `json:"repeat_mode"`
Volume *int `json:"volume"`
ItemOrder []uuid.UUID `json:"item_order"`
}
// AddToQueue adds a track to the user's queue
func (s *QueueService) AddToQueue(ctx context.Context, userID uuid.UUID, trackID uuid.UUID) (*models.QueueItem, error) {
q, err := s.GetOrCreateQueue(ctx, userID)
if err != nil {
return nil, err
}
var maxPos int
s.db.WithContext(ctx).Model(&models.QueueItem{}).Where("queue_id = ?", q.ID).Select("COALESCE(MAX(position), -1)").Scan(&maxPos)
item := models.QueueItem{QueueID: q.ID, TrackID: trackID, Position: maxPos + 1}
if err := s.db.WithContext(ctx).Create(&item).Error; err != nil {
return nil, err
}
s.db.WithContext(ctx).Preload("Track").First(&item, item.ID)
return &item, nil
}
// RemoveFromQueue removes an item from the queue
func (s *QueueService) RemoveFromQueue(ctx context.Context, userID uuid.UUID, itemID uuid.UUID) error {
q, err := s.GetOrCreateQueue(ctx, userID)
if err != nil {
return err
}
result := s.db.WithContext(ctx).Where("id = ? AND queue_id = ?", itemID, q.ID).Delete(&models.QueueItem{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
}
// ClearQueue removes all items from the user's queue
func (s *QueueService) ClearQueue(ctx context.Context, userID uuid.UUID) error {
q, err := s.GetOrCreateQueue(ctx, userID)
if err != nil {
return err
}
return s.db.WithContext(ctx).Where("queue_id = ?", q.ID).Delete(&models.QueueItem{}).Error
}