fifteen/apps/web/app/api/cities/route.ts

56 lines
1.5 KiB
TypeScript

import { NextResponse } from "next/server";
import { sql } from "@/lib/db";
import { cacheGet, cacheSet } from "@/lib/cache";
import type { City } from "@transportationer/shared";
export const runtime = "nodejs";
export async function GET() {
const cacheKey = "api:cities:all:v2";
const cached = await cacheGet<City[]>(cacheKey);
if (cached) {
return NextResponse.json(cached, { headers: { "X-Cache": "HIT" } });
}
const rows = await Promise.resolve(sql<{
slug: string;
name: string;
country_code: string;
geofabrik_url: string;
bbox_arr: number[] | null;
boundary_geojson: string | null;
status: string;
last_ingested: string | null;
}[]>`
SELECT
slug,
name,
country_code,
geofabrik_url,
CASE WHEN bbox IS NOT NULL THEN ARRAY[
ST_XMin(bbox)::float,
ST_YMin(bbox)::float,
ST_XMax(bbox)::float,
ST_YMax(bbox)::float
] END AS bbox_arr,
ST_AsGeoJSON(boundary) AS boundary_geojson,
status,
last_ingested
FROM cities
ORDER BY name
`);
const cities: City[] = rows.map((r) => ({
slug: r.slug,
name: r.name,
countryCode: r.country_code,
geofabrikUrl: r.geofabrik_url,
bbox: (r.bbox_arr as [number, number, number, number]) ?? [0, 0, 0, 0],
boundary: r.boundary_geojson ? JSON.parse(r.boundary_geojson) : null,
status: r.status as City["status"],
lastIngested: r.last_ingested,
}));
await cacheSet(cacheKey, cities, "API_CITIES");
return NextResponse.json(cities);
}