fix: preserve brief notification preferences
All checks were successful
CI / Lint, test, typecheck and build (push) Successful in 10m45s

This commit is contained in:
zv
2026-07-04 15:28:58 +02:00
parent b1cbc771f8
commit f15ebd28dc
4 changed files with 53 additions and 8 deletions

View File

@@ -54,12 +54,24 @@ export async function GET(request: Request) {
const now = new Date(); const now = new Date();
const briefTime = normalizeTime(process.env.NOTIFICATIONS_MORNING_BRIEF_TIME, DEFAULT_MORNING_BRIEF_TIME); 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) =>
subscription.morningBriefEnabled && subscription.morningBriefEnabled &&
Number.isFinite(subscription.latitude) && Number.isFinite(subscription.latitude) &&
Number.isFinite(subscription.longitude), 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; let warningsUnavailable = false;
const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL"); const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL");
const warnings = hasPolishSubscriptions const warnings = hasPolishSubscriptions
@@ -76,11 +88,13 @@ export async function GET(request: Request) {
const localTime = getLocalDateParts(subscription.timezone, now); const localTime = getLocalDateParts(subscription.timezone, now);
if (localTime.time < briefTime) { if (localTime.time < briefTime) {
skipped += 1; skipped += 1;
skippedReasons.beforeTime += 1;
continue; continue;
} }
const dateKey = localTime.dateKey; const dateKey = localTime.dateKey;
if (hasSentMorningBrief(subscription.endpoint, dateKey)) { if (hasSentMorningBrief(subscription.endpoint, dateKey)) {
skipped += 1; skipped += 1;
skippedReasons.alreadySent += 1;
continue; continue;
} }
@@ -103,6 +117,7 @@ export async function GET(request: Request) {
}); });
if (!brief) { if (!brief) {
skipped += 1; skipped += 1;
skippedReasons.noBrief += 1;
continue; continue;
} }
await sendMorningBriefNotification(subscription, brief); await sendMorningBriefNotification(subscription, brief);
@@ -119,10 +134,12 @@ export async function GET(request: Request) {
return NextResponse.json({ return NextResponse.json({
ok: true, ok: true,
time: briefTime, time: briefTime,
subscriptions: subscriptions.length, subscriptions: allSubscriptions.length,
eligibleSubscriptions: subscriptions.length,
warningsUnavailable, warningsUnavailable,
sent, sent,
skipped, skipped,
skippedReasons,
failed, failed,
}); });
} }

View File

@@ -66,12 +66,24 @@ export async function GET(request: Request) {
const now = new Date(); const now = new Date();
const briefTime = normalizeTime(process.env.NOTIFICATIONS_TOMORROW_BRIEF_TIME, DEFAULT_TOMORROW_BRIEF_TIME); 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) =>
subscription.tomorrowBriefEnabled && subscription.tomorrowBriefEnabled &&
Number.isFinite(subscription.latitude) && Number.isFinite(subscription.latitude) &&
Number.isFinite(subscription.longitude), 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; let warningsUnavailable = false;
const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL"); const hasPolishSubscriptions = subscriptions.some((subscription) => subscription.region === "PL");
const warnings = hasPolishSubscriptions const warnings = hasPolishSubscriptions
@@ -88,11 +100,13 @@ export async function GET(request: Request) {
const localToday = getLocalDateParts(subscription.timezone, now); const localToday = getLocalDateParts(subscription.timezone, now);
if (localToday.time < briefTime) { if (localToday.time < briefTime) {
skipped += 1; skipped += 1;
skippedReasons.beforeTime += 1;
continue; continue;
} }
const targetDateKey = getLocalDateParts(subscription.timezone, now, 1).dateKey; const targetDateKey = getLocalDateParts(subscription.timezone, now, 1).dateKey;
if (hasSentTomorrowBrief(subscription.endpoint, targetDateKey)) { if (hasSentTomorrowBrief(subscription.endpoint, targetDateKey)) {
skipped += 1; skipped += 1;
skippedReasons.alreadySent += 1;
continue; continue;
} }
@@ -115,6 +129,7 @@ export async function GET(request: Request) {
}); });
if (!brief) { if (!brief) {
skipped += 1; skipped += 1;
skippedReasons.noBrief += 1;
continue; continue;
} }
await sendTomorrowBriefNotification(subscription, brief); await sendTomorrowBriefNotification(subscription, brief);
@@ -131,10 +146,12 @@ export async function GET(request: Request) {
return NextResponse.json({ return NextResponse.json({
ok: true, ok: true,
time: briefTime, time: briefTime,
subscriptions: subscriptions.length, subscriptions: allSubscriptions.length,
eligibleSubscriptions: subscriptions.length,
warningsUnavailable, warningsUnavailable,
sent, sent,
skipped, skipped,
skippedReasons,
failed, failed,
}); });
} }

View File

@@ -428,14 +428,15 @@ export function SettingsPage() {
setMorningBriefEnabled(enabled); setMorningBriefEnabled(enabled);
if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return; if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return;
try { try {
const preferences = useWeatherStore.getState();
const registration = await navigator.serviceWorker?.getRegistration(); const registration = await navigator.serviceWorker?.getRegistration();
const subscription = await registration?.pushManager.getSubscription(); const subscription = await registration?.pushManager.getSubscription();
if (!subscription) return; if (!subscription) return;
await savePushSubscription(subscription, effectiveProvince, language, { await savePushSubscription(subscription, effectiveProvince, language, {
region: selectedRegion, region: selectedRegion,
officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled, officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled,
morningBriefEnabled: enabled, morningBriefEnabled: preferences.morningBriefNotificationsEnabled,
tomorrowBriefEnabled, tomorrowBriefEnabled: preferences.tomorrowBriefNotificationsEnabled,
latitude: notificationLatitude, latitude: notificationLatitude,
longitude: notificationLongitude, longitude: notificationLongitude,
locationName: notificationLocationName, locationName: notificationLocationName,
@@ -453,14 +454,15 @@ export function SettingsPage() {
setTomorrowBriefEnabled(enabled); setTomorrowBriefEnabled(enabled);
if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return; if (!notificationsEnabled || !canUsePush || notificationStatus !== "granted") return;
try { try {
const preferences = useWeatherStore.getState();
const registration = await navigator.serviceWorker?.getRegistration(); const registration = await navigator.serviceWorker?.getRegistration();
const subscription = await registration?.pushManager.getSubscription(); const subscription = await registration?.pushManager.getSubscription();
if (!subscription) return; if (!subscription) return;
await savePushSubscription(subscription, effectiveProvince, language, { await savePushSubscription(subscription, effectiveProvince, language, {
region: selectedRegion, region: selectedRegion,
officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled, officialWarningsEnabled: canUseOfficialWarnings && notificationsEnabled,
morningBriefEnabled, morningBriefEnabled: preferences.morningBriefNotificationsEnabled,
tomorrowBriefEnabled: enabled, tomorrowBriefEnabled: preferences.tomorrowBriefNotificationsEnabled,
latitude: notificationLatitude, latitude: notificationLatitude,
longitude: notificationLongitude, longitude: notificationLongitude,
locationName: notificationLocationName, locationName: notificationLocationName,

View File

@@ -102,6 +102,15 @@ Worker:
Endpointy harmonogramu nie uruchamiają się same bez workera albo zewnętrznego crona. 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 ## Zewnętrzny Cron
Jeśli nie używasz `npm run notifications:worker`, zewnętrzny cron powinien wywoływać: Jeśli nie używasz `npm run notifications:worker`, zewnętrzny cron powinien wywoływać: