2026-07-04 16:08:51 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* 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';
|
2026-07-04 16:43:55 +02:00
|
|
|
|
import { validateIdParam } from '../utils/validation.js';
|
2026-07-04 16:08:51 +02:00
|
|
|
|
|
|
|
|
|
|
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 };
|
2026-07-04 16:43:55 +02:00
|
|
|
|
const idErr = validateIdParam(id, 'id');
|
|
|
|
|
|
if (idErr) return reply.code(400).send({ error: idErr });
|
2026-07-04 16:08:51 +02:00
|
|
|
|
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 };
|
2026-07-04 16:43:55 +02:00
|
|
|
|
const idErr = validateIdParam(id, 'id');
|
|
|
|
|
|
if (idErr) return reply.code(400).send({ error: idErr });
|
2026-07-04 16:08:51 +02:00
|
|
|
|
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();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|