feat: add global weather support
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
This commit is contained in:
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
||||
|
||||
const DEFAULT_APP_URL = "http://127.0.0.1:3000";
|
||||
const DEFAULT_WARNING_INTERVAL_MINUTES = 5;
|
||||
const DEFAULT_BRIEF_INTERVAL_MINUTES = 5;
|
||||
const DEFAULT_MORNING_BRIEF_TIME = "07:00";
|
||||
const DEFAULT_TOMORROW_BRIEF_TIME = "18:00";
|
||||
const LOOP_INTERVAL_MS = 30_000;
|
||||
@@ -13,12 +14,13 @@ loadEnvFile(".env.local", true);
|
||||
const appUrl = normalizeAppUrl(process.env.WTR_APP_URL ?? process.env.NEXT_PUBLIC_APP_URL ?? DEFAULT_APP_URL);
|
||||
const cronSecret = process.env.NOTIFICATIONS_CRON_SECRET ?? "";
|
||||
const warningIntervalMinutes = readPositiveNumber(process.env.NOTIFICATIONS_WARNING_INTERVAL_MINUTES, DEFAULT_WARNING_INTERVAL_MINUTES);
|
||||
const briefIntervalMinutes = readPositiveNumber(process.env.NOTIFICATIONS_BRIEF_INTERVAL_MINUTES, DEFAULT_BRIEF_INTERVAL_MINUTES);
|
||||
const morningBriefTime = normalizeTime(process.env.NOTIFICATIONS_MORNING_BRIEF_TIME ?? DEFAULT_MORNING_BRIEF_TIME, DEFAULT_MORNING_BRIEF_TIME);
|
||||
const tomorrowBriefTime = normalizeTime(process.env.NOTIFICATIONS_TOMORROW_BRIEF_TIME ?? DEFAULT_TOMORROW_BRIEF_TIME, DEFAULT_TOMORROW_BRIEF_TIME);
|
||||
|
||||
let lastWarningCheckAt = 0;
|
||||
let lastMorningBriefDate = "";
|
||||
let lastTomorrowBriefDate = "";
|
||||
let lastMorningBriefCheckAt = 0;
|
||||
let lastTomorrowBriefCheckAt = 0;
|
||||
let isRunningTick = false;
|
||||
|
||||
function loadEnvFile(path, overrideLoadedValues) {
|
||||
@@ -50,27 +52,6 @@ function normalizeTime(value, fallback) {
|
||||
return /^\d{2}:\d{2}$/.test(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function getWarsawDateParts(date = new Date()) {
|
||||
const parts = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: "Europe/Warsaw",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(date);
|
||||
const part = (type) => parts.find((entry) => entry.type === type)?.value ?? "";
|
||||
return {
|
||||
dateKey: `${part("year")}-${part("month")}-${part("day")}`,
|
||||
time: `${part("hour")}:${part("minute")}`,
|
||||
};
|
||||
}
|
||||
|
||||
function isAtOrAfterTime(currentTime, targetTime) {
|
||||
return currentTime >= targetTime;
|
||||
}
|
||||
|
||||
async function callNotificationEndpoint(path) {
|
||||
const response = await fetch(`${appUrl}${path}`, {
|
||||
headers: cronSecret ? { authorization: `Bearer ${cronSecret}` } : {},
|
||||
@@ -106,6 +87,7 @@ async function tick() {
|
||||
isRunningTick = true;
|
||||
const now = Date.now();
|
||||
const warningIntervalMs = warningIntervalMinutes * 60_000;
|
||||
const briefIntervalMs = briefIntervalMinutes * 60_000;
|
||||
|
||||
try {
|
||||
if (now - lastWarningCheckAt >= warningIntervalMs) {
|
||||
@@ -113,15 +95,14 @@ async function tick() {
|
||||
await callNotificationEndpointSafely("/api/notifications/check");
|
||||
}
|
||||
|
||||
const warsawTime = getWarsawDateParts();
|
||||
if (isAtOrAfterTime(warsawTime.time, morningBriefTime) && lastMorningBriefDate !== warsawTime.dateKey) {
|
||||
const sentBrief = await callNotificationEndpointSafely("/api/notifications/daily-brief");
|
||||
if (sentBrief) lastMorningBriefDate = warsawTime.dateKey;
|
||||
if (now - lastMorningBriefCheckAt >= briefIntervalMs) {
|
||||
lastMorningBriefCheckAt = now;
|
||||
await callNotificationEndpointSafely("/api/notifications/daily-brief");
|
||||
}
|
||||
|
||||
if (isAtOrAfterTime(warsawTime.time, tomorrowBriefTime) && lastTomorrowBriefDate !== warsawTime.dateKey) {
|
||||
const sentBrief = await callNotificationEndpointSafely("/api/notifications/tomorrow-brief");
|
||||
if (sentBrief) lastTomorrowBriefDate = warsawTime.dateKey;
|
||||
if (now - lastTomorrowBriefCheckAt >= briefIntervalMs) {
|
||||
lastTomorrowBriefCheckAt = now;
|
||||
await callNotificationEndpointSafely("/api/notifications/tomorrow-brief");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(formatWorkerError(error));
|
||||
@@ -131,6 +112,6 @@ async function tick() {
|
||||
}
|
||||
|
||||
console.log(`wtr. notification worker: ${appUrl}`);
|
||||
console.log(`Warnings every ${warningIntervalMinutes} min, morning brief at ${morningBriefTime}, tomorrow brief at ${tomorrowBriefTime} Europe/Warsaw.`);
|
||||
console.log(`Warnings every ${warningIntervalMinutes} min. Brief checks every ${briefIntervalMinutes} min; targets: ${morningBriefTime} and ${tomorrowBriefTime} in each subscription timezone.`);
|
||||
void tick();
|
||||
setInterval(tick, LOOP_INTERVAL_MS);
|
||||
|
||||
Reference in New Issue
Block a user