Files
Primora/apps/backend/internal/handlers/events.go
T
Tomas DvorakandDevin 236357facb feat: Client-published events + realtime presence
- POST /projects/:id/events accepts client-authored events restricted to
  the custom.* namespace — system types can't be spoofed. They flow
  through the same publishEvent fan-out: realtime, webhooks, and
  event_pattern functions all fire.
- Presence: subscribe/unsubscribe bumps a per-project count and
  broadcasts presence.update {online} to the realtime stream only —
  deliberately not through publishEvent so webhooks and "*" functions
  don't fire on connect noise. GET …/realtime/presence reads the count.
- @primora/client gains publish() and presence(); CLI gains events:send.

Verified live: presence 0->1 on subscribe, presence.update frame
delivered, custom event delivered to SSE + fired a custom.* function,
spoofed document.created rejected 400.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-24 20:55:48 +02:00

45 lines
1.2 KiB
Go

package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
)
// publishEvent accepts a client-authored domain event (custom.* namespace)
// and fans it out through the standard publishEvent path — realtime
// subscribers, matching webhooks, and event_pattern functions all fire.
func (h *HTTPHandler) publishEvent(c *gin.Context) {
actor, projectID, ok := h.actorAndProject(c)
if !ok {
return
}
var body struct {
Type string `json:"type" validate:"required"`
Data map[string]any `json:"data"`
}
if !h.bindAndValidate(c, &body) {
return
}
if err := h.Platform.PublishProjectEvent(c.Request.Context(), actor, projectID, body.Type, body.Data); err != nil {
h.handleError(c, err)
return
}
c.JSON(http.StatusAccepted, gin.H{"published": body.Type})
}
// realtimePresence returns the count of clients currently subscribed to the
// project's realtime stream.
func (h *HTTPHandler) realtimePresence(c *gin.Context) {
actor, projectID, ok := h.actorAndProject(c)
if !ok {
return
}
n, err := h.Platform.Presence(c.Request.Context(), actor, projectID)
if err != nil {
h.handleError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"online": n})
}