165 lines
5.3 KiB
TypeScript
165 lines
5.3 KiB
TypeScript
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<string, string | string[] | undefined> }) {
|
|
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);
|
|
});
|
|
}
|