/** * GET /api/geoportal/search?q=...&type=...&limit=... * * Searches parcels (by cadastral ref, owner) and UATs (by name). * Returns centroids in EPSG:4326 (WGS84) for map flyTo. */ import { NextResponse } from "next/server"; import { prisma } from "@/core/storage/prisma"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; type SearchResultItem = { id: string; type: "parcel" | "uat" | "building"; label: string; sublabel?: string; coordinates?: [number, number]; }; export async function GET(req: Request) { try { const url = new URL(req.url); const q = url.searchParams.get("q")?.trim() ?? ""; const typeFilter = url.searchParams.get("type") ?? ""; const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "20", 10), 50); if (q.length < 2) { return NextResponse.json({ results: [] }); } const results: SearchResultItem[] = []; const pattern = `%${q}%`; // Search UATs by name if (!typeFilter || typeFilter === "uat") { const uats = await prisma.$queryRaw` SELECT siruta, name, county, ST_X(ST_Centroid(ST_Transform(geom, 4326))) as lng, ST_Y(ST_Centroid(ST_Transform(geom, 4326))) as lat FROM "GisUat" WHERE geom IS NOT NULL AND (name ILIKE ${pattern} OR county ILIKE ${pattern}) ORDER BY name LIMIT ${limit} ` as Array<{ siruta: string; name: string; county: string | null; lng: number; lat: number }>; for (const u of uats) { results.push({ id: `uat-${u.siruta}`, type: "uat", label: u.name, sublabel: u.county ? `Jud. ${u.county}` : undefined, coordinates: u.lng && u.lat ? [u.lng, u.lat] : undefined, }); } } // Search parcels by cadastral ref or enrichment data if (!typeFilter || typeFilter === "parcel") { const isNumericish = /^\d/.test(q); if (isNumericish) { // Search by cadastral reference const parcels = await prisma.$queryRaw` SELECT f.id, f."cadastralRef", f."areaValue", f.siruta, f.enrichment, u.name as uat_name, ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng, ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat FROM "GisFeature" f LEFT JOIN "GisUat" u ON u.siruta = f.siruta WHERE f.geom IS NOT NULL AND f."layerId" LIKE 'TERENURI%' AND (f."cadastralRef" ILIKE ${pattern} OR f.enrichment::text ILIKE ${`%"NR_CAD":"${q}%`}) ORDER BY f."cadastralRef" LIMIT ${limit} ` as Array<{ id: string; cadastralRef: string | null; areaValue: number | null; siruta: string; uat_name: string | null; enrichment: Record | null; lng: number; lat: number; }>; for (const p of parcels) { const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?"; const area = p.areaValue ? `${Math.round(p.areaValue)} mp` : ""; const uatLabel = p.uat_name ?? `SIRUTA ${p.siruta}`; results.push({ id: `parcel-${p.id}`, type: "parcel", label: `Parcela ${nrCad} — ${uatLabel}`, sublabel: [area, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "), coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined, }); } } else { // Search by owner name in enrichment JSON const parcels = await prisma.$queryRaw` SELECT f.id, f."cadastralRef", f."areaValue", f.siruta, f.enrichment, u.name as uat_name, ST_X(ST_Centroid(ST_Transform(f.geom, 4326))) as lng, ST_Y(ST_Centroid(ST_Transform(f.geom, 4326))) as lat FROM "GisFeature" f LEFT JOIN "GisUat" u ON u.siruta = f.siruta WHERE f.geom IS NOT NULL AND f."layerId" LIKE 'TERENURI%' AND f.enrichment IS NOT NULL AND f.enrichment::text ILIKE ${pattern} ORDER BY f."cadastralRef" LIMIT ${limit} ` as Array<{ id: string; cadastralRef: string | null; areaValue: number | null; siruta: string; uat_name: string | null; enrichment: Record | null; lng: number; lat: number; }>; for (const p of parcels) { const nrCad = (p.enrichment?.NR_CAD as string) ?? p.cadastralRef ?? "?"; const owner = (p.enrichment?.PROPRIETARI as string) ?? ""; const uatLabel = p.uat_name ?? `SIRUTA ${p.siruta}`; const ownerShort = owner.length > 60 ? owner.slice(0, 60) + "..." : owner; results.push({ id: `parcel-${p.id}`, type: "parcel", label: `Parcela ${nrCad} — ${uatLabel}`, sublabel: [ownerShort, p.uat_name ? `SIRUTA ${p.siruta}` : ""].filter(Boolean).join(" | "), coordinates: p.lng && p.lat ? [p.lng, p.lat] : undefined, }); } } } return NextResponse.json({ results: results.slice(0, limit) }); } catch (error) { const msg = error instanceof Error ? error.message : "Eroare la cautare"; return NextResponse.json({ error: msg }, { status: 500 }); } }