edtech / apps /api /src /routes /notifications.ts
CognxSafeTrack
fix(audit): resolve remaining minor technical debt issues
d74c1b3
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<string, unknown>);
} 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 };
});
}