diff --git a/veza-backend-api/internal/api/routes_live.go b/veza-backend-api/internal/api/routes_live.go index d0013731c..963d6cfed 100644 --- a/veza-backend-api/internal/api/routes_live.go +++ b/veza-backend-api/internal/api/routes_live.go @@ -16,14 +16,19 @@ func (r *APIRouter) setupLiveRoutes(router *gin.RouterGroup) { live := router.Group("/live") { - live.GET("/streams", liveStreamHandler.ListLiveStreams) - live.GET("/streams/:id", liveStreamHandler.GetLiveStream) - + // Protected routes (me/* MUST come before :id) if r.config != nil && r.config.AuthMiddleware != nil { protected := live.Group("") protected.Use(r.config.AuthMiddleware.RequireAuth()) r.applyCSRFProtection(protected) + protected.GET("/streams/me", liveStreamHandler.GetMyStreams) + protected.GET("/streams/me/key", liveStreamHandler.GetMyStreamKey) + protected.POST("/streams/me/key/regenerate", liveStreamHandler.RegenerateStreamKey) protected.POST("/streams", liveStreamHandler.CreateLiveStream) + protected.PUT("/streams/:id", liveStreamHandler.UpdateLiveStream) } + // Public routes (after protected) + live.GET("/streams", liveStreamHandler.ListLiveStreams) + live.GET("/streams/:id", liveStreamHandler.GetLiveStream) } } diff --git a/veza-backend-api/internal/handlers/live_stream_handler.go b/veza-backend-api/internal/handlers/live_stream_handler.go index 64c3aa764..27e7c3f23 100644 --- a/veza-backend-api/internal/handlers/live_stream_handler.go +++ b/veza-backend-api/internal/handlers/live_stream_handler.go @@ -95,3 +95,108 @@ func (h *LiveStreamHandler) CreateLiveStream(c *gin.Context) { } 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}) +}