import type { FastifyInstance } from 'fastify'; import { z } from 'zod'; import { listWebPushSubscriptions, getAutomationNotificationPreference, getWebPushConfig, registerAutomationNotificationPreferenceSchema, registerWebPushSubscription, registerWebPushSubscriptionSchema, sendNotifications, sendIosNotificationSchema, setupNotificationTables, upsertAutomationNotificationPreference, unregisterWebPushSubscription, unregisterWebPushSubscriptionSchema, } from '../services/notification-service.js'; import { createNotificationMessage, deleteNotificationMessage, getNotificationMessage, listNotificationMessages, notificationMessageListQuerySchema, notificationMessagePayloadSchema, notificationMessageReadPayloadSchema, updateNotificationMessageReadState, } from '../services/notification-message-service.js'; const automationNotificationPreferenceQuerySchema = z.object({ targetKind: z.enum(['client', 'web-endpoint']).optional(), targetId: z.string().trim().min(1).max(1000).optional(), }); function getClientIdHeader(request: { headers: Record }) { const rawClientId = request.headers['x-client-id']; const clientId = Array.isArray(rawClientId) ? rawClientId[0] : rawClientId; return clientId?.trim() ?? ''; } export async function registerNotificationRoutes(app: FastifyInstance) { app.post('/api/notifications/setup', async () => setupNotificationTables()); app.get('/api/notifications/subscriptions/web', async () => ({ items: await listWebPushSubscriptions(), })); app.get('/api/notifications/webpush/config', async () => getWebPushConfig()); app.get('/api/notifications/messages', async (request) => { const query = notificationMessageListQuerySchema.parse(request.query ?? {}); const clientId = getClientIdHeader(request); return { ok: true, ...(await listNotificationMessages(query, clientId)), }; }); app.get('/api/notifications/messages/:id', async (request, reply) => { const id = z.coerce.number().int().positive().parse((request.params as { id: string }).id); const item = await getNotificationMessage(id); if (!item) { return reply.code(404).send({ message: '알림 메시지를 찾을 수 없습니다.', }); } return { ok: true, item, }; }); app.post('/api/notifications/messages', async (request) => { const item = await createNotificationMessage(notificationMessagePayloadSchema.parse(request.body ?? {})); return { ok: true, item, }; }); app.patch('/api/notifications/messages/:id', async (request, reply) => { const id = z.coerce.number().int().positive().parse((request.params as { id: string }).id); const item = await updateNotificationMessageReadState(id, notificationMessageReadPayloadSchema.parse(request.body ?? {})); if (!item) { return reply.code(404).send({ message: '상태를 변경할 알림 메시지를 찾을 수 없습니다.', }); } return { ok: true, item, }; }); app.delete('/api/notifications/messages/:id', async (request, reply) => { const id = z.coerce.number().int().positive().parse((request.params as { id: string }).id); const deleted = await deleteNotificationMessage(id); if (!deleted) { return reply.code(404).send({ message: '삭제할 알림 메시지를 찾을 수 없습니다.', }); } return { ok: true, deleted: true, }; }); app.get('/api/notifications/preferences/automation', async (request) => { const query = automationNotificationPreferenceQuerySchema.parse(request.query ?? {}); const targetId = query.targetId || getClientIdHeader(request); const targetKind = query.targetId ? query.targetKind ?? 'client' : 'client'; return { ok: true, automation: await getAutomationNotificationPreference(targetId, targetKind), }; }); app.put('/api/notifications/preferences/automation', async (request, reply) => { try { const payload = registerAutomationNotificationPreferenceSchema.parse(request.body ?? {}); const targetId = payload.targetId || getClientIdHeader(request); if (!targetId) { throw new Error('알림 설정을 저장할 클라이언트 ID가 없습니다.'); } return upsertAutomationNotificationPreference({ ...payload, targetId, }); } catch (error) { return reply.code(409).send({ message: error instanceof Error ? error.message : '알림 설정 저장에 실패했습니다.', }); } }); app.put('/api/notifications/subscriptions/web', async (request) => { const payload = registerWebPushSubscriptionSchema.parse(request.body ?? {}); return registerWebPushSubscription(payload); }); app.delete('/api/notifications/subscriptions/web', async (request) => { const payload = unregisterWebPushSubscriptionSchema.parse(request.body ?? {}); return unregisterWebPushSubscription(payload.endpoint); }); app.post('/api/notifications/send', async (request) => { const payload = sendIosNotificationSchema.parse(request.body ?? {}); return sendNotifications(payload); }); app.post('/api/notifications/send-test', async (request) => { const payload = sendIosNotificationSchema.parse(request.body ?? {}); return sendNotifications(payload); }); }