/** * Notifications Routes – List, create, mark read, delete notifications */ import type { FastifyInstance } from 'fastify'; import type { DatabaseInterface } from '../database/DatabaseInterface.js'; import type { AuthService } from '../auth/AuthService.js'; import { validateIdParam } from '../utils/validation.js'; function extractToken(request: any): string | null { const auth = request.headers?.authorization; if (auth && auth.startsWith('Bearer ')) return auth.slice(7); return null; } export function registerNotificationRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) { // List notifications for current user fastify.get('/api/notifications', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); return db.listNotifications(user.id); }); // Create notification (only for self) fastify.post('/api/notifications', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const body = request.body as { type?: string; title?: string; message?: string }; if (!body.title || !body.message) return reply.code(400).send({ error: 'title and message are required' }); const validTypes = ['info', 'share', 'warning', 'error']; const type = validTypes.includes(body.type ?? '') ? body.type! : 'info'; return reply.code(201).send(db.createNotification({ user_id: user.id, type, title: body.title, message: body.message, })); }); // Mark notification as read (ownership check) fastify.patch('/api/notifications/:id/read', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const { id } = request.params as { id: string }; const idErr = validateIdParam(id, 'id'); if (idErr) return reply.code(400).send({ error: idErr }); const notif = db.getNotification(id); if (!notif) return reply.code(404).send({ error: 'Notification not found' }); if (notif.user_id !== user.id) return reply.code(403).send({ error: 'Forbidden' }); const ok = db.markNotificationRead(id); if (!ok) return reply.code(404).send({ error: 'Notification not found' }); return reply.code(204).send(); }); // Delete notification (ownership check) fastify.delete('/api/notifications/:id', async (request, reply) => { const token = extractToken(request); if (!token) return reply.code(401).send({ error: 'Authentication required' }); const user = authService.getUserFromSession(token); if (!user) return reply.code(401).send({ error: 'Invalid or expired session' }); const { id } = request.params as { id: string }; const idErr = validateIdParam(id, 'id'); if (idErr) return reply.code(400).send({ error: idErr }); const notif = db.getNotification(id); if (!notif) return reply.code(404).send({ error: 'Notification not found' }); if (notif.user_id !== user.id) return reply.code(403).send({ error: 'Forbidden' }); const ok = db.deleteNotification(id); if (!ok) return reply.code(404).send({ error: 'Notification not found' }); return reply.code(204).send(); }); }