Initial import
This commit is contained in:
207
etc/servers/work-server/src/routes/notification.ts
Executable file
207
etc/servers/work-server/src/routes/notification.ts
Executable file
@@ -0,0 +1,207 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
listIosNotificationTokens,
|
||||
getAutomationNotificationPreference,
|
||||
getWebPushConfig,
|
||||
registerIosNotificationToken,
|
||||
registerAutomationNotificationPreferenceSchema,
|
||||
registerIosTokenSchema,
|
||||
registerWebPushSubscription,
|
||||
registerWebPushSubscriptionSchema,
|
||||
sendNotifications,
|
||||
sendIosNotificationSchema,
|
||||
setupNotificationTables,
|
||||
upsertAutomationNotificationPreference,
|
||||
unregisterIosNotificationToken,
|
||||
unregisterIosTokenSchema,
|
||||
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', 'ios-token', 'ios-token-client', 'web-endpoint']).optional(),
|
||||
targetId: z.string().trim().min(1).max(1000).optional(),
|
||||
});
|
||||
|
||||
type AutomationNotificationPreferenceTargetKind = NonNullable<
|
||||
z.infer<typeof automationNotificationPreferenceQuerySchema>['targetKind']
|
||||
>;
|
||||
|
||||
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/tokens', async () => ({
|
||||
items: await listIosNotificationTokens(),
|
||||
}));
|
||||
|
||||
app.get('/api/notifications/webpush/config', async () => getWebPushConfig());
|
||||
|
||||
app.get('/api/notifications/messages', async (request) => {
|
||||
const query = notificationMessageListQuerySchema.parse(request.query ?? {});
|
||||
return {
|
||||
ok: true,
|
||||
...(await listNotificationMessages(query)),
|
||||
};
|
||||
});
|
||||
|
||||
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 getAutomationNotificationPreferenceWithFallback(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/tokens/ios', async (request) => {
|
||||
const payload = registerIosTokenSchema.parse(request.body ?? {});
|
||||
return registerIosNotificationToken(payload);
|
||||
});
|
||||
|
||||
app.delete('/api/notifications/tokens/ios', async (request) => {
|
||||
const payload = unregisterIosTokenSchema.parse(request.body ?? {});
|
||||
return unregisterIosNotificationToken(payload.token);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
async function getAutomationNotificationPreferenceWithFallback(
|
||||
targetId: string,
|
||||
targetKind: AutomationNotificationPreferenceTargetKind,
|
||||
) {
|
||||
const automation = await getAutomationNotificationPreference(targetId, targetKind);
|
||||
|
||||
if (automation || targetKind !== 'ios-token-client') {
|
||||
return automation;
|
||||
}
|
||||
|
||||
const [token, clientId] = targetId.split('::client::');
|
||||
|
||||
if (token?.trim()) {
|
||||
const tokenAutomation = await getAutomationNotificationPreference(token.trim(), 'ios-token');
|
||||
|
||||
if (tokenAutomation) {
|
||||
return tokenAutomation;
|
||||
}
|
||||
}
|
||||
|
||||
if (clientId?.trim()) {
|
||||
return getAutomationNotificationPreference(clientId.trim(), 'client');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user