feat(notifications): persistent notification center

- notifications table (goose migration) + sqlc queries; list with unread
  count, mark-one, mark-all endpoints under /notifications.
- Deployment success/failure now writes a notification row for the owner
  (insertUserNotification — best-effort, never breaks the deploy flow).
- Shell bell reads the real persisted feed: unread badge, click-to-read,
  mark-all, 30s polling. Replaces the transient builds+audit derivation.

Verified e2e: migration applies clean, list/read/read-all round-trips.

Authored By: TDvorak <[email protected]>
This commit is contained in:
Tomas Dvorak committed 2026-09-17 17:52:37 +02:00
1 parent 08d92fb10f
commit 07ac54aa05
14 files changed
+685 -45

No files matched your search

+22
View File
@@ -1427,3 +1427,25 @@ export async function listCronExecutions(id: string): Promise<CronExecutionEntit
export async function triggerCronJob(id: string): Promise<void> {
await requestJson(`/cron-jobs/${encodeURIComponent(id)}/trigger`, { method: 'POST' });
}
export type NotificationEntity = components['schemas']['Notification'];
export type NotificationList = {
notifications: NotificationEntity[];
unread: number;
};
export async function listNotifications(limit = 20): Promise<NotificationList> {
const payload = await requestJson<{ notifications?: NotificationEntity[]; unread?: number }>(
`/notifications?limit=${limit}`,
);
return { notifications: payload.notifications ?? [], unread: payload.unread ?? 0 };
}
export async function markNotificationRead(id: string): Promise<void> {
await requestJson(`/notifications/${encodeURIComponent(id)}/read`, { method: 'POST' });
}
export async function markAllNotificationsRead(): Promise<void> {
await requestJson('/notifications/read-all', { method: 'POST' });
}