Skip to main content
Version: 0.14.0 (Latest)

🛣️ Notification routes

Notification routes are SDK composers, not Express middleware. Every route authenticates first; ownership or self validation runs second.

Inventory

RouteMethod / protocolPathSchemaValidatorsSuccess status
updateNotificationToReadRoutePOST / HTTP/notifications/:notificationId/readupdateNotificationToReadSchemaisAuthenticated(), ownsNotification204
findNotificationsRouteGET / HTTP/notifications/identities/:identityIdfindNotificationsSchemaisAuthenticated(), isSelf(['params', 'requestParams', 'identityId'])200
updateNotificationToReadBatchRoutePOST / HTTP/notifications/identities/:identityId/readupdateNotificationToReadBatchSchemaisAuthenticated(), isSelf(['params', 'requestParams', 'identityId'])204

Details

updateNotificationToReadRoute

Implementation

Endpoint: POST /notifications/:notificationId/read

POST /notifications/:notificationId/read marks an owned notification read and returns 204; an already-read document also succeeds without an update.

Access: isAuthenticated(), then ownsNotification with the path ID.

Request: updateNotificationToReadSchema requires path notificationId; no body or query. Send Bearer authentication by default or the accessToken cookie in cookie mode.

Pipeline: buildNotificationReadbuildGetNotificationFiltergetNotification → conditional updateNotificationorThrow.

Success: Explicit 204 descriptor with undefined data.

Failure: The route maps NotificationNotFoundBlockError to 404 and database errors to 500. In the mounted route, however, ownsNotification runs first: a nonexistent notification normally has no receiverId and therefore fails ownership with 403 before the route pipeline. Other validator failures also occur first.

View complete source
export const updateNotificationToReadRoute = withRoute({
handler: compose(
applyPayloadArgs(buildNotificationRead, [], 'notification'),
flatMapAsync(
applyPayloadArgs(
buildGetNotificationFilter,
[['params', 'requestParams', 'notificationId']],
'getNotificationFilter',
),
),
flatMapAsync(
withLogging(
applyPayloadArgs(
getNotification,
[
['context', 'db', 'notifications'],
['context', 'data', 'getNotificationFilter'],
],
'existingNotification',
),
),
),
flatMapAsync(
ifElse(
match(Boolean, ['context', 'data', 'existingNotification', 'isRead']),
applyPayloadArgs(noop, [[]]),
compose(
withLogging(
applyPayloadArgs(
updateNotification,
[
['context', 'db', 'notifications'],
['params', 'requestParams', 'notificationId'],
['context', 'data', 'notification'],
],
'isUpdated',
),
),
),
),
),
lift(
orThrow(
[
[NotificationNotFoundBlockError, 404],
[NotificationDbError, 500],
],
[[''], 204],
),
),
),
method: 'POST',
path: '/notifications/:notificationId/read',
validators: [isAuthenticated(), ownsNotification(['params', 'requestParams', 'notificationId'])],
});

findNotificationsRoute

Implementation

Endpoint: GET /notifications/identities/:identityId

GET /notifications/identities/:identityId returns that authenticated identity's normalized paginated notifications with 200.

Access: isAuthenticated(), then isSelf(['params', 'requestParams', 'identityId']).

Request: findNotificationsSchema requires path identityId and accepts integer query page (11000) and limit (150); no body. Send Bearer authentication by default or the accessToken cookie in cookie mode. withPagination defaults omitted values to page 1 and limit 10, removes those keys before the downstream query, and applies the resulting skip/limit.

Pipeline: buildFindNotificationsFilterwithPagination around findNotificationsnormalizeDocumentsapplySpecorThrow.

Success: Explicit 200 { data, metadata: { pagination } }; normalizeDocuments removes Mongo _id.

Failure: Notification database errors map to 500; validator failures occur first. An empty result is 200.

