First commit
This commit is contained in:
158
src/server/modules/alerts/routes.js
Normal file
158
src/server/modules/alerts/routes.js
Normal file
@@ -0,0 +1,158 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user