From f15ebd28dcacfaaf459edf66ec561e2b96ec8406 Mon Sep 17 00:00:00 2001 From: zv Date: Sat, 4 Jul 2026 15:28:58 +0200 Subject: [PATCH] fix: preserve brief notification preferences --- app/api/notifications/daily-brief/route.ts | 21 +++++++++++++++++-- app/api/notifications/tomorrow-brief/route.ts | 21 +++++++++++++++++-- components/settings/settings-page.tsx | 10 +++++---- docs/notifications.md | 9 ++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/app/api/notifications/daily-brief/route.ts b/app/api/notifications/daily-brief/route.ts index a2c8d5b..f650648 100644 --- a/app/api/notifications/daily-brief/route.ts +++ b/app/api/notifications/daily-brief/route.ts @@ -54,12 +54,24 @@ export async function GET(request: Request) { const now = new Date(); const briefTime = normalizeTime(process.env.NOTIFICATIONS_MORNING_BRIEF_TIME, DEFAULT_MORNING_BRIEF_TIME); - const subscriptions = getPushSubscriptions().filter( + const allSubscriptions = getPushSubscriptions(); + const subscriptions = allSubscriptions.filter( (subscription) => subscription.morningBriefEnabled && Number.isFinite(subscription.latitude) && Number.isFinite(subscription.longitude), ); + const skippedReasons = { + disabled: allSubscriptions.filter((subscription) => !subscription.morningBriefEnabled).length, + missingLocation: allSubscriptions.filter( + (subscription) => + subscription.morningBriefEnabled && + (!Number.isFinite(subscription.latitude) || !Number.isFinite(subscription.longitude)), + ).length, + beforeTime: 0, + alreadySent: 0, + noBrief: 0, + }; let warningsUnavailable = false; const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL"); const warnings = hasPolishSubscriptions @@ -76,11 +88,13 @@ export async function GET(request: Request) { const localTime = getLocalDateParts(subscription.timezone, now); if (localTime.time < briefTime) { skipped += 1; + skippedReasons.beforeTime += 1; continue; } const dateKey = localTime.dateKey; if (hasSentMorningBrief(subscription.endpoint, dateKey)) { skipped += 1; + skippedReasons.alreadySent += 1; continue; } @@ -103,6 +117,7 @@ export async function GET(request: Request) { }); if (!brief) { skipped += 1; + skippedReasons.noBrief += 1; continue; } await sendMorningBriefNotification(subscription, brief); @@ -119,10 +134,12 @@ export async function GET(request: Request) { return NextResponse.json({ ok: true, time: briefTime, - subscriptions: subscriptions.length, + subscriptions: allSubscriptions.length, + eligibleSubscriptions: subscriptions.length, warningsUnavailable, sent, skipped, + skippedReasons, failed, }); } diff --git a/app/api/notifications/tomorrow-brief/route.ts b/app/api/notifications/tomorrow-brief/route.ts index e020340..b0d6630 100644 --- a/app/api/notifications/tomorrow-brief/route.ts +++ b/app/api/notifications/tomorrow-brief/route.ts @@ -66,12 +66,24 @@ export async function GET(request: Request) { const now = new Date(); const briefTime = normalizeTime(process.env.NOTIFICATIONS_TOMORROW_BRIEF_TIME, DEFAULT_TOMORROW_BRIEF_TIME); - const subscriptions = getPushSubscriptions().filter( + const allSubscriptions = getPushSubscriptions(); + const subscriptions = allSubscriptions.filter( (subscription) => subscription.tomorrowBriefEnabled && Number.isFinite(subscription.latitude) && Number.isFinite(subscription.longitude), ); + const skippedReasons = { + disabled: allSubscriptions.filter((subscription) => !subscription.tomorrowBriefEnabled).length, + missingLocation: allSubscriptions.filter( + (subscription) => + subscription.tomorrowBriefEnabled && + (!Number.isFinite(subscription.latitude) || !Number.isFinite(subscription.longitude)), + ).length, + beforeTime: 0, + alreadySent: 0, + noBrief: 0, + }; let warningsUnavailable = false; const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL"); const warnings = hasPolishSubscriptions @@ -88,11 +100,13 @@ export async function GET(request: Request) { const localToday = getLocalDateParts(subscription.timezone, now); if (localToday.time < briefTime) { skipped += 1; + skippedReasons.beforeTime += 1; continue; } const targetDateKey = getLocalDateParts(subscription.timezone, now, 1).dateKey; if (hasSentTomorrowBrief(subscription.endpoint, targetDateKey)) { skipped += 1; + skippedReasons.alreadySent += 1; continue; } @@ -115,6 +129,7 @@ export async function GET(request: Request) { }); if (!brief) { skipped += 1; + skippedReasons.noBrief += 1; continue; } await sendTomorrowBriefNotification(subscription, brief); @@ -131,10 +146,12 @@ export async function GET(request: Request) { return NextResponse.json({ ok: true, time: briefTime, - subscriptions: subscriptions.length, + subscriptions: allSubscriptions.length, + eligibleSubscriptions: subscriptions.length, warningsUnavailable, sent, skipped, + skippedReasons, failed, }); } diff --git a/components/settings/settings-page.tsx b/components/settings/settings-page.tsx index 7b8666a..64ff5cc 100644 --- a/components/settings/settings-page.tsx +++ b/components/settings/settings-page.tsx @@ -428,14 +428,15 @@ export function SettingsPage() { setMorningBriefEnabled(enabled); if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return; try { + const preferences = useWeatherStore.getState(); const registration = await navigator.serviceWorker?.getRegistration(); const subscription = await registration?.pushManager.getSubscription(); if (!subscription) return; await savePushSubscription(subscription, effectiveProvince, language, { region: selectedRegion, officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled, - morningBriefEnabled: enabled, - tomorrowBriefEnabled, + morningBriefEnabled: preferences.morningBriefNotificationsEnabled, + tomorrowBriefEnabled: preferences.tomorrowBriefNotificationsEnabled, latitude: notificationLatitude, longitude: notificationLongitude, locationName: notificationLocationName, @@ -453,14 +454,15 @@ export function SettingsPage() { setTomorrowBriefEnabled(enabled); if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return; try { + const preferences = useWeatherStore.getState(); const registration = await navigator.serviceWorker?.getRegistration(); const subscription = await registration?.pushManager.getSubscription(); if (!subscription) return; await savePushSubscription(subscription, effectiveProvince, language, { region: selectedRegion, officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled, - morningBriefEnabled, - tomorrowBriefEnabled: enabled, + morningBriefEnabled: preferences.morningBriefNotificationsEnabled, + tomorrowBriefEnabled: preferences.tomorrowBriefNotificationsEnabled, latitude: notificationLatitude, longitude: notificationLongitude, locationName: notificationLocationName, diff --git a/docs/notifications.md b/docs/notifications.md index 9474d21..4d136e1 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -102,6 +102,15 @@ Worker: Endpointy harmonogramu nie uruchamiają się same bez workera albo zewnętrznego crona. +Endpointy briefów zwracają diagnostykę JSON z polami `subscriptions`, `eligibleSubscriptions`, `sent`, `skipped`, +`failed` oraz `skippedReasons`. Dla problemów z harmonogramem najważniejsze powody to: + +- `disabled` - subskrypcja istnieje, ale dany brief nie jest włączony w zapisanym stanie serwera, +- `missingLocation` - brief jest włączony, ale subskrypcja nie ma współrzędnych, +- `beforeTime` - lokalny czas subskrypcji jest jeszcze przed skonfigurowaną godziną, +- `alreadySent` - brief dla tej lokalnej daty został już oznaczony jako wysłany, +- `noBrief` - prognoza nie pozwoliła zbudować treści briefu. + ## Zewnętrzny Cron Jeśli nie używasz `npm run notifications:worker`, zewnętrzny cron powinien wywoływać: