veza/veza-backend-api/internal/api/routes_live.go

45 lines
1.8 KiB
Go
Raw Normal View History

package api
import (
"github.com/gin-gonic/gin"
"veza-backend-api/internal/handlers"
"veza-backend-api/internal/repositories"
"veza-backend-api/internal/services"
)
// setupLiveRoutes configure les routes live streams
func (r *APIRouter) setupLiveRoutes(router *gin.RouterGroup) {
liveStreamRepo := repositories.NewLiveStreamRepository(r.db.GormDB)
roomRepo := repositories.NewRoomRepository(r.db.GormDB)
liveStreamService := services.NewLiveStreamService(liveStreamRepo, roomRepo)
liveStreamHandler := handlers.NewLiveStreamHandler(liveStreamService, r.logger)
callbackHandler := handlers.NewLiveStreamCallbackHandler(liveStreamService, r.logger)
live := router.Group("/live")
{
// v0.10.6 F471: Nginx-RTMP callbacks (secret-protected, no auth)
live.POST("/callback/publish", callbackHandler.HandlePublish)
live.POST("/callback/publish_done", callbackHandler.HandlePublishDone)
// 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)
feat(backend,web): surface RTMP ingest health on the Go Live page Fifth item of the v1.0.6 backlog. "Go Live" was silent when the nginx-rtmp profile wasn't up — an artist could copy the RTMP URL + stream key, fire up OBS, hit "Start Streaming" and broadcast into the void with no in-UI signal that the ingest wasn't listening. The audit flagged this 🟡 ("livestream sans feedback UI si nginx-rtmp down"). Backend (`GET /api/v1/live/health`) * `LiveHealthHandler` TCP-dials `NGINX_RTMP_ADDR` (default `localhost:1935`) with a 2s timeout. Reports `rtmp_reachable`, `rtmp_addr`, a UI-safe `error` string (no raw dial target in the body — avoids leaking internal hostnames to the browser), and `last_check_at`. * 15s TTL cache protected by a mutex so a burst of page loads can't hammer the ingest. First call dials; subsequent calls within TTL serve the cached verdict. * Response ships `Cache-Control: private, max-age=15` so browsers piggy-back the same quarter-minute window. * When the dial fails the handler emits a WARN log so an operator watching backend logs sees the outage before a user does. * Public endpoint — no auth. The "RTMP is up / down" signal has no sensitive payload and is useful pre-login too. Frontend * `useLiveHealth()` hook: react-query with 15s stale time, 1 retry, then falls back to an optimistic `{ rtmpReachable: true }` — we'd rather miss a banner than flash a false negative during a transient blip on the health endpoint itself. * `LiveRtmpHealthBanner`: amber, non-blocking banner with a Retry button that invalidates the health query. Copy explicitly tells the artist their stream key is still valid but broadcasting now won't reach anyone. * `GoLivePage` wraps `GoLiveView` in a vertical stack with the banner above — the view itself stays unchanged (the key + instructions remain readable even when the ingest is down). Tests * 3 Go tests: live listener reports reachable + Cache-Control header; dead address reports unreachable + UI-safe error (asserts no `127.0.0.1` leak); TTL cache survives listener teardown within window. * 3 Vitest tests: banner renders nothing when reachable; banner visible + Retry enabled when unreachable; Retry invalidates the right query key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:52:36 +00:00
// v1.0.6: RTMP ingest health probe — lets the Go Live UI render a
// warning banner when nginx-rtmp isn't up (so artists aren't
// silently broadcasting into a void).
live.GET("/health", handlers.LiveHealthHandler(r.logger))
}
}