import { NextResponse } from "next/server"; import { fetchMeteoWarnings } from "@/lib/server-warnings"; import { getPushSubscriptions, hasSentWarning, markWarningSent, pruneSentWarnings, removePushSubscription, } from "@/lib/push-store"; import { isWebPushConfigured, sendWarningNotification } from "@/lib/push-service"; import { warningMatchesCounty } from "@/lib/warning-regions"; import type { WeatherWarning } from "@/types/imgw"; export const runtime = "nodejs"; function getTimestamp(value: string | null) { if (!value) return null; const timestamp = new Date(value).getTime(); return Number.isNaN(timestamp) ? null : timestamp; } function isRelevantWarning(warning: WeatherWarning, now: number) { const validTo = getTimestamp(warning.validTo); return warning.kind === "meteo" && (validTo === null || validTo > now); } function isCronAuthorized(request: Request) { const secret = process.env.NOTIFICATIONS_CRON_SECRET; if (!secret) return process.env.NODE_ENV !== "production"; const authHeader = request.headers.get("authorization"); const secretHeader = request.headers.get("x-cron-secret"); return authHeader === `Bearer ${secret}` || secretHeader === secret; } function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : "Unknown notification check error."; } export async function GET(request: Request) { if (!isCronAuthorized(request)) { return NextResponse.json({ error: "Unauthorized." }, { status: 401 }); } if (!isWebPushConfigured()) { return NextResponse.json({ error: "Web Push is not configured." }, { status: 503 }); } try { const now = Date.now(); const warnings = (await fetchMeteoWarnings(AbortSignal.timeout(12_000))).filter((warning) => isRelevantWarning(warning, now), ); const subscriptions = getPushSubscriptions().filter( (subscription) => subscription.region === "PL" && subscription.enabled && subscription.province, ); const activeWarningIds = new Set(warnings.map((warning) => warning.id)); let sent = 0; let skipped = 0; let failed = 0; pruneSentWarnings(activeWarningIds); for (const subscription of subscriptions) { const matchingWarnings = warnings.filter((warning) => subscription.countyTeryt ? warningMatchesCounty(warning, subscription.countyTeryt) : subscription.province !== null && warning.provinces.includes(subscription.province), ); for (const warning of matchingWarnings) { if (hasSentWarning(subscription.endpoint, warning.id)) { skipped += 1; continue; } try { await sendWarningNotification(subscription, warning); markWarningSent(subscription.endpoint, warning.id); sent += 1; } catch (error) { failed += 1; const statusCode = typeof error === "object" && error !== null && "statusCode" in error ? Number(error.statusCode) : null; if (statusCode === 404 || statusCode === 410) removePushSubscription(subscription.endpoint); } } } return NextResponse.json({ ok: true, subscriptions: subscriptions.length, warnings: warnings.length, sent, skipped, failed, }); } catch (error) { return NextResponse.json( { error: "Unable to check IMGW meteorological warnings.", details: getErrorMessage(error), }, { status: 502 }, ); } }