feat: add consent-based GPS location

This commit is contained in:
zv
2026-06-01 20:28:03 +02:00
parent f5898ced4f
commit ce2e1176fa
9 changed files with 256 additions and 4 deletions

View File

@@ -0,0 +1,65 @@
import { NextResponse } from "next/server";
const REVERSE_GEOCODING_URL = "https://nominatim.openstreetmap.org/reverse";
const USER_AGENT = "wtr./1.0 (https://git.zvcloud.net/zv/wtr)";
interface RawReverseLocation {
name?: string;
display_name?: string;
address?: {
city?: string;
town?: string;
village?: string;
municipality?: string;
state?: string;
};
}
function parseCoordinate(value: string | null, min: number, max: number) {
if (!value?.trim()) return null;
const coordinate = Number(value);
return Number.isFinite(coordinate) && coordinate >= min && coordinate <= max ? Number(coordinate.toFixed(3)) : null;
}
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const latitude = parseCoordinate(searchParams.get("latitude"), -90, 90);
const longitude = parseCoordinate(searchParams.get("longitude"), -180, 180);
const language = searchParams.get("language") === "en" ? "en" : "pl";
if (latitude === null || longitude === null) {
return NextResponse.json({ error: "Invalid coordinates." }, { status: 400 });
}
const params = new URLSearchParams({
format: "jsonv2",
lat: String(latitude),
lon: String(longitude),
zoom: "10",
addressdetails: "1",
"accept-language": language,
});
try {
const response = await fetch(`${REVERSE_GEOCODING_URL}?${params}`, {
next: { revalidate: 86400 },
headers: { Accept: "application/json", "User-Agent": USER_AGENT },
});
if (!response.ok) return NextResponse.json({ error: "Reverse geocoding is unavailable." }, { status: 502 });
const data = await response.json() as RawReverseLocation;
const name = data.address?.city
?? data.address?.town
?? data.address?.village
?? data.address?.municipality
?? data.name
?? data.display_name?.split(",")[0]?.trim();
if (!name) return NextResponse.json({ error: "Place name is unavailable." }, { status: 404 });
return NextResponse.json({
name,
province: data.address?.state ?? null,
}, {
headers: { "Cache-Control": "public, s-maxage=86400, stale-while-revalidate=604800" },
});
} catch {
return NextResponse.json({ error: "Reverse geocoding is unavailable." }, { status: 502 });
}
}