From 34491f3a563a91b4de300a1a97c7bf43d8df3bd5 Mon Sep 17 00:00:00 2001 From: Giancarlo Erra Date: Tue, 10 Mar 2026 18:04:53 +0000 Subject: [PATCH] fix: accept both GET and POST for cron trigger endpoint cron-job.org sends GET by default. Using app.get + app.post ensures the trigger works regardless of which HTTP method the cron service uses. Also renames "Automated Data Collection" to "Automated Weather Refresh" in README and fixes Email Notifications section references. --- README.md | 8 ++++---- server/index.js | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 36c13e0..22bb593 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,7 @@ If you prefer to manage the token outside the UI, set `CRON_TRIGGER_TOKEN` in yo A cron service calls the trigger endpoint on a schedule to pre-fetch fresh data before you open the dashboard. Without it the app still works — it downloads on demand when you visit — but you wait a few seconds. With it, data is always ready instantly. - **Service**: any free HTTP cron service (e.g. [cron-job.org](https://cron-job.org), GitHub Actions scheduled workflows, Render cron jobs) -- **Endpoint**: `/api/weather/refresh/trigger?key=` (shown in Settings after you generate the key) +- **Endpoint**: `/api/weather/refresh/trigger?key=` (shown in Settings after you generate the key) - **Key management**: generate, rotate, or revoke from **Settings → Automated Weather Refresh** (or set `CRON_TRIGGER_TOKEN` env var) - **Rate limit**: 3 requests/minute (separate from the manual refresh limit of 5/min) - **Recommended schedule**: once ~10:30 and once ~14:35 local time @@ -329,7 +329,7 @@ A cron service calls the trigger endpoint on a schedule to pre-fetch fresh data You can pair APD with an automation platform (e.g. [Make.com](https://make.com), [n8n](https://n8n.io), Zapier) to send an email digest after each scheduled refresh: - **Trigger**: schedule a second HTTP call ~5 minutes after the cron refresh -- **Content**: fetch the forecast from `/api/weather/refresh/trigger?key=` (shown in Settings after you generate the key) +- **Content**: fetch the forecast from `/api/weather/refresh/trigger?key=` (shown in Settings after you generate the key) - **Smart Email**: construct it from the `astrophotography_score` field to highlight clear nights (simple AI can be very effective here — e.g. "Tonight looks excellent for astrophotography with only 10% cloud cover, while tomorrow is poor with 80% clouds.") --- @@ -397,7 +397,7 @@ The app uses a single **master password** for access control. |----------|---------| | `GET /api/health` | Health check — also confirms Redis connectivity | | `GET /api/weather/summary` | LLM-friendly weather forecast JSON (for AI integrations) | -| `GET /api/weather/refresh/trigger?key=…` | Cron trigger — token-authenticated weather refresh (see Settings) | +| `GET/POST /api/weather/refresh/trigger?key=…` | Cron trigger — token-authenticated weather refresh (see Settings) | | `GET /api/auth/check` | Check authentication status | | `POST /api/auth/login` | Login with master password | | `POST /api/auth/logout` | Clear session | @@ -565,7 +565,7 @@ Both the weather dashboard and sky dashboard share a single observer location st |--------|----------|------|-------------| | `GET` | `/api/weather` | Yes | Cached weather data (auto-downloads if stale) | | `POST` | `/api/weather/refresh` | Yes | Force fresh download (manual/UI use) | -| `GET` | `/api/weather/refresh/trigger?key=…` | **Token** | Cron trigger — public, token-authenticated (3 req/min) | +| `GET/POST` | `/api/weather/refresh/trigger?key=…` | **Token** | Cron trigger — public, token-authenticated (3 req/min) | | `GET` | `/api/weather/status` | Yes | Download status only | | `GET` | `/api/weather/metoffice` | Yes | Cached Met Office data | | `GET` | `/api/weather/summary` | **No** | LLM-friendly processed forecast | diff --git a/server/index.js b/server/index.js index 48f7f9d..f4932fe 100644 --- a/server/index.js +++ b/server/index.js @@ -297,7 +297,7 @@ async function getCronTokenData() { return null; } -app.get("/api/weather/refresh/trigger", triggerLimiter, async (req, res) => { +const triggerHandler = [triggerLimiter, async (req, res) => { const key = typeof req.query.key === "string" ? req.query.key : ""; if (!key) { return res.status(401).json({ error: "Missing trigger key" }); @@ -335,7 +335,9 @@ app.get("/api/weather/refresh/trigger", triggerLimiter, async (req, res) => { console.error("Cron trigger refresh error:", error); res.status(500).json({ error: "Refresh failed" }); } -}); +}]; +app.get("/api/weather/refresh/trigger", ...triggerHandler); +app.post("/api/weather/refresh/trigger", ...triggerHandler); // ===== PROTECTED ROUTES — everything below requires auth ===== app.use("/api/*", requireAuth);