Context
The notifications subsystem exists but nothing ever creates a notification. NotificationsService.create() (api/src/notifications/notifications.service.ts) persists a row, pushes it over the /streams namespace (gateway.emitNotification), and the user-facing surface — GET /notifications, PATCH /notifications/read-all, PATCH /notifications/:id/read, DELETE /notifications/:id (api/src/notifications/notifications.controller.ts) — is fully built and tested. A repo-wide search shows NotificationsService.create has no callers outside its own spec. The notifications table stays empty, the unread badge can never light up, and the entire feature (issue #398's delivery mechanism) is dead weight.
The natural trigger already exists: StreamsService.update() fires dispatchStatusWebhook on every valid status transition and StreamsService is where the event payloads (startedAt/stoppedAt/occurredAt) are built. A naive implementation would scatter notifications.create() calls through services; the maintainable shape is a single hook site that turns the same lifecycle events into notifications.
Goal
Wire notifications to real events so the notification store, WebSocket push, and unread endpoints become live: notify the stream owner on stream status transitions (and optionally notify on webhook delivery failure, reusing WebhooksService's failure path), driven from one integration point.
Scope
1. Stream lifecycle notifications
- On a valid
PATCH /streams/:id status transition (the same transitions dispatchStatusWebhook handles), create a notification for the stream owner with a typed payload ({ streamId, status, occurredAt }). Reuse the payload shapes in api/src/gateways/stream-events.ts (StreamStartedPayload etc.) so notification payloads and socket payloads cannot drift. Decide whether error transitions get a distinct notification type.
2. Webhook delivery failure notifications (optional but recommended)
- When a delivery becomes terminally failed (
attemptCount exhausts MAX_RETRIES in api/src/webhooks/webhooks.service.ts), create a notification for the subscription owner. This requires WebhooksService to know the owner (the subscription row already carries userId) and to depend on NotificationsService — mirror the @Optional() gateway injection pattern in NotificationsService to keep the webhook path testable.
3. App-side surface
- The app already has
app/src/components/NotificationsDropdown/NotificationsDropdown.tsx; wire it to GET /notifications with the auth header story from the dashboard-auth work, and make the unread badge react to the notification:new socket event (NOTIFICATION_EVENTS.NEW in api/src/gateways/stream-events.ts). No new UI components are required beyond what exists.
Downstream impact
api/src/streams/streams.service.ts (notification trigger in update), api/src/webhooks/webhooks.service.ts (failure notifications), api/src/notifications/notifications.service.ts (unchanged API, new callers), api/src/webhooks/webhooks.module.ts / streams.module.ts (module wiring).
app/src/components/NotificationsDropdown/NotificationsDropdown.tsx, app/hooks/useNotifications.ts if created.
- Contracts: extend
tests/contracts with GET /notifications so the API shape is pinned.
- SDK:
Notification type if the SDK gains a notifications surface (optional; keep out of scope unless trivial).
Acceptance criteria
Service
Tests
Documentation
Out of scope
Email/push-channel delivery, notification preferences, and the notification retention sweep (already implemented in NotificationsService.sweepExpired).
Getting started
Real files in scope: api/src/notifications/notifications.service.ts, api/src/streams/streams.service.ts (update, dispatchStatusWebhook), api/src/webhooks/webhooks.service.ts, api/src/gateways/stream-events.ts, app/src/components/NotificationsDropdown/NotificationsDropdown.tsx, tests/contracts/src/ (new contract).
Verify with:
cd api && npm run typecheck && npm test
cd ../app && npm run typecheck && npm test
Good first files to read: api/src/notifications/notifications.service.ts, api/src/streams/streams.service.ts (dispatchStatusWebhook), api/src/gateways/stream-events.ts.
Context
The notifications subsystem exists but nothing ever creates a notification.
NotificationsService.create()(api/src/notifications/notifications.service.ts) persists a row, pushes it over the/streamsnamespace (gateway.emitNotification), and the user-facing surface —GET /notifications,PATCH /notifications/read-all,PATCH /notifications/:id/read,DELETE /notifications/:id(api/src/notifications/notifications.controller.ts) — is fully built and tested. A repo-wide search showsNotificationsService.createhas no callers outside its own spec. Thenotificationstable stays empty, the unread badge can never light up, and the entire feature (issue #398's delivery mechanism) is dead weight.The natural trigger already exists:
StreamsService.update()firesdispatchStatusWebhookon every valid status transition andStreamsServiceis where the event payloads (startedAt/stoppedAt/occurredAt) are built. A naive implementation would scatternotifications.create()calls through services; the maintainable shape is a single hook site that turns the same lifecycle events into notifications.Goal
Wire notifications to real events so the notification store, WebSocket push, and unread endpoints become live: notify the stream owner on stream status transitions (and optionally notify on webhook delivery failure, reusing
WebhooksService's failure path), driven from one integration point.Scope
1. Stream lifecycle notifications
PATCH /streams/:idstatus transition (the same transitionsdispatchStatusWebhookhandles), create a notification for the stream owner with a typed payload ({ streamId, status, occurredAt }). Reuse the payload shapes inapi/src/gateways/stream-events.ts(StreamStartedPayloadetc.) so notification payloads and socket payloads cannot drift. Decide whethererrortransitions get a distinct notification type.2. Webhook delivery failure notifications (optional but recommended)
attemptCountexhaustsMAX_RETRIESinapi/src/webhooks/webhooks.service.ts), create a notification for the subscription owner. This requiresWebhooksServiceto know the owner (the subscription row already carriesuserId) and to depend onNotificationsService— mirror the@Optional()gateway injection pattern inNotificationsServiceto keep the webhook path testable.3. App-side surface
app/src/components/NotificationsDropdown/NotificationsDropdown.tsx; wire it toGET /notificationswith the auth header story from the dashboard-auth work, and make the unread badge react to thenotification:newsocket event (NOTIFICATION_EVENTS.NEWinapi/src/gateways/stream-events.ts). No new UI components are required beyond what exists.Downstream impact
api/src/streams/streams.service.ts(notification trigger inupdate),api/src/webhooks/webhooks.service.ts(failure notifications),api/src/notifications/notifications.service.ts(unchanged API, new callers),api/src/webhooks/webhooks.module.ts/streams.module.ts(module wiring).app/src/components/NotificationsDropdown/NotificationsDropdown.tsx,app/hooks/useNotifications.tsif created.tests/contractswithGET /notificationsso the API shape is pinned.Notificationtype if the SDK gains a notifications surface (optional; keep out of scope unless trivial).Acceptance criteria
Service
read_atnull and a payload containing the stream id, new status, and timestamp; invalid transitions create none.notification:new) to the owner's user room and is returned byGET /notifications.Tests
api/src/streams/streams.service.spec.ts: transitions create notifications with correct payloads; a webhook fan-out failure does not prevent the notification.api/src/webhooks/webhooks.service.spec.ts: exhausted-retry delivery creates a notification; transient failures do not.GET /notificationspasses in provider and consumer suites.notification:new.Documentation
NotificationsServiceJSDoc and Swagger.Out of scope
Email/push-channel delivery, notification preferences, and the notification retention sweep (already implemented in
NotificationsService.sweepExpired).Getting started
Real files in scope:
api/src/notifications/notifications.service.ts,api/src/streams/streams.service.ts(update,dispatchStatusWebhook),api/src/webhooks/webhooks.service.ts,api/src/gateways/stream-events.ts,app/src/components/NotificationsDropdown/NotificationsDropdown.tsx,tests/contracts/src/(new contract).Verify with:
Good first files to read:
api/src/notifications/notifications.service.ts,api/src/streams/streams.service.ts(dispatchStatusWebhook),api/src/gateways/stream-events.ts.