feat(observabilidade): rota /api/sessions com validação Zod
Task 5 do MVP Espelho: endpoint Express com factory createSessionsRouter(db) que expõe GET / (lista filtrável por days/project/tool/skill/q + limit/offset validados via Zod) e GET /:id (meta + eventos via parseSessionFile). Integrado em server.ts com DB aberta a partir de OBSERVABILIDADE_DB ?? DEFAULT_DB_PATH. Validação empírica: total=559 sessões (últimos 7d), detalhe com 37 eventos. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Rota /api/sessions — lista e detalhe de sessões Claude Code.
|
||||
* Validação Zod em query params; detalhe carrega eventos via parseSessionFile.
|
||||
* @author Descomplicar® | Projecto Observabilidade (Espelho)
|
||||
*/
|
||||
import { Router } from 'express'
|
||||
import { z } from 'zod'
|
||||
import type { SessionsDb } from '../services/sessions/db.js'
|
||||
import { parseSessionFile } from '../services/sessions/parser.js'
|
||||
|
||||
const ListQuerySchema = z.object({
|
||||
days: z.coerce.number().int().min(1).max(3650).optional(),
|
||||
project: z.string().min(1).max(200).optional(),
|
||||
tool: z.string().min(1).max(100).optional(),
|
||||
skill: z.string().min(1).max(200).optional(),
|
||||
q: z.string().max(500).optional(),
|
||||
limit: z.coerce.number().int().min(1).max(200).default(50),
|
||||
offset: z.coerce.number().int().min(0).default(0),
|
||||
})
|
||||
|
||||
const IdParamSchema = z.object({ id: z.string().min(1).max(200) })
|
||||
|
||||
export function createSessionsRouter(db: SessionsDb): Router {
|
||||
const router = Router()
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const parsed = ListQuerySchema.safeParse(req.query)
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid query', details: parsed.error.format() })
|
||||
}
|
||||
const filters = parsed.data
|
||||
const items = db.listSessions(filters)
|
||||
const total = db.countSessions(filters)
|
||||
return res.json({ total, items })
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const parsed = IdParamSchema.safeParse(req.params)
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid id' })
|
||||
}
|
||||
const session = db.getSession(parsed.data.id)
|
||||
if (!session) return res.status(404).json({ error: 'Session not found' })
|
||||
|
||||
try {
|
||||
const { events } = await parseSessionFile(session.jsonl_path)
|
||||
return res.json({ meta: session, events })
|
||||
} catch (err) {
|
||||
return res.status(500).json({ error: 'Failed to parse session', message: (err as Error).message })
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
Reference in New Issue
Block a user