・プッシュ通知の修正

・メニューをスマホに最適化
・アラート送信済みの条件が再度発動しないように修正
This commit is contained in:
CyberRex
2026-05-25 16:29:38 +09:00
parent 50b872b439
commit a0356e630e
10 changed files with 844 additions and 88 deletions

View File

@@ -21,6 +21,10 @@ const pushSubscriptionSchema = z.object({
}),
});
const pushSubscriptionStatusSchema = z.object({
endpoint: z.string().trim().url().max(2048),
});
function serializeWebhook(row) {
return {
notificationMethodId: row.notification_method_id,
@@ -31,40 +35,25 @@ function serializeWebhook(row) {
};
}
function serializePushSubscription(row) {
return {
notificationMethodId: row.notification_method_id,
endpoint: row.push_endpoint,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
router.use('*', requireAuth);
router.get('/', async (c) => {
const user = c.get('user');
const result = await query(
`SELECT notification_method_id,
notification_type,
alias,
url,
push_endpoint,
created_at,
updated_at
FROM notification_methods
WHERE user_id = $1
AND notification_type = 'webhook'
ORDER BY created_at DESC`,
[user.user_id],
);
return c.json({
webhooks: result.rows
.filter((row) => row.notification_type === 'webhook')
.map(serializeWebhook),
pushSubscriptions: result.rows
.filter((row) => row.notification_type === 'push')
.map(serializePushSubscription),
webhooks: result.rows.map(serializeWebhook),
vapidPublicKey: env.vapidPublicKey,
});
});
@@ -150,6 +139,30 @@ router.delete('/webhooks/:methodId', async (c) => {
return c.json({ ok: true });
});
router.post('/push-subscription-status', async (c) => {
const body = pushSubscriptionStatusSchema.safeParse(await c.req.json().catch(() => null));
if (!body.success) {
throw badRequest('購読状態を確認できません', body.error.flatten());
}
const endpoint = new URL(body.data.endpoint);
if (endpoint.protocol !== 'https:') {
throw badRequest('Push endpoint は HTTPS である必要があります');
}
const result = await query(
`SELECT notification_method_id
FROM notification_methods
WHERE user_id = $1
AND notification_type = 'push'
AND push_endpoint = $2
LIMIT 1`,
[c.get('user').user_id, body.data.endpoint],
);
return c.json({ registered: Boolean(result.rows[0]) });
});
router.post('/push-subscriptions', async (c) => {
const body = pushSubscriptionSchema.safeParse(await c.req.json().catch(() => null));
if (!body.success) {
@@ -178,7 +191,43 @@ router.post('/push-subscriptions', async (c) => {
[user.user_id, body.data.endpoint, body.data.keys.p256dh, body.data.keys.auth],
);
return c.json({ pushSubscription: serializePushSubscription(result.rows[0]) }, 201);
return c.json(
{
pushSubscription: {
notificationMethodId: result.rows[0].notification_method_id,
createdAt: result.rows[0].created_at,
updatedAt: result.rows[0].updated_at,
},
},
201,
);
});
router.delete('/push-subscriptions', async (c) => {
const body = pushSubscriptionStatusSchema.safeParse(await c.req.json().catch(() => null));
if (!body.success) {
throw badRequest('購読情報を確認してください', body.error.flatten());
}
const endpoint = new URL(body.data.endpoint);
if (endpoint.protocol !== 'https:') {
throw badRequest('Push endpoint は HTTPS である必要があります');
}
const result = await query(
`DELETE FROM notification_methods
WHERE user_id = $1
AND notification_type = 'push'
AND push_endpoint = $2
RETURNING notification_method_id`,
[c.get('user').user_id, body.data.endpoint],
);
if (!result.rows[0]) {
throw notFound('Push 購読情報が見つかりません');
}
return c.json({ ok: true });
});
export default router;