diff --git a/app/api/imgw/[...path]/route.ts b/app/api/imgw/[...path]/route.ts index 0713e33..9527147 100644 --- a/app/api/imgw/[...path]/route.ts +++ b/app/api/imgw/[...path]/route.ts @@ -9,6 +9,7 @@ const ALLOWED_PATHS = new Set([ "product", ]); const IMGW_BASE_URL = "https://danepubliczne.imgw.pl/api/data"; +const USER_AGENT = "wtr./1.0 (weather data proxy)"; export async function GET(_: Request, context: { params: Promise<{ path: string[] }> }) { const { path } = await context.params; @@ -24,7 +25,7 @@ export async function GET(_: Request, context: { params: Promise<{ path: string[ try { const response = await fetch(`${IMGW_BASE_URL}/${upstreamPath}`, { next: { revalidate: 300 }, - headers: { Accept: "application/json" }, + headers: { Accept: "application/json", "User-Agent": USER_AGENT }, }); if (!response.ok) { return NextResponse.json({ error: "IMGW API jest chwilowo niedostępne." }, { status: response.status }); diff --git a/lib/server-warnings.ts b/lib/server-warnings.ts index 11eb95d..de3548a 100644 --- a/lib/server-warnings.ts +++ b/lib/server-warnings.ts @@ -2,14 +2,18 @@ import { normalizeWarning } from "@/lib/weather-utils"; import type { RawWarning, WeatherWarning } from "@/types/imgw"; const IMGW_WARNINGS_METEO_URL = "https://danepubliczne.imgw.pl/api/data/warningsmeteo"; +const USER_AGENT = "wtr./1.0 (weather notification worker)"; export async function fetchMeteoWarnings(signal?: AbortSignal): Promise { const response = await fetch(IMGW_WARNINGS_METEO_URL, { - headers: { accept: "application/json" }, + headers: { accept: "application/json", "user-agent": USER_AGENT }, next: { revalidate: 300 }, signal, }); - if (!response.ok) throw new Error("Unable to load IMGW meteorological warnings."); + if (!response.ok) { + const details = await response.text().catch(() => ""); + throw new Error(`Unable to load IMGW meteorological warnings: ${response.status}${details ? ` ${details.slice(0, 240)}` : ""}`); + } const rows = await response.json() as RawWarning[]; return Array.isArray(rows) ? rows.map((warning, index) => normalizeWarning(warning, "meteo", index)) : []; } diff --git a/scripts/notification-worker.mjs b/scripts/notification-worker.mjs index f8f6459..0571d62 100644 --- a/scripts/notification-worker.mjs +++ b/scripts/notification-worker.mjs @@ -77,6 +77,16 @@ async function callNotificationEndpoint(path) { return body; } +async function callNotificationEndpointSafely(path) { + try { + await callNotificationEndpoint(path); + return true; + } catch (error) { + console.error(formatWorkerError(error)); + return false; + } +} + function formatWorkerError(error) { if (error?.cause?.code === "ECONNREFUSED") { return [ @@ -91,17 +101,19 @@ function formatWorkerError(error) { async function tick() { if (isRunningTick) return; isRunningTick = true; + const now = Date.now(); + const warningIntervalMs = warningIntervalMinutes * 60_000; + try { - const now = Date.now(); - const warningIntervalMs = warningIntervalMinutes * 60_000; if (now - lastWarningCheckAt >= warningIntervalMs) { lastWarningCheckAt = now; - await callNotificationEndpoint("/api/notifications/check"); + await callNotificationEndpointSafely("/api/notifications/check"); } const warsawTime = getWarsawDateParts(); if (isAtOrAfterTime(warsawTime.time, morningBriefTime) && lastMorningBriefDate !== warsawTime.dateKey) { - await callNotificationEndpoint("/api/notifications/daily-brief"); + const sentBrief = await callNotificationEndpointSafely("/api/notifications/daily-brief"); + if (!sentBrief) return; lastMorningBriefDate = warsawTime.dateKey; } } catch (error) {