58 lines
2.5 KiB
TypeScript
58 lines
2.5 KiB
TypeScript
|
|
import { NextResponse, type NextRequest } from "next/server";
|
||
|
|
import { getLocalContext, type LocalContext } from "@/lib/weather/extras/air-quality";
|
||
|
|
import { getNwsAlerts } from "@/lib/weather/extras/nws-alerts";
|
||
|
|
import { isProviderId, isReady, PROVIDERS } from "@/lib/weather/providers";
|
||
|
|
import { badRequest, errorResponse, forbidCrossSite, parseCoords } from "@/lib/weather/respond";
|
||
|
|
import type { WeatherAlert, WeatherReport } from "@/lib/weather/types";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/weather?provider=openweather&lat=44.77&lon=-93.27
|
||
|
|
*
|
||
|
|
* Runs on the server so API keys stay in .env / .env.local and never reach the browser.
|
||
|
|
*
|
||
|
|
* Three things are fetched at once:
|
||
|
|
* 1. the forecast from the chosen provider (required — failure is an error)
|
||
|
|
* 2. air quality + time zone from Open-Meteo (optional — failure just hides it)
|
||
|
|
* 3. NWS alerts for US locations (optional — failure just hides them)
|
||
|
|
*/
|
||
|
|
export async function GET(request: NextRequest) {
|
||
|
|
const blocked = forbidCrossSite(request);
|
||
|
|
if (blocked) return blocked;
|
||
|
|
|
||
|
|
const params = request.nextUrl.searchParams;
|
||
|
|
const providerId = params.get("provider") ?? "openweather";
|
||
|
|
const coords = parseCoords(params);
|
||
|
|
|
||
|
|
if (!isProviderId(providerId)) return badRequest(`Unknown provider "${providerId}"`);
|
||
|
|
if (!coords) return badRequest("lat and lon are required");
|
||
|
|
|
||
|
|
const provider = PROVIDERS[providerId];
|
||
|
|
if (!isReady(provider)) return badRequest(`${provider.label} has no API key configured`);
|
||
|
|
|
||
|
|
const [forecast, context, nwsAlerts] = await Promise.allSettled([
|
||
|
|
provider.getForecast(coords.lat, coords.lon),
|
||
|
|
getLocalContext(coords.lat, coords.lon),
|
||
|
|
getNwsAlerts(coords.lat, coords.lon),
|
||
|
|
]);
|
||
|
|
|
||
|
|
if (forecast.status === "rejected") return errorResponse(forecast.reason);
|
||
|
|
|
||
|
|
const local: LocalContext | null = context.status === "fulfilled" ? context.value : null;
|
||
|
|
const official: WeatherAlert[] = nwsAlerts.status === "fulfilled" ? nwsAlerts.value : [];
|
||
|
|
|
||
|
|
// Prefer official NWS alerts in the US; elsewhere use whatever the provider gave us.
|
||
|
|
const alerts = official.length > 0 ? official : forecast.value.alerts;
|
||
|
|
const alertSource = official.length > 0 ? "National Weather Service" : alerts.length > 0 ? provider.label : null;
|
||
|
|
|
||
|
|
const report: WeatherReport = {
|
||
|
|
...forecast.value,
|
||
|
|
provider: providerId,
|
||
|
|
timeZone: forecast.value.timeZone ?? local?.timeZone ?? null,
|
||
|
|
airQuality: local?.airQuality ?? null,
|
||
|
|
alerts,
|
||
|
|
alertSource,
|
||
|
|
fetchedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
return NextResponse.json(report);
|
||
|
|
}
|