49 lines
2.5 KiB
Markdown
49 lines
2.5 KiB
Markdown
# notifications — the in-app feed and unread badge
|
|
|
|
> Client seam `client/src/services/notifications/` · `USE_NOTIFICATIONS_MOCK = false` (**real**) · 4 server ops
|
|
> Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29).
|
|
|
|
Server-raised, user-scoped notifications. In-app only — there is no push channel and no email channel.
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Verdict |
|
|
| --- | --- | --- |
|
|
| GET | `/api/v1/notifications/get_notifications` | wired · paginated |
|
|
| GET | `/api/v1/notifications/get_unread_count` | wired — polled for the bell badge |
|
|
| POST | `/api/v1/notifications/mark_notification_read` | wired |
|
|
| POST | `/api/v1/notifications/mark_all_read` | wired |
|
|
|
|
All `[Authorize]`. No phantoms. The domain maps 1:1.
|
|
|
|
## Shape rules the JSON does not express
|
|
|
|
- **`dataJson` is a string containing JSON**, not an object. It is the deep-link payload: the client parses
|
|
it (`parse.ts`) and resolves a route from it (`deepLink.ts`), both with unit tests, and **falls back to
|
|
an inert notification rather than throwing** on anything unrecognised. A notification whose payload
|
|
cannot be parsed still renders — it just is not tappable.
|
|
- **Unread count is a separate read, not derived from the list.** The badge must be correct without
|
|
fetching a page, so `get_unread_count` is its own cheap query.
|
|
- **`mark_all_read` is a bulk write**; the client invalidates both the list and the count keys, and does not
|
|
patch items locally.
|
|
- The feed is **day-grouped in the UI** with Shamsi headers — a client-side transform over UTC
|
|
`createdAt`. The server sends no grouping.
|
|
- Notifications are raised through a self-committing facade (`DispatchAsync`) that runs **after** the
|
|
originating handler's `CommitAsync` — so a notification never exists for a transaction that rolled back.
|
|
|
|
## Enums
|
|
|
|
`type` is a **bare string** on the wire and the vocabulary is open-ended by design — new server-side
|
|
notification types must not break an older client. The client models the *payload* as a discriminated
|
|
union (`NotificationData`, keyed on a `kind` inside `dataJson`) and treats an unknown `type` as
|
|
non-actionable rather than an error.
|
|
|
|
Consequence for the server: **adding a notification type is safe; changing an existing type's `dataJson`
|
|
shape is not.** The deep-link parser keys off the payload, not the type string.
|
|
|
|
## Open REQs
|
|
|
|
| REQ | Status | Effect |
|
|
| --- | --- | --- |
|
|
| REQ-054 | deferred, non-blocking | No web push for new booking requests. The nurse dashboard's 15 s poll remains the only signal. Nothing was built for it |
|