T14 fixup: route ordering + UUID guard for /api/agents/:id

Verification after commit d4c02a3 found Hono matching /:id BEFORE /activity
when the bare /api/agents/activity request hit the API — Postgres returned a
500 cast error because id='activity' was not a valid UUID.

Two changes:
1. Move GET /api/agents/activity registration to BEFORE GET /:id so Hono's
   matcher picks the static path before the param path.
2. Add a UUID-format guard at the top of GET /api/agents/:id — returns
   404 when id is not a UUID. Defense in depth: prevents future 500s if
   a similarly-shaped static route collides with /:id.

Parent: t_e1cbd87d
This commit is contained in:
Hermes
2026-08-01 04:24:20 +00:00
parent 2e451093b6
commit 3847ef7b2d
+24 -16
View File
@@ -100,11 +100,35 @@ agentRoutes.post("/", async (c) => {
}
});
// GET /api/agents/activity — All activity (bare path, no agent filter)
agentRoutes.get("/activity", async (c) => {
try {
await requireAuth(c);
const items = await db.select()
.from(agentActivity)
.orderBy(desc(agentActivity.createdAt))
.limit(100);
return c.json({ items, totalItems: items.length });
} catch (error) {
if (error instanceof AuthError) return c.json({ error: { code: error.code, message: error.message } }, error.status as any);
console.error("[agents] GET /activity error:", error);
return c.json({ error: { code: "INTERNAL_ERROR", message: "Failed to get activity" } }, 500);
}
});
// GET /api/agents/:id — Read
agentRoutes.get("/:id", async (c) => {
try {
await requireAuth(c);
const id = c.req.param("id");
// Guard: /:id must be a UUID. Hono matches /:id before /activity when the
// param path was registered first; without this guard we get a Postgres
// "invalid input syntax for type uuid" 500 on /api/agents/activity.
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
return c.json({ error: { code: "NOT_FOUND", message: "Agent not found" } }, 404);
}
const [agent] = await db.select().from(agents).where(eq(agents.id, id)).limit(1);
if (!agent) return c.json({ error: { code: "NOT_FOUND", message: "Agent not found" } }, 404);
return c.json(agent);
@@ -224,22 +248,6 @@ agentRoutes.get("/:id/permissions", async (c) => {
}
});
// GET /api/agents/activity — All activity (bare path, no agent filter)
agentRoutes.get("/activity", async (c) => {
try {
await requireAuth(c);
const items = await db.select()
.from(agentActivity)
.orderBy(desc(agentActivity.createdAt))
.limit(100);
return c.json({ items, totalItems: items.length });
} catch (error) {
if (error instanceof AuthError) return c.json({ error: { code: error.code, message: error.message } }, error.status as any);
console.error("[agents] GET /activity error:", error);
return c.json({ error: { code: "INTERNAL_ERROR", message: "Failed to get activity" } }, 500);
}
});
// GET /api/agents/:id/activity — Agent activity log (or all if id=_all)
agentRoutes.get("/:id/activity", async (c) => {
try {