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