feat(notifications): persistent read cursor + delivery history

notification_cursors table stores one read-cursor per user per tenant;
GET /v1/activity returns unreadCount + readAt, POST /v1/activity/read
persists the cursor. Reminder dispatches now append reminder.sent /
reminder.failed system events so delivery history reaches the feed.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 16:55:21 +02:00
1 parent 76e45d4178
commit 2464aa45fa
14 files changed
+332 -8

No files matched your search

+32 -1
View File
@@ -1431,6 +1431,30 @@ paths:
$ref: "#/components/schemas/ActivityFeedResponse"
"404":
description: Tenant membership not found
/v1/activity/read:
post:
tags: [Activity]
operationId: markActivityRead
security:
- bearerAuth: []
summary: Mark all activity events as read
description: >-
Persists the caller's read cursor — activity events at or before the
returned timestamp count as read for this user on subsequent loads.
responses:
"200":
description: Read cursor persisted
content:
application/json:
schema:
type: object
required: [readAt]
properties:
readAt:
type: string
format: date-time
"404":
description: Tenant membership not found
/v1/metrics/staff:
get:
tags: [Metrics]
@@ -2180,12 +2204,19 @@ components:
format: date-time
ActivityFeedResponse:
type: object
required: [events]
required: [events, unreadCount]
properties:
events:
type: array
items:
$ref: "#/components/schemas/ActivityEvent"
unreadCount:
type: integer
description: Events newer than the caller's persisted read cursor
readAt:
type: string
format: date-time
description: Caller's read cursor; absent until the first mark-read
StaffMetric:
type: object
required: [staffId, displayName, bookings, completed, cancelled, utilizationPercent, revenueCents]