Repository Public
1/**2 * In-app notifications ("something needs you"). Bodies are generic by default and never contain3 * model or tool output. Rows are created from the kernel event feed (`events-hub.ts`); the4 * browser reads them over REST.5 */6import type { Notification } from '../../dto-auth.js';7import type { Db } from '../../db.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(private readonly db: Db) {}3233 /** `subject` (e.g. a mission title) is included only when the user chose detail = 'titles'. */34 async notify(userId: string, kind: Notification['kind'], generic: string, subject: string | null, link: string | null): Promise<void> {35 const settings = await getSettings(this.db, userId);36 if (!settings.notifications.inApp) return;37 const body = settings.notifications.detail === 'titles' && subject ? subject.slice(0, 200) : '';38 await this.db.q('INSERT INTO notifications (user_id, kind, title, body, link) VALUES ($1, $2, $3, $4, $5)', [userId, kind, generic, body, link]);39 }40}41