Repository Public
1import type { FastifyInstance } from 'fastify';2import { z } from 'zod';3import { Id, Notification } from '@cr/shared';4import { authOf, parse, type Ctx } from '../../http.js';5import { NOTIF_COLS, notificationDto, type NotificationRow } from './service.js';67const ListQuery = z.object({ limit: z.coerce.number().int().min(1).max(200).default(50), unread: z.enum(['0', '1']).optional() });8const ReadBody = z.object({ ids: z.array(Id).max(500).optional(), all: z.boolean().optional() });910export default async function notificationsPlugin(app: FastifyInstance, ctx: Ctx): Promise<void> {11 app.get('/notifications', { config: { doc: { summary: 'Recent notifications', query: ListQuery, response: z.array(Notification), responseName: 'NotificationList' } } }, async (req) => {12 const a = authOf(req);13 const q = parse(ListQuery, req.query);14 const rows = await ctx.db.q<NotificationRow>(15 `SELECT ${NOTIF_COLS} FROM notifications WHERE user_id = $1 AND ($2::boolean IS NOT TRUE OR read_at IS NULL)16 ORDER BY created_at DESC LIMIT $3`,17 [a.userId, q.unread === '1', q.limit],18 );19 return rows.map(notificationDto);20 });21 app.post('/notifications/read', { config: { doc: { summary: 'Mark notifications read ({ids} or {all:true})', body: ReadBody, bodyName: 'NotificationsRead' } } }, async (req) => {22 const a = authOf(req);23 const body = parse(ReadBody, req.body);24 const rows = await ctx.db.q(25 `UPDATE notifications SET read_at = now()26 WHERE user_id = $1 AND read_at IS NULL AND ($2::boolean OR id = ANY($3::uuid[])) RETURNING id`,27 [a.userId, body.all === true, body.ids ?? []],28 );29 return { ok: true, updated: rows.length };30 });31}32