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 }