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

36 lines
1.1 KiB
Go
Raw Normal View History

feat(embed): /embed/track/:id widget + /oembed envelope + per-track OG tags (W3 Day 15) End-to-end embed pipeline. Standalone HTML widget for iframes, oEmbed JSON for unfurlers (Twitter/Discord/Slack), runtime per-track OG + Twitter player card on the SPA. Share-token storage + handlers were already in place from earlier — Day 15 only adds the embed surface. Backend (root router, no /api/v1 prefix — matches what scrapers expect) - internal/handlers/embed_handler.go : EmbedTrack renders inline HTML with OG tags + <audio controls>. DMCA-blocked tracks 451, private tracks 404 (don't leak existence). X-Frame-Options=ALLOWALL + CSP frame-ancestors=* so the page can be iframed by third parties. OEmbed handler accepts ?url=&format=json, validates the URL points at /tracks/:id, returns a type=rich envelope with an iframe HTML string. ?maxwidth clamped to [240, 1280]. - internal/api/routes_embed.go : registers the two endpoints. - internal/handlers/embed_handler_test.go : pure-function coverage for extractTrackIDFromURL (8 cases incl. trailing slash, query string, hash fragment, subpath) + parseSafeInt (overflow + non-digit rejection). Frontend - apps/web/src/features/tracks/hooks/useTrackOpenGraph.ts : runtime injection of og:* + twitter:player + <link rel=alternate> (oEmbed discovery) into document.head. Limitation noted inline — pure HTML scrapers don't see these ; the embed widget itself carries server-rendered OG tags so unfurlers always work. - TrackDetailPage : wires useTrackOpenGraph(track) on render. E2E (tests/e2e/30-embed-and-share.spec.ts) - 30. /embed/track/:id renders HTML with OG tags + audio src. - 31. /oembed returns valid JSON envelope (rich type, iframe HTML). - 32. /oembed rejects non-track URLs (400). - 33. share-token roundtrip — creator mints, anonymous resolves via /api/v1/tracks/shared/:token (re-uses existing share handler ; Day 15 didn't add new share infra, just covers it under the embed acceptance gate). Acceptance (Day 15) : embed widget Twitter card preview ✓ (OG tags present), oEmbed JSON valid ✓, share token roundtrip ✓. W3 verification gate : Redis Sentinel ✓ · MinIO distribué ✓ · CDN signed URLs ✓ · DMCA E2E ✓ · embed + share token ✓ · all 5 W3 days shipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:49:54 +00:00
package api
import (
"github.com/gin-gonic/gin"
"veza-backend-api/internal/handlers"
)
// setupEmbedRoutes registers the public embed widget + oEmbed
// endpoints. Both live on the root router (no /api/v1 prefix) so
// the URLs match what blogs / Twitter / Discord scrapers expect :
//
// GET /embed/track/:id — standalone HTML widget
// GET /oembed?url=&format=json — oEmbed JSON envelope
//
// v1.0.9 W3 Day 15.
func (r *APIRouter) setupEmbedRoutes(router *gin.Engine) {
frontend := r.config.FrontendURL
if frontend == "" {
frontend = "https://veza.fr"
}
apiBase := frontend // best default — assume frontend reverse-proxies API
if r.config.StreamServerURL != "" {
// Not actually the API base, but if we ever introduce an
// explicit API_BASE_URL config it should land here. For now
// the embed page uses relative-looking URLs which the
// browser resolves against the embed page's own origin.
_ = r.config.StreamServerURL
}
embedHandler := handlers.NewEmbedHandler(r.db.GormDB, frontend, apiBase, r.logger)
router.GET("/embed/track/:id", embedHandler.EmbedTrack)
router.GET("/oembed", embedHandler.OEmbed)
}