SPB Git

Repository Public

service.ts 1.7 KB 48 lines typescript

1/**2 * In-app notifications. Bodies are generic by default and never contain terminal content.3 * Web Push is NOT implemented in this milestone (the `webPush` setting is only stored).4 */5import type { Notification } from '@cr/shared';6import type { Db } from '../../db.js';7import type { EventHub } from '../../events-hub.js';8import { getSettings } from '../settings/store.js';910export interface NotificationRow {11  id: string;12  kind: string;13  title: string;14  body: string;15  link: string | null;16  read_at: Date | null;17  created_at: Date;18}19export const NOTIF_COLS = 'id, kind, title, body, link, read_at, created_at';20export const notificationDto = (r: NotificationRow): Notification => ({21  id: r.id,22  kind: r.kind as Notification['kind'],23  title: r.title,24  body: r.body,25  link: r.link,26  readAt: r.read_at?.toISOString() ?? null,27  createdAt: r.created_at.toISOString(),28});2930export class Notifier {31  constructor(32    private readonly db: Db,33    private readonly hub: EventHub,34  ) {}3536  /** `subject` (e.g. a session/task title) is included only when the user chose detail = 'titles'. */37  async notify(userId: string, kind: Notification['kind'], generic: string, subject: string | null, link: string | null): Promise<void> {38    const settings = await getSettings(this.db, userId);39    if (!settings.notifications.inApp) return;40    const body = settings.notifications.detail === 'titles' && subject ? subject.slice(0, 200) : '';41    const row = await this.db.one<NotificationRow>(42      `INSERT INTO notifications (user_id, kind, title, body, link) VALUES ($1, $2, $3, $4, $5) RETURNING ${NOTIF_COLS}`,43      [userId, kind, generic, body, link],44    );45    if (row) this.hub.publish(userId, { type: 'notification', notification: notificationDto(row) });46  }47}48
Files main
Menu

Theme