[Event-Bus P1] Core event router — POST /v1/events endpoint #21

Open
opened 2026-09-09 23:41:15 +00:00 by poimen · 0 comments
Member

What

Add POST /v1/events endpoint to the api-gateway that receives typed event envelopes and routes them to configured targets.

Why

Services generate events independently with no unified routing. Each webhook is point-to-point. Need a single ingestion point that can fan-out to multiple consumers.

Design

Event Schema

{"source": "forgejo", "type": "pr.merged", "subject": "rock/homelab/pulls/18", "data": {...}, "priority": 5, "metadata": {"trace_id": "abc"}}

Config-driven routing

events:
  routes:
    - match: {source: forgejo, type: "pr.*"}
      targets:
        - type: gotify
        - type: sqs
          queue: forgejo-events
    - match: {source: "*", type: "*"}
      targets:
        - type: sqs
          queue: events-catchall

Response

{"event_id": "evt_a1b2c3d4", "status": "accepted", "targets": ["gotify", "sqs:forgejo-events"]}

Files (homelab-frontend repo)

  • internal/events/event.go — types + validation
  • internal/events/router.go — match events to targets
  • internal/events/handler.go — HTTP handler
  • internal/config/ — extend with EventsConfig
  • internal/proxy/proxy.go — register route

Acceptance

Functional

  • POST /v1/events with valid envelope returns 202 Accepted with {event_id, status, targets, timestamp}
  • POST /v1/events without Authorization header returns 401 Unauthorized
  • POST /v1/events with JWT missing events:publish capability returns 403 Forbidden
  • POST /v1/events with malformed JSON returns 400 Bad Request with RFC 9457 problem detail
  • POST /v1/events with missing source or type field returns 400 with specific validation error
  • Event matching {source: "forgejo", type: "pr.created"} routes to both gotify and sqs targets per config
  • Glob matching: type: "pr.*" matches pr.created, pr.merged, pr.closed but not push
  • Wildcard route {source: "*", type: "*"} catches unmatched events
  • No matching route and no catchall returns 400 with "no route for event type"

Audit

  • Every accepted event logged: {"level":"info","message":"event routed","extra":{"event_id":"...","source":"forgejo","type":"pr.created","targets":"gotify,sqs:forgejo-events"}}
  • Rejected events logged with reason

Tests

  • go test ./internal/events/ passes — router matching logic (exact, glob, wildcard, no-match)
  • go test ./internal/events/ passes — handler validation (missing fields, bad JSON, auth)
  • Integration test: POST real event → verify 202 + correct target list in response
## What Add `POST /v1/events` endpoint to the api-gateway that receives typed event envelopes and routes them to configured targets. ## Why Services generate events independently with no unified routing. Each webhook is point-to-point. Need a single ingestion point that can fan-out to multiple consumers. ## Design ### Event Schema ```json {"source": "forgejo", "type": "pr.merged", "subject": "rock/homelab/pulls/18", "data": {...}, "priority": 5, "metadata": {"trace_id": "abc"}} ``` ### Config-driven routing ```yaml events: routes: - match: {source: forgejo, type: "pr.*"} targets: - type: gotify - type: sqs queue: forgejo-events - match: {source: "*", type: "*"} targets: - type: sqs queue: events-catchall ``` ### Response ```json {"event_id": "evt_a1b2c3d4", "status": "accepted", "targets": ["gotify", "sqs:forgejo-events"]} ``` ## Files (homelab-frontend repo) - `internal/events/event.go` — types + validation - `internal/events/router.go` — match events to targets - `internal/events/handler.go` — HTTP handler - `internal/config/` — extend with EventsConfig - `internal/proxy/proxy.go` — register route ## Acceptance ### Functional - [ ] `POST /v1/events` with valid envelope returns `202 Accepted` with `{event_id, status, targets, timestamp}` - [ ] `POST /v1/events` without `Authorization` header returns `401 Unauthorized` - [ ] `POST /v1/events` with JWT missing `events:publish` capability returns `403 Forbidden` - [ ] `POST /v1/events` with malformed JSON returns `400 Bad Request` with RFC 9457 problem detail - [ ] `POST /v1/events` with missing `source` or `type` field returns `400` with specific validation error - [ ] Event matching `{source: "forgejo", type: "pr.created"}` routes to both gotify and sqs targets per config - [ ] Glob matching: `type: "pr.*"` matches `pr.created`, `pr.merged`, `pr.closed` but not `push` - [ ] Wildcard route `{source: "*", type: "*"}` catches unmatched events - [ ] No matching route and no catchall returns `400` with `"no route for event type"` ### Audit - [ ] Every accepted event logged: `{"level":"info","message":"event routed","extra":{"event_id":"...","source":"forgejo","type":"pr.created","targets":"gotify,sqs:forgejo-events"}}` - [ ] Rejected events logged with reason ### Tests - [ ] `go test ./internal/events/` passes — router matching logic (exact, glob, wildcard, no-match) - [ ] `go test ./internal/events/` passes — handler validation (missing fields, bad JSON, auth) - [ ] Integration test: POST real event → verify 202 + correct target list in response
poimen added this to the Event-Driven Platform milestone 2026-09-09 23:41:15 +00:00
poimen added the area/event-buspriority/highstatus/todoarea/gatewaytype/feature labels 2026-09-09 23:41:16 +00:00
Sign in to join this conversation.