/** * Alert Endpoints (Story 12.1) * * POST /api/alerts/rules — create alert rule * GET /api/alerts/rules — list alert rules * GET /api/alerts/rules/:id — get single alert rule * PUT /api/alerts/rules/:id — update alert rule * DELETE /api/alerts/rules/:id — delete alert rule * GET /api/alerts/history — list alert history */ import { Hono } from 'hono'; import { getTenantId } from './tenant-helper.js'; import { ulid } from 'ulid'; import { createAlertRuleSchema, updateAlertRuleSchema } from '@agentkitai/agentlens-core'; import type { AlertRule } from '@agentkitai/agentlens-core'; import type { IEventStore } from '@agentkitai/agentlens-core'; import type { AuthVariables } from '../middleware/auth.js'; import { NotFoundError } from '../db/errors.js'; import { getTenantStore } from './tenant-helper.js'; import { parseBody, notFound, created } from './helpers.js'; export function alertsRoutes(store: IEventStore) { const app = new Hono<{ Variables: AuthVariables }>(); // POST /api/alerts/rules — create alert rule app.post('/rules', async (c) => { const tenantStore = getTenantStore(store, c); const parsed = await parseBody(c, createAlertRuleSchema); if (!parsed.success) return parsed.response; const input = parsed.data; const now = new Date().toISOString(); const rule: AlertRule = { id: ulid(), name: input.name, enabled: input.enabled, condition: input.condition, threshold: input.threshold, windowMinutes: input.windowMinutes, scope: input.scope, notifyChannels: input.notifyChannels, createdAt: now, updatedAt: now, tenantId: getTenantId(c), }; await tenantStore.createAlertRule(rule); return created(c, rule); }); // GET /api/alerts/rules — list all alert rules app.get('/rules', async (c) => { const tenantStore = getTenantStore(store, c); const rules = await tenantStore.listAlertRules(); return c.json({ rules }); }); // GET /api/alerts/rules/:id — get single alert rule app.get('/rules/:id', async (c) => { const tenantStore = getTenantStore(store, c); const id = c.req.param('id'); const rule = await tenantStore.getAlertRule(id); if (!rule) { return notFound(c, 'Alert rule'); } return c.json(rule); }); // PUT /api/alerts/rules/:id — update alert rule app.put('/rules/:id', async (c) => { const tenantStore = getTenantStore(store, c); const id = c.req.param('id'); const parsed = await parseBody(c, updateAlertRuleSchema); if (!parsed.success) return parsed.response; const updates = parsed.data; try { await tenantStore.updateAlertRule(id, { ...updates, updatedAt: new Date().toISOString(), }); } catch (err) { if (err instanceof NotFoundError) { return notFound(c, 'Alert rule'); } throw err; } const updated = await tenantStore.getAlertRule(id); return c.json(updated); }); // DELETE /api/alerts/rules/:id — delete alert rule app.delete('/rules/:id', async (c) => { const tenantStore = getTenantStore(store, c); const id = c.req.param('id'); try { await tenantStore.deleteAlertRule(id); } catch (err) { if (err instanceof NotFoundError) { return notFound(c, 'Alert rule'); } throw err; } return c.json({ id, deleted: true }); }); // GET /api/alerts/history — list alert history app.get('/history', async (c) => { const tenantStore = getTenantStore(store, c); const ruleId = c.req.query('ruleId'); const limitStr = c.req.query('limit'); const offsetStr = c.req.query('offset'); const limit = limitStr ? Math.max(1, Math.min(parseInt(limitStr, 10) || 50, 500)) : 50; const offset = offsetStr ? Math.max(0, parseInt(offsetStr, 10) || 0) : 0; const result = await tenantStore.listAlertHistory({ ruleId: ruleId ?? undefined, limit, offset, }); return c.json({ entries: result.entries, total: result.total, hasMore: offset + result.entries.length < result.total, }); }); return app; }