Kind: Controller
Source: atloria-monorepo/apps/api/src/events/events.controller.ts
Public event-ingest endpoint for browser surfaces (docs viewer, chat widget). Anonymous allowed (public docs visitors give feedback too); userId is attached when authenticated. Throttled — it's an append-only signal sink, not an API.
EventsController exposes a public, anonymous-friendly event ingestion endpoint used by browser surfaces like the docs viewer and chat widget. It accepts append-only telemetry/feedback signals, attaching a userId when the caller is authenticated, and is intentionally throttled because it is a signal sink rather than a general-purpose API. It routes incoming event payloads into the backend event pipeline/storage without exposing read/query capabilities.
Diagram
mermaidgraph LR A[Browser Surface<br/>Docs Viewer / Chat Widget] -->|POST /events| B[EventsController] B -->|Apply throttling / validation| C[Events Service / Ingestion Pipeline] C --> D[(Event Store / Queue)] B -->|If authenticated| E[Auth Context] E -->|Attach userId| C
Usage
ts// Browser-side: send an event from docs viewer / chat widget.
// This endpoint is designed to be callable without auth (anonymous),
// but if you include credentials (cookie/JWT), the backend may attach userId.
type PublicEventPayload = {
name: string; // e.g. "docs_feedback_submitted"
source: "docs" | "chat";
timestamp?: string; // ISO string; optional if server sets
metadata?: Record<string, any>;
};
async function ingestEvent(payload: PublicEventPayload) {
const res = await fetch("https://api.example.com/events", {
method: "POST",
headers: { "content-type": "application/json" },
// If your app uses cookies for auth, keep credentials enabled.
credentials: "include",
body: JSON.stringify(payload),
});
if (!res.ok) {
// Because this endpoint is throttled, 429 is a common response during bursts.
// Your UI should treat failures as non-blocking.
const text = await res.text().catch(() => "");
throw new Error(`Event ingest failed (${res.status}): ${text}`);
}
// Often returns a simple ack; treat as fire-and-forget.
return res.json().catch(() => ({}));
}
// Example calls
ingestEvent({
name: "docs_page_viewed",
source: "docs",
metadata: { path: "/getting-started", referrer: document.referrer },
});
ingestEvent({
name: "chat_widget_opened",
source: "chat",
metadata: { location: window.location.href },
});
AI Coding Instructions
- Keep this controller write-only / append-only: do not add read/query endpoints here; route analytics reads through a separate, secured API.
- Preserve anonymous access while enriching events opportunistically: attach
userIdonly when an authenticated context is present; never require auth for basic ingest. - Maintain and test throttling behavior (429 responses are expected); client-side usage should be best-effort and non-blocking.
- Validate and sanitize incoming payloads defensively (size limits, allowed fields) to avoid turning the endpoint into a general data dump surface.
- Integrate with the existing ingestion pipeline/service rather than adding persistence logic inside the controller; keep controller thin and side-effect routing centralized.
Relationships
- MODULE_DECLARES →
record - DEPENDS_ON →
EventsService - DEPENDS_ON →
PrismaService
Referenced By
EventsModule(MODULE_DECLARES)
Was this page helpful?