202 lines
6.1 KiB
Go
202 lines
6.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
|
|
apperrors "veza-backend-api/internal/errors"
|
|
"veza-backend-api/internal/models"
|
|
"veza-backend-api/internal/services"
|
|
)
|
|
|
|
// LiveStreamHandler handles live stream HTTP requests
|
|
type LiveStreamHandler struct {
|
|
service *services.LiveStreamService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewLiveStreamHandler creates a new LiveStreamHandler
|
|
func NewLiveStreamHandler(service *services.LiveStreamService, logger *zap.Logger) *LiveStreamHandler {
|
|
return &LiveStreamHandler{service: service, logger: logger}
|
|
}
|
|
|
|
// CreateLiveStreamRequest represents the request body for creating a live stream
|
|
type CreateLiveStreamRequest struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
ThumbnailURL string `json:"thumbnailUrl"`
|
|
StreamerName string `json:"streamer"`
|
|
Tags []string `json:"tags"`
|
|
}
|
|
|
|
// ListLiveStreams returns all live streams (public - optionally filter by is_live)
|
|
func (h *LiveStreamHandler) ListLiveStreams(c *gin.Context) {
|
|
var isLive *bool
|
|
if q := c.Query("is_live"); q != "" {
|
|
b, err := strconv.ParseBool(q)
|
|
if err == nil {
|
|
isLive = &b
|
|
}
|
|
}
|
|
streams, err := h.service.List(c.Request.Context(), isLive)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to list streams", err))
|
|
return
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{"streams": streams})
|
|
}
|
|
|
|
// GetLiveStream returns a single live stream by ID
|
|
func (h *LiveStreamHandler) GetLiveStream(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewValidationError("invalid stream ID"))
|
|
return
|
|
}
|
|
stream, err := h.service.Get(c.Request.Context(), id)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.NewNotFoundError("stream not found"))
|
|
return
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{"stream": stream})
|
|
}
|
|
|
|
// CreateLiveStream creates a new live stream (requires auth)
|
|
func (h *LiveStreamHandler) CreateLiveStream(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req CreateLiveStreamRequest
|
|
if appErr := NewCommonHandler(h.logger).BindAndValidateJSON(c, &req); appErr != nil {
|
|
RespondWithAppError(c, appErr)
|
|
return
|
|
}
|
|
stream := &models.LiveStream{
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
Category: req.Category,
|
|
ThumbnailURL: req.ThumbnailURL,
|
|
StreamerName: req.StreamerName,
|
|
Tags: req.Tags,
|
|
}
|
|
if stream.StreamerName == "" {
|
|
stream.StreamerName = "Streamer"
|
|
}
|
|
created, err := h.service.Create(c.Request.Context(), userID, stream)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create stream", err))
|
|
return
|
|
}
|
|
RespondSuccess(c, http.StatusCreated, gin.H{"stream": created})
|
|
}
|
|
|
|
// GetMyStreams returns the authenticated user's streams (including stream_key)
|
|
func (h *LiveStreamHandler) GetMyStreams(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
streams, err := h.service.ListByUser(c.Request.Context(), userID)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to list streams", err))
|
|
return
|
|
}
|
|
type streamWithKey struct {
|
|
*models.LiveStream
|
|
StreamKey string `json:"stream_key"`
|
|
}
|
|
result := make([]streamWithKey, len(streams))
|
|
for i, s := range streams {
|
|
result[i] = streamWithKey{LiveStream: s, StreamKey: s.StreamKey}
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{"streams": result})
|
|
}
|
|
|
|
// GetMyStreamKey returns the user's stream key (creates draft if none exist)
|
|
func (h *LiveStreamHandler) GetMyStreamKey(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
streams, err := h.service.ListByUser(c.Request.Context(), userID)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to get streams", err))
|
|
return
|
|
}
|
|
var streamKey string
|
|
if len(streams) > 0 {
|
|
streamKey = streams[0].StreamKey
|
|
} else {
|
|
draft := &models.LiveStream{Title: "My Stream"}
|
|
created, createErr := h.service.Create(c.Request.Context(), userID, draft)
|
|
if createErr != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to create stream", createErr))
|
|
return
|
|
}
|
|
streamKey = created.StreamKey
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{
|
|
"stream_key": streamKey,
|
|
"rtmp_url": "rtmp://stream.veza.app/live",
|
|
})
|
|
}
|
|
|
|
// RegenerateStreamKey generates a new stream key for the user's first stream
|
|
func (h *LiveStreamHandler) RegenerateStreamKey(c *gin.Context) {
|
|
userID, ok := GetUserIDUUID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
streams, err := h.service.ListByUser(c.Request.Context(), userID)
|
|
if err != nil || len(streams) == 0 {
|
|
RespondWithAppError(c, apperrors.NewNotFoundError("no stream found"))
|
|
return
|
|
}
|
|
newKey, err := h.service.RegenerateStreamKey(c.Request.Context(), streams[0].ID, userID)
|
|
if err != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to regenerate key", err))
|
|
return
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{
|
|
"stream_key": newKey,
|
|
"rtmp_url": "rtmp://stream.veza.app/live",
|
|
})
|
|
}
|
|
|
|
// UpdateLiveStream updates a live stream's metadata (ownership check)
|
|
func (h *LiveStreamHandler) UpdateLiveStream(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 stream ID"))
|
|
return
|
|
}
|
|
var req CreateLiveStreamRequest
|
|
if appErr := NewCommonHandler(h.logger).BindAndValidateJSON(c, &req); appErr != nil {
|
|
RespondWithAppError(c, appErr)
|
|
return
|
|
}
|
|
stream := &models.LiveStream{
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
Category: req.Category,
|
|
ThumbnailURL: req.ThumbnailURL,
|
|
StreamerName: req.StreamerName,
|
|
Tags: req.Tags,
|
|
}
|
|
updated, updateErr := h.service.Update(c.Request.Context(), id, userID, stream)
|
|
if updateErr != nil {
|
|
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to update stream", updateErr))
|
|
return
|
|
}
|
|
RespondSuccess(c, http.StatusOK, gin.H{"stream": updated})
|
|
}
|