import "server-only"; import { randomUUID } from "node:crypto"; import { db } from "@/lib/server/db/drizzle"; import { notifications } from "@/lib/server/db/schema"; import type { NotificationKind, NotificationRecord } from "@/lib/shared/contracts/notifications"; import { desc, eq } from "drizzle-orm"; import { emitNotification, emitNotificationCleared, emitNotificationReadAll } from "./emitter"; const MAX_NOTIFICATIONS = 50; function toRecord(row: typeof notifications.$inferSelect): NotificationRecord { return { id: row.id, title: row.title, body: row.body, kind: row.kind as NotificationKind, read: row.read, createdAt: row.createdAt.toISOString(), }; } export async function listNotifications(): Promise { const rows = await db .select() .from(notifications) .orderBy(desc(notifications.createdAt)) .limit(MAX_NOTIFICATIONS); return rows.map(toRecord); } export async function createNotification(input: { title: string; body: string; kind?: NotificationKind; }): Promise { const id = randomUUID(); const [row] = await db .insert(notifications) .values({ id, title: input.title, body: input.body, kind: input.kind ?? "info", read: false, }) .returning(); const record = toRecord(row); emitNotification(record); // Prune old rows non-blocking void pruneOldNotifications().catch(() => undefined); return record; } export async function markAllNotificationsRead(): Promise { await db.update(notifications).set({ read: true }).where(eq(notifications.read, false)); emitNotificationReadAll(); } export async function clearAllNotifications(): Promise { await db.delete(notifications); emitNotificationCleared(); } async function pruneOldNotifications() { const rows = await db .select({ id: notifications.id }) .from(notifications) .orderBy(desc(notifications.createdAt)) .offset(MAX_NOTIFICATIONS); if (rows.length === 0) return; for (const row of rows) { await db.delete(notifications).where(eq(notifications.id, row.id)); } }