import { FastifyInstance } from 'fastify'; import { pushService } from '../services/push'; import { z } from 'zod'; export async function notificationRoutes(fastify: FastifyInstance) { /** * POST /v1/notifications/subscribe * Subscribe the current user to push notifications */ fastify.post('/subscribe', async (request, reply) => { const bodySchema = z.object({ subscription: z.object({ endpoint: z.string(), keys: z.object({ p256dh: z.string(), auth: z.string() }) }) }); const { subscription } = bodySchema.parse(request.body); const user = request.user; const organizationId = request.organizationId; if (!user || !organizationId) { return reply.code(401).send({ error: 'Unauthorized' }); } await pushService.subscribe(user.id, organizationId, subscription); return { success: true }; }); /** * POST /v1/notifications/push * Send a push notification to a user or organization (internal use, API-key protected) */ fastify.post('/push', async (request, reply) => { const bodySchema = z.object({ userId: z.string().optional(), organizationId: z.string().optional(), title: z.string(), body: z.string(), icon: z.string().optional(), data: z.record(z.unknown()).optional(), }); const parsed = bodySchema.safeParse(request.body); if (!parsed.success) { return reply.code(400).send({ error: 'Invalid payload', details: parsed.error.issues }); } const { userId, organizationId, title, body, icon, data } = parsed.data; if (!userId && !organizationId) { return reply.code(400).send({ error: 'userId or organizationId required' }); } if (userId) { if (!organizationId) { return reply.code(400).send({ error: 'organizationId required when userId is provided' }); } await pushService.sendToUser(userId, organizationId, title, body, icon, data as Record); } else { await pushService.notifyOrganization(organizationId!, title, body, icon); } return { success: true }; }); /** * GET /v1/notifications/vapid-key * Returns the public VAPID key for the frontend */ fastify.get('/vapid-key', async (_req, reply) => { const publicKey = process.env.VAPID_PUBLIC_KEY; if (!publicKey) return reply.code(503).send({ error: 'Push notifications not configured' }); return { publicKey }; }); }