From ad4c72f527684ec0809287a95110b8429a0c8948 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Mon, 23 Mar 2026 12:44:09 +0200 Subject: [PATCH] perf(parcel-sync): make GisFeature groupBy opt-in on /api/eterra/uats The groupBy query scanning the entire GisFeature table (~30k+ rows) was blocking the UAT list API for 25+ seconds on every page load. Feature counts are now opt-in via ?features=true query param. Default response is instant (just GisUat table, no joins). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/api/eterra/uats/route.ts | 38 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/app/api/eterra/uats/route.ts b/src/app/api/eterra/uats/route.ts index bceb266..8c3bf43 100644 --- a/src/app/api/eterra/uats/route.ts +++ b/src/app/api/eterra/uats/route.ts @@ -119,31 +119,33 @@ function unwrapArray(data: any): any[] { /* No eTerra credentials needed — instant response. */ /* ------------------------------------------------------------------ */ -export async function GET() { +export async function GET(req: Request) { try { - // Fetch UATs and local feature counts in parallel - const [rows, featureCounts] = await Promise.all([ - prisma.gisUat.findMany({ orderBy: { name: "asc" } }), - prisma.gisFeature - .groupBy({ - by: ["siruta"], - _count: { id: true }, - }) - .then((groups) => { - const map = new Map(); - for (const g of groups) { - map.set(g.siruta, g._count.id); - } - return map; - }), - ]); + const url = new URL(req.url); + const withFeatures = url.searchParams.get("features") === "true"; + + const rows = await prisma.gisUat.findMany({ orderBy: { name: "asc" } }); + + // Feature counts are expensive (scans entire GisFeature table) + // Only include when explicitly requested + let featureCounts: Map | null = null; + if (withFeatures) { + const groups = await prisma.gisFeature.groupBy({ + by: ["siruta"], + _count: { id: true }, + }); + featureCounts = new Map(); + for (const g of groups) { + featureCounts.set(g.siruta, g._count.id); + } + } const uats: UatResponse[] = rows.map((r) => ({ siruta: r.siruta, name: r.name, county: r.county ?? "", workspacePk: r.workspacePk ?? 0, - localFeatures: featureCounts.get(r.siruta) ?? 0, + localFeatures: featureCounts?.get(r.siruta) ?? 0, })); // Populate in-memory workspace cache for search route