import { NextResponse } from "next/server"; import { fetchServerForecast } from "@/lib/server-forecast"; import { fetchMeteoWarnings } from "@/lib/server-warnings"; import { getPushSubscriptions, hasSentMorningBrief, markMorningBriefSent, removePushSubscription } from "@/lib/push-store"; import { isWebPushConfigured, sendMorningBriefNotification } from "@/lib/push-service"; import { buildWeatherBrief } from "@/lib/weather-brief"; export const runtime = "nodejs"; 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 getWarsawDateKey(date = new Date()) { const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Warsaw", year: "numeric", month: "2-digit", day: "2-digit", }).formatToParts(date); const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((entry) => entry.type === type)?.value ?? ""; return `${part("year")}-${part("month")}-${part("day")}`; } 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 }); } const now = new Date(); const dateKey = getWarsawDateKey(now); const subscriptions = getPushSubscriptions().filter((subscription) => ( subscription.enabled && subscription.morningBriefEnabled && Number.isFinite(subscription.latitude) && Number.isFinite(subscription.longitude) )); let warningsUnavailable = false; const warnings = await fetchMeteoWarnings(AbortSignal.timeout(12_000)).catch(() => { warningsUnavailable = true; return []; }); let sent = 0; let skipped = 0; let failed = 0; for (const subscription of subscriptions) { if (hasSentMorningBrief(subscription.endpoint, dateKey)) { skipped += 1; continue; } try { const forecast = await fetchServerForecast(subscription.latitude as number, subscription.longitude as number); const brief = buildWeatherBrief({ forecast, warnings, province: subscription.province, countyTeryt: subscription.countyTeryt, locationName: subscription.locationName ?? "wtr.", language: subscription.language, now, }); if (!brief) { skipped += 1; continue; } await sendMorningBriefNotification(subscription, brief); markMorningBriefSent(subscription.endpoint, dateKey); 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, date: dateKey, subscriptions: subscriptions.length, warningsUnavailable, sent, skipped, failed, }); }