import { NextRequest, NextResponse } from "next/server"; import { sql } from "@/lib/db"; export const runtime = "nodejs"; export async function GET( req: NextRequest, { params }: { params: Promise<{ tile: string[] }> }, ) { const { tile } = await params; if (tile.length !== 3) { return new NextResponse("Invalid tile path", { status: 400 }); } const z = parseInt(tile[0], 10); const x = parseInt(tile[1], 10); const y = parseInt(tile[2], 10); if ([z, x, y].some(isNaN)) { return new NextResponse("Invalid tile coordinates", { status: 400 }); } const city = req.nextUrl.searchParams.get("city") ?? ""; if (!city) return new NextResponse("Missing city", { status: 400 }); try { const rows = await Promise.resolve(sql<{ mvt: Uint8Array }[]>` WITH envelope AS (SELECT ST_TileEnvelope(${z}, ${x}, ${y}) AS env), city_info AS (SELECT COALESCE(resolution_m, 200) AS resolution_m FROM cities WHERE slug = ${city}) SELECT ST_AsMVT(t, 'hidden-gems', 4096, 'geom') AS mvt FROM ( SELECT ST_AsMVTGeom( ST_Expand(ST_Transform(gp.geom, 3857), ci.resolution_m::float / 2), e.env, 4096, 0, true ) AS geom, ROUND(gp.hidden_gem_score * 100)::int AS score FROM grid_points gp CROSS JOIN envelope e CROSS JOIN city_info ci WHERE gp.city_slug = ${city} AND gp.hidden_gem_score IS NOT NULL AND ST_Intersects( ST_Transform(gp.geom, 3857), ST_Expand(e.env, ci.resolution_m::float / 2) ) ) t WHERE t.geom IS NOT NULL `); const buf = rows[0]?.mvt; const data = buf ? new Uint8Array(buf) : new Uint8Array(0); return new NextResponse(data, { headers: { "Content-Type": "application/x-protobuf", "Cache-Control": "public, max-age=3600", "Access-Control-Allow-Origin": "*", }, }); } catch (err) { console.error("[tiles/hidden-gems] Error:", err); return new NextResponse("Internal Server Error", { status: 500 }); } }