import { NextRequest, NextResponse } from "next/server"; import { sql } from "@/lib/db"; import { fetchIsochrone } from "@/lib/valhalla"; import { getValhallaQueue } from "@/lib/queue"; export const runtime = "nodejs"; const CACHE_TOLERANCE_M = 50; export async function POST(req: NextRequest) { let body: unknown; try { body = await req.json(); } catch { return NextResponse.json( { error: "Invalid JSON body", code: "INVALID_BODY" }, { status: 400 }, ); } const { lng, lat, travelMode, contourMinutes } = body as Record< string, unknown >; if (typeof lng !== "number" || typeof lat !== "number") { return NextResponse.json( { error: "lng and lat must be numbers", code: "INVALID_COORDS" }, { status: 400 }, ); } const contours: number[] = Array.isArray(contourMinutes) ? (contourMinutes as number[]) : [5, 10, 15]; const mode = typeof travelMode === "string" ? travelMode : "walking"; // Check PostGIS isochrone cache const cached = await Promise.resolve(sql<{ result: object }[]>` SELECT result FROM isochrone_cache WHERE travel_mode = ${mode} AND contours_min = ${contours} AND ST_DWithin( origin_geom::geography, ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326)::geography, ${CACHE_TOLERANCE_M} ) ORDER BY created_at DESC LIMIT 1 `); if (cached.length > 0) { return NextResponse.json({ ...cached[0].result, cached: true }); } // Refuse to call valhalla_service while tiles are being rebuilt — // the service is stopped during the build and requests would hang or fail. const activeValhalla = await getValhallaQueue().getActiveCount(); if (activeValhalla > 0) { return NextResponse.json( { error: "Routing engine is rebuilding, please try again shortly.", code: "VALHALLA_REBUILDING" }, { status: 503, headers: { "Retry-After": "60" } }, ); } // Fetch from local Valhalla let geojson: object; try { geojson = await fetchIsochrone({ lng, lat, travelMode: mode, contourMinutes: contours, polygons: true, }); } catch (err) { return NextResponse.json( { error: "Routing engine unavailable", code: "VALHALLA_ERROR", detail: err instanceof Error ? err.message : "unknown", }, { status: 503 }, ); } // Store in PostGIS cache await Promise.resolve(sql` INSERT INTO isochrone_cache (origin_geom, travel_mode, contours_min, result) VALUES ( ST_SetSRID(ST_MakePoint(${lng}, ${lat}), 4326), ${mode}, ${contours}, ${JSON.stringify(geojson)} ) `); return NextResponse.json({ ...geojson, cached: false }); }