159 lines
4.2 KiB
JavaScript
159 lines
4.2 KiB
JavaScript
import { Hono } from 'hono';
|
|
import { z } from 'zod';
|
|
import { query } from '../../db/pool.js';
|
|
import { requireAuth } from '../../middleware/auth.js';
|
|
import { badRequest, notFound } from '../../utils/httpErrors.js';
|
|
|
|
const router = new Hono();
|
|
|
|
const filterSchema = z.object({
|
|
siteId: z.string().uuid().optional(),
|
|
alertType: z.string().trim().min(1).max(80).optional(),
|
|
from: z.string().datetime({ offset: true }).optional(),
|
|
to: z.string().datetime({ offset: true }).optional(),
|
|
});
|
|
|
|
function serializeAlert(row) {
|
|
return {
|
|
alertId: row.alert_id,
|
|
userId: row.user_id,
|
|
siteId: row.site_id,
|
|
siteAlias: row.site_alias,
|
|
siteUrl: row.site_url,
|
|
alertType: row.alert_type,
|
|
content: row.content,
|
|
occurredAt: row.occurred_at,
|
|
readAt: row.read_at,
|
|
deliveryChannels: row.delivery_channels ?? [],
|
|
deliveryResult: row.delivery_result ?? {},
|
|
};
|
|
}
|
|
|
|
router.use('*', requireAuth);
|
|
|
|
router.get('/', async (c) => {
|
|
const rawFilters = {
|
|
siteId: c.req.query('siteId') || undefined,
|
|
alertType: c.req.query('alertType') || undefined,
|
|
from: c.req.query('from') || undefined,
|
|
to: c.req.query('to') || undefined,
|
|
};
|
|
const filters = filterSchema.safeParse(rawFilters);
|
|
if (!filters.success) {
|
|
throw badRequest('絞り込み条件を確認してください', filters.error.flatten());
|
|
}
|
|
|
|
const conditions = ['a.user_id = $1'];
|
|
const params = [c.get('user').user_id];
|
|
|
|
if (filters.data.siteId) {
|
|
params.push(filters.data.siteId);
|
|
conditions.push(`a.site_id = $${params.length}`);
|
|
}
|
|
|
|
if (filters.data.alertType) {
|
|
params.push(filters.data.alertType);
|
|
conditions.push(`a.alert_type = $${params.length}`);
|
|
}
|
|
|
|
if (filters.data.from) {
|
|
params.push(filters.data.from);
|
|
conditions.push(`a.occurred_at >= $${params.length}`);
|
|
}
|
|
|
|
if (filters.data.to) {
|
|
params.push(filters.data.to);
|
|
conditions.push(`a.occurred_at <= $${params.length}`);
|
|
}
|
|
|
|
const result = await query(
|
|
`SELECT a.alert_id,
|
|
a.user_id,
|
|
a.site_id,
|
|
s.alias AS site_alias,
|
|
s.url AS site_url,
|
|
a.alert_type,
|
|
a.content,
|
|
a.occurred_at,
|
|
a.read_at,
|
|
a.delivery_channels,
|
|
a.delivery_result
|
|
FROM alert_history a
|
|
LEFT JOIN sites s ON s.site_id = a.site_id
|
|
WHERE ${conditions.join(' AND ')}
|
|
ORDER BY a.occurred_at DESC, a.created_at DESC
|
|
LIMIT 200`,
|
|
params,
|
|
);
|
|
|
|
return c.json({ alerts: result.rows.map(serializeAlert) });
|
|
});
|
|
|
|
router.patch('/:alertId/read', async (c) => {
|
|
const alertId = z.string().uuid().safeParse(c.req.param('alertId'));
|
|
if (!alertId.success) {
|
|
throw badRequest('アラート ID が不正です');
|
|
}
|
|
|
|
const result = await query(
|
|
`WITH updated AS (
|
|
UPDATE alert_history
|
|
SET read_at = COALESCE(read_at, now())
|
|
WHERE user_id = $1
|
|
AND alert_id = $2
|
|
RETURNING alert_id,
|
|
user_id,
|
|
site_id,
|
|
alert_type,
|
|
content,
|
|
occurred_at,
|
|
read_at,
|
|
delivery_channels,
|
|
delivery_result
|
|
)
|
|
SELECT u.alert_id,
|
|
u.user_id,
|
|
u.site_id,
|
|
s.alias AS site_alias,
|
|
s.url AS site_url,
|
|
u.alert_type,
|
|
u.content,
|
|
u.occurred_at,
|
|
u.read_at,
|
|
u.delivery_channels,
|
|
u.delivery_result
|
|
FROM updated u
|
|
LEFT JOIN sites s ON s.site_id = u.site_id`,
|
|
[c.get('user').user_id, alertId.data],
|
|
);
|
|
|
|
if (!result.rows[0]) {
|
|
throw notFound('アラートが見つかりません');
|
|
}
|
|
|
|
return c.json({ alert: serializeAlert(result.rows[0]) });
|
|
});
|
|
|
|
router.delete('/:alertId', async (c) => {
|
|
const alertId = z.string().uuid().safeParse(c.req.param('alertId'));
|
|
if (!alertId.success) {
|
|
throw badRequest('アラート ID が不正です');
|
|
}
|
|
|
|
const result = await query(
|
|
`DELETE FROM alert_history
|
|
WHERE user_id = $1
|
|
AND alert_id = $2
|
|
RETURNING alert_id`,
|
|
[c.get('user').user_id, alertId.data],
|
|
);
|
|
|
|
if (!result.rows[0]) {
|
|
throw notFound('アラートが見つかりません');
|
|
}
|
|
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
export default router;
|