import type { Hono } from "hono"; import { html } from "../pages/html.ts"; import { relativeTime } from "../pages/layout.ts"; import { NOTIFICATION_KINDS, type NotificationKind, setting } from "../server/db.ts"; import { type AdminContext, adminPage, formField, textFields } from "./console.ts"; import { saveSettings } from "./store.ts"; type Notification = { id: number; text: string; href: string; created_at: number; read: number }; export const LABELS: Record = { issue_filed: "an issue is filed", issue_followup: "someone replies to an issue", sync_failed: "a repo's sync starts failing", backup_failed: "a backup starts failing", }; /** §24's Notifications section: the list, opening one, mark all read, and which kinds to raise. */ export function registerNotifications(app: Hono, ctx: AdminContext): void { app.get("/admin/notifications", (c) => { if (c.req.query("format") === "json") { const unread = ctx.kepr .query<{ n: number }, []>("SELECT COUNT(*) AS n FROM notifications WHERE read = 0") .get()?.n ?? 0; const recent = ctx.kepr .query( "SELECT id, text, href, created_at, read FROM notifications ORDER BY id DESC LIMIT 5", ) .all(); return c.json({ unread, notifications: recent.map(({ id, text, created_at, read }) => ({ id, text, read, when: relativeTime(created_at), })), }); } const rows = ctx.kepr .query( "SELECT id, text, href, created_at, read FROM notifications ORDER BY id DESC", ) .all(); const row = (n: Notification) => html` ${n.text} ${n.read ? "" : "unread · "}${relativeTime(n.created_at)}`; const kind = (k: NotificationKind) => html``; const body = html`
${formField(c)}
${rows.length > 0 ? rows.map(row) : html`

nothing yet

`}
${formField(c)} ${NOTIFICATION_KINDS.map(kind)}
`; return c.html(adminPage(c, ctx, "notifications", body)); }); app.get("/admin/notifications/:id{[0-9]{1,9}}", (c) => { const found = ctx.kepr .query<{ href: string }, [number]>( "UPDATE notifications SET read = 1 WHERE id = ? RETURNING href", ) .get(Number(c.req.param("id"))); return found ? c.redirect(found.href, 303) : c.notFound(); }); app.post("/admin/notifications", async (c) => { const fields = await textFields(c); if (fields.action === "read_all") { ctx.kepr.run("UPDATE notifications SET read = 1 WHERE read = 0"); } else if (fields.action === "preferences") { // A box left unticked is a kind switched off; the setting records only that. saveSettings( ctx.kepr, Object.fromEntries( NOTIFICATION_KINDS.map((k) => [`notify_off_${k}`, fields[k] === "1" ? "" : "1"]), ), ); } else return c.text("Unknown action.", 400); return c.redirect("/admin/notifications?saved=1", 303); }); }