import { z } from "zod"; import { getDb } from "@tline/db"; import { clusterMoments } from "../../lib/moments"; export const runtime = "nodejs"; const querySchema = z .object({ start: z.string().datetime().optional(), end: z.string().datetime().optional(), includeFailed: z.coerce.number().int().optional(), limit: z.coerce.number().int().positive().max(2000).default(1000), }) .strict(); export async function GET(request: Request): Promise { const url = new URL(request.url); const parsed = querySchema.safeParse({ start: url.searchParams.get("start") ?? undefined, end: url.searchParams.get("end") ?? undefined, includeFailed: url.searchParams.get("includeFailed") ?? undefined, limit: url.searchParams.get("limit") ?? undefined, }); if (!parsed.success) { return Response.json( { error: "invalid_query", issues: parsed.error.issues }, { status: 400 }, ); } const query = parsed.data; const start = query.start ? new Date(query.start) : null; const end = query.end ? new Date(query.end) : null; const includeFailed = query.includeFailed === 1; const db = getDb(); const rows = await db< { id: string; capture_ts_utc: string | null; }[] >` select id, capture_ts_utc from assets where capture_ts_utc is not null and (${start}::timestamptz is null or capture_ts_utc >= ${start}::timestamptz) and (${end}::timestamptz is null or capture_ts_utc < ${end}::timestamptz) and (${includeFailed}::boolean is true or status <> 'failed') order by capture_ts_utc asc, id asc limit ${query.limit} `; const clusters = clusterMoments( rows .filter((row) => Boolean(row.capture_ts_utc)) .map((row) => ({ id: row.id, capture_ts_utc: row.capture_ts_utc as string, })), ); return Response.json({ start: start ? start.toISOString() : null, end: end ? end.toISOString() : null, clusters, }); }