View complete source
export const findNotificationsRoute = withRoute({
handler: compose(
applyPayloadArgs(buildFindNotificationsFilter, [['params', 'requestParams', 'identityId']], 'filter'),
flatMapAsync(
withPagination(
withLogging(
applyPayloadArgs(
findNotifications,
[
['context', 'db', 'notifications'],
['context', 'data', 'filter'],
['context', 'data', 'options'],
],
'rawNotifications',
),
),
),
),
flatMapAsync(
applyPayloadArgs(
normalizeDocuments,
[['context', 'data', 'rawNotifications', 'data']],
'normalizedNotifications',
),
),
flatMapAsync(
applyPayloadArgs(
applySpec({data: nthArg(0), metadata: {pagination: nthArg(1)}}),
[
['context', 'data', 'normalizedNotifications'],
['context', 'data', 'rawNotifications', 'metadata'],
],
'normalizedBody',
),
),
lift(orThrow([[NotificationDbError, 500]], [['context', 'data', 'normalizedBody'], 200])),
),
method: 'GET',
path: '/notifications/identities/:identityId',
validators: [isAuthenticated(), isSelf(['params', 'requestParams', 'identityId'])],
});

updateNotificationToReadBatchRoute

Implementation

Endpoint: POST /notifications/identities/:identityId/read

POST /notifications/identities/:identityId/read marks unread notifications through a receiver-owned anchor and returns 204; no unread matches after the anchor is a successful no-op.

Access: isAuthenticated(), then isSelf(['params', 'requestParams', 'identityId']).

Request: updateNotificationToReadBatchSchema requires path identityId and strict application/json { lastReadNotificationId } body. Send Bearer authentication by default or the accessToken cookie in cookie mode.

Pipeline: filter anchor → getNotificationassertHasCreatedAtbuildNotificationsReadFilterbuildNotificationRead → conditionalized updateNotificationBatchorThrow.

Success: Explicit 204 descriptor with undefined data; a later NotificationNotFoundBlockError from batch update becomes ok(false).

Failure: Missing anchor is 404; missing createdAt or database errors are 500; validator failures occur first.

View complete source
export const updateNotificationToReadBatchRoute = withRoute({
handler: compose(
applyPayloadArgs(
buildGetNotificationFilter,
[
['params', 'requestBody', 'lastReadNotificationId'],
['params', 'requestParams', 'identityId'],
],
'filterForLatestReadNotification',
),
flatMapAsync(
withLogging(
applyPayloadArgs(
getNotification,
[
['context', 'db', 'notifications'],
['context', 'data', 'filterForLatestReadNotification'],
],
'latestReadNotification',
),
),
),
flatMapAsync(
withLogging(
applyPayloadArgs(assertHasCreatedAt, [['context', 'data', 'latestReadNotification']], 'hasCreatedAtTimestamp'),
),
),
flatMapAsync(
withLogging(
applyPayloadArgs(
buildNotificationsReadFilter,
[
['params', 'requestParams', 'identityId'],
['context', 'data', 'latestReadNotification', 'createdAt'],
],
'filterForNotificationsToMarkRead',
),
),
),
flatMapAsync(withLogging(applyPayloadArgs(buildNotificationRead, [], 'notification'))),
flatMapAsync(
withLogging(
applyPayloadArgs(
compose(
updateNotificationBatch,
lift(
ifElse(
result => result.isErr() && result.error instanceof NotificationNotFoundBlockError,
() => ok(false),
noop,
),
),
),
[
['context', 'db', 'notifications'],
['context', 'data', 'notification'],
['context', 'data', 'filterForNotificationsToMarkRead'],
],
'hasUpdated',
),
),
),
lift(
orThrow(
[
[CommonUnexpectedBlockError, 500],
[NotificationNotFoundBlockError, 404],
[NotificationDbError, 500],
],
[[''], 204],
),
),
),
method: 'POST',
path: '/notifications/identities/:identityId/read',
validators: [isAuthenticated(), isSelf(['params', 'requestParams', 'identityId'])],
});