veza/veza-backend-api/internal/handlers/queue_handler.go

174 lines
5.9 KiB
Go
Raw Normal View History

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
apperrors "veza-backend-api/internal/errors"
"veza-backend-api/internal/services"
)
// QueueHandler handles queue HTTP requests
type QueueHandler struct {
queueService *services.QueueService
logger *zap.Logger
}
// NewQueueHandler creates a new QueueHandler
func NewQueueHandler(queueService *services.QueueService, logger *zap.Logger) *QueueHandler {
return &QueueHandler{queueService: queueService, logger: logger}
}
// GetQueue returns the user's queue
feat(openapi): annotate queue + password-reset handlers + regen Closes the two annotation gaps that blocked finishing the orval migration in v1.0.8 : - queue_handler.go (5 routes — GetQueue, UpdateQueue, AddQueueItem, RemoveQueueItem, ClearQueue) — under @Tags Queue with @Security BearerAuth, @Param body/path, @Success/@Failure on the standard APIResponse envelope. - queue_session_handler.go (5 routes — CreateSession, GetSession, DeleteSession, AddToSession, RemoveFromSession). GetSession is public (no @Security tag) since the share-token URL is meant for join-via-link from outside the auth wall. - password_reset_handler.go (2 routes — RequestPasswordReset and ResetPassword factory functions). Both are public (no @Security) since they're the entry-points for users who can't log in. The request-side annotation documents the intentional generic 200 response (anti-enumeration: same body whether the email exists or not). After regen : - openapi.yaml gains 7 queue paths (/queue, /queue/items[/{id}], /queue/session[/{token}[/items[/{id}]]]) and 2 password paths (/auth/password/reset, /auth/password/reset-request). +568 LOC. - docs/{docs.go,swagger.json,swagger.yaml} updated identically by swag init. - apps/web/src/services/generated/queue/queue.ts created (10 HTTP funcs + matching React Query hooks). model/ index extended with the queue + password-reset request/response shapes. Validates with `swag init` (Swagger 2.0). go build ./... clean. No runtime behaviour change — annotations are pure metadata read by the spec generator. The orval regen IS the wiring point for the follow-up frontend commit (queue.ts migration + authService finish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:55:26 +00:00
// @Summary Get user playback queue
// @Description Returns the authenticated user's persistent playback queue with all items in position order
// @Tags Queue
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} APIResponse{data=object{queue=object,items=[]object}} "Queue + items"
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 500 {object} APIResponse "Internal Error"
// @Router /queue [get]
func (h *QueueHandler) GetQueue(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
q, err := h.queueService.GetOrCreateQueue(c.Request.Context(), userID)
if err != nil {
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get queue", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{
"queue": q,
"items": q.Items,
})
}
// UpdateQueue updates the user's queue
feat(openapi): annotate queue + password-reset handlers + regen Closes the two annotation gaps that blocked finishing the orval migration in v1.0.8 : - queue_handler.go (5 routes — GetQueue, UpdateQueue, AddQueueItem, RemoveQueueItem, ClearQueue) — under @Tags Queue with @Security BearerAuth, @Param body/path, @Success/@Failure on the standard APIResponse envelope. - queue_session_handler.go (5 routes — CreateSession, GetSession, DeleteSession, AddToSession, RemoveFromSession). GetSession is public (no @Security tag) since the share-token URL is meant for join-via-link from outside the auth wall. - password_reset_handler.go (2 routes — RequestPasswordReset and ResetPassword factory functions). Both are public (no @Security) since they're the entry-points for users who can't log in. The request-side annotation documents the intentional generic 200 response (anti-enumeration: same body whether the email exists or not). After regen : - openapi.yaml gains 7 queue paths (/queue, /queue/items[/{id}], /queue/session[/{token}[/items[/{id}]]]) and 2 password paths (/auth/password/reset, /auth/password/reset-request). +568 LOC. - docs/{docs.go,swagger.json,swagger.yaml} updated identically by swag init. - apps/web/src/services/generated/queue/queue.ts created (10 HTTP funcs + matching React Query hooks). model/ index extended with the queue + password-reset request/response shapes. Validates with `swag init` (Swagger 2.0). go build ./... clean. No runtime behaviour change — annotations are pure metadata read by the spec generator. The orval regen IS the wiring point for the follow-up frontend commit (queue.ts migration + authService finish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:55:26 +00:00
// @Summary Update user playback queue
// @Description Updates the authenticated user's queue state (current track / position / playback flags / item order)
// @Tags Queue
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body services.UpdateQueueRequest true "Queue update payload"
// @Success 200 {object} APIResponse{data=object{queue=object,items=[]object}} "Updated queue"
// @Failure 400 {object} APIResponse "Validation error"
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 500 {object} APIResponse "Internal Error"
// @Router /queue [put]
func (h *QueueHandler) UpdateQueue(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
var req services.UpdateQueueRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid request body"))
return
}
q, err := h.queueService.UpdateQueue(c.Request.Context(), userID, &req)
if err != nil {
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update queue", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{
"queue": q,
"items": q.Items,
})
}
// AddQueueItemRequest represents the request body for adding an item
type AddQueueItemRequest struct {
TrackID uuid.UUID `json:"track_id" binding:"required"`
}
// AddQueueItem adds a track to the queue
feat(openapi): annotate queue + password-reset handlers + regen Closes the two annotation gaps that blocked finishing the orval migration in v1.0.8 : - queue_handler.go (5 routes — GetQueue, UpdateQueue, AddQueueItem, RemoveQueueItem, ClearQueue) — under @Tags Queue with @Security BearerAuth, @Param body/path, @Success/@Failure on the standard APIResponse envelope. - queue_session_handler.go (5 routes — CreateSession, GetSession, DeleteSession, AddToSession, RemoveFromSession). GetSession is public (no @Security tag) since the share-token URL is meant for join-via-link from outside the auth wall. - password_reset_handler.go (2 routes — RequestPasswordReset and ResetPassword factory functions). Both are public (no @Security) since they're the entry-points for users who can't log in. The request-side annotation documents the intentional generic 200 response (anti-enumeration: same body whether the email exists or not). After regen : - openapi.yaml gains 7 queue paths (/queue, /queue/items[/{id}], /queue/session[/{token}[/items[/{id}]]]) and 2 password paths (/auth/password/reset, /auth/password/reset-request). +568 LOC. - docs/{docs.go,swagger.json,swagger.yaml} updated identically by swag init. - apps/web/src/services/generated/queue/queue.ts created (10 HTTP funcs + matching React Query hooks). model/ index extended with the queue + password-reset request/response shapes. Validates with `swag init` (Swagger 2.0). go build ./... clean. No runtime behaviour change — annotations are pure metadata read by the spec generator. The orval regen IS the wiring point for the follow-up frontend commit (queue.ts migration + authService finish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:55:26 +00:00
// @Summary Add track to queue
// @Description Appends a track to the authenticated user's playback queue
// @Tags Queue
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body AddQueueItemRequest true "Track to enqueue"
// @Success 201 {object} APIResponse{data=object{item=object}} "Created queue item"
// @Failure 400 {object} APIResponse "Validation error"
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 500 {object} APIResponse "Internal Error"
// @Router /queue/items [post]
func (h *QueueHandler) AddQueueItem(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
var req AddQueueItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithAppError(c, apperrors.NewValidationError("track_id is required"))
return
}
item, err := h.queueService.AddToQueue(c.Request.Context(), userID, req.TrackID)
if err != nil {
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to add to queue", err))
return
}
RespondSuccess(c, http.StatusCreated, gin.H{"item": item})
}
// RemoveQueueItem removes an item from the queue
feat(openapi): annotate queue + password-reset handlers + regen Closes the two annotation gaps that blocked finishing the orval migration in v1.0.8 : - queue_handler.go (5 routes — GetQueue, UpdateQueue, AddQueueItem, RemoveQueueItem, ClearQueue) — under @Tags Queue with @Security BearerAuth, @Param body/path, @Success/@Failure on the standard APIResponse envelope. - queue_session_handler.go (5 routes — CreateSession, GetSession, DeleteSession, AddToSession, RemoveFromSession). GetSession is public (no @Security tag) since the share-token URL is meant for join-via-link from outside the auth wall. - password_reset_handler.go (2 routes — RequestPasswordReset and ResetPassword factory functions). Both are public (no @Security) since they're the entry-points for users who can't log in. The request-side annotation documents the intentional generic 200 response (anti-enumeration: same body whether the email exists or not). After regen : - openapi.yaml gains 7 queue paths (/queue, /queue/items[/{id}], /queue/session[/{token}[/items[/{id}]]]) and 2 password paths (/auth/password/reset, /auth/password/reset-request). +568 LOC. - docs/{docs.go,swagger.json,swagger.yaml} updated identically by swag init. - apps/web/src/services/generated/queue/queue.ts created (10 HTTP funcs + matching React Query hooks). model/ index extended with the queue + password-reset request/response shapes. Validates with `swag init` (Swagger 2.0). go build ./... clean. No runtime behaviour change — annotations are pure metadata read by the spec generator. The orval regen IS the wiring point for the follow-up frontend commit (queue.ts migration + authService finish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:55:26 +00:00
// @Summary Remove queue item
// @Description Removes a single item from the authenticated user's playback queue by item ID
// @Tags Queue
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Queue item ID (UUID)"
// @Success 200 {object} APIResponse{data=object{message=string}} "Item removed"
// @Failure 400 {object} APIResponse "Invalid item ID"
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 404 {object} APIResponse "Queue item not found"
// @Router /queue/items/{id} [delete]
func (h *QueueHandler) RemoveQueueItem(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
id, err := uuid.Parse(c.Param("id"))
if err != nil {
RespondWithAppError(c, apperrors.NewValidationError("invalid item ID"))
return
}
if err := h.queueService.RemoveFromQueue(c.Request.Context(), userID, id); err != nil {
RespondWithAppError(c, apperrors.NewNotFoundError("queue item not found"))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "item removed"})
}
// ClearQueue removes all items from the queue
feat(openapi): annotate queue + password-reset handlers + regen Closes the two annotation gaps that blocked finishing the orval migration in v1.0.8 : - queue_handler.go (5 routes — GetQueue, UpdateQueue, AddQueueItem, RemoveQueueItem, ClearQueue) — under @Tags Queue with @Security BearerAuth, @Param body/path, @Success/@Failure on the standard APIResponse envelope. - queue_session_handler.go (5 routes — CreateSession, GetSession, DeleteSession, AddToSession, RemoveFromSession). GetSession is public (no @Security tag) since the share-token URL is meant for join-via-link from outside the auth wall. - password_reset_handler.go (2 routes — RequestPasswordReset and ResetPassword factory functions). Both are public (no @Security) since they're the entry-points for users who can't log in. The request-side annotation documents the intentional generic 200 response (anti-enumeration: same body whether the email exists or not). After regen : - openapi.yaml gains 7 queue paths (/queue, /queue/items[/{id}], /queue/session[/{token}[/items[/{id}]]]) and 2 password paths (/auth/password/reset, /auth/password/reset-request). +568 LOC. - docs/{docs.go,swagger.json,swagger.yaml} updated identically by swag init. - apps/web/src/services/generated/queue/queue.ts created (10 HTTP funcs + matching React Query hooks). model/ index extended with the queue + password-reset request/response shapes. Validates with `swag init` (Swagger 2.0). go build ./... clean. No runtime behaviour change — annotations are pure metadata read by the spec generator. The orval regen IS the wiring point for the follow-up frontend commit (queue.ts migration + authService finish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 22:55:26 +00:00
// @Summary Clear queue
// @Description Removes every item from the authenticated user's playback queue
// @Tags Queue
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} APIResponse{data=object{message=string}} "Queue cleared"
// @Failure 401 {object} APIResponse "Unauthorized"
// @Failure 500 {object} APIResponse "Internal Error"
// @Router /queue [delete]
func (h *QueueHandler) ClearQueue(c *gin.Context) {
userID, ok := GetUserIDUUID(c)
if !ok {
return
}
if err := h.queueService.ClearQueue(c.Request.Context(), userID); err != nil {
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to clear queue", err))
return
}
RespondSuccess(c, http.StatusOK, gin.H{"message": "queue cleared"})
}