Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**: `<your-host>/api/weather/refresh/trigger?key=<your-key>` (shown in Settings after you generate the key)
- **Endpoint**: `<your-app-url>/api/weather/refresh/trigger?key=<your-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
Expand All @@ -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 `<your-host>/api/weather/refresh/trigger?key=<your-key>` (shown in Settings after you generate the key)
- **Content**: fetch the forecast from `<your-app-url>/api/weather/refresh/trigger?key=<your-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.")

---
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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 |
Expand Down
6 changes: 4 additions & 2 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand Down Expand Up @@ -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);
Expand Down