init
This commit is contained in:
commit
6250d23297
53 changed files with 6381 additions and 0 deletions
57
app/api/weather/route.ts
Normal file
57
app/api/weather/route.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue