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) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 12:44:09 +02:00
parent 2886703d0f
commit ad4c72f527
+20 -18
View File
@@ -119,31 +119,33 @@ function unwrapArray(data: any): any[] {
/* No eTerra credentials needed — instant response. */ /* No eTerra credentials needed — instant response. */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
export async function GET() { export async function GET(req: Request) {
try { try {
// Fetch UATs and local feature counts in parallel const url = new URL(req.url);
const [rows, featureCounts] = await Promise.all([ const withFeatures = url.searchParams.get("features") === "true";
prisma.gisUat.findMany({ orderBy: { name: "asc" } }),
prisma.gisFeature const rows = await prisma.gisUat.findMany({ orderBy: { name: "asc" } });
.groupBy({
by: ["siruta"], // Feature counts are expensive (scans entire GisFeature table)
_count: { id: true }, // Only include when explicitly requested
}) let featureCounts: Map<string, number> | null = null;
.then((groups) => { if (withFeatures) {
const map = new Map<string, number>(); const groups = await prisma.gisFeature.groupBy({
for (const g of groups) { by: ["siruta"],
map.set(g.siruta, g._count.id); _count: { id: true },
} });
return map; featureCounts = new Map<string, number>();
}), for (const g of groups) {
]); featureCounts.set(g.siruta, g._count.id);
}
}
const uats: UatResponse[] = rows.map((r) => ({ const uats: UatResponse[] = rows.map((r) => ({
siruta: r.siruta, siruta: r.siruta,
name: r.name, name: r.name,
county: r.county ?? "", county: r.county ?? "",
workspacePk: r.workspacePk ?? 0, workspacePk: r.workspacePk ?? 0,
localFeatures: featureCounts.get(r.siruta) ?? 0, localFeatures: featureCounts?.get(r.siruta) ?? 0,
})); }));
// Populate in-memory workspace cache for search route // Populate in-memory workspace cache for search route