v1.5.0 Phase 2a: ntfy channel (#72) + per-location alert feeds (#75) #112

Merged
claude-bot merged 2 commits from feat/v1.5.0-phase2 into main 2026-07-19 14:53:26 +00:00
Contributor

Two independent "notification reach" features.

#72 — ntfy channel

Self-hosted push via ntfy, following the one-file-per-channel notifier pattern.

  • New ChannelType.ntfy + migration 0025 (ALTER TYPE ADD VALUE, per the 0021 pattern).
  • notifiers/ntfy.py: send_alert/send_alert_lifted POST the shared-formatter body to {server_url}/{topic} with Title/Priority/Tags headers; Bearer-token or Basic auth; severity→priority default. Raises Transient/Permanent delivery errors per the outbox contract (5xx/429/network → transient; other 4xx / missing topic → permanent). Non-ASCII header values are RFC-2047 encoded.
  • NtfyConfig schema (topic required, server_url http(s), priority 1-5) in the per-type validation; secret config keys redacted.
  • Wired into both dispatch chains; full web-form UI in channels.html (type option, config fieldset, payload builder, card icon 📣).
  • Like email/pushover/sms, ntfy carries NWS alerts + all-clears (the SPC/forecast/AI chains are discord+webhook-only by design, so this is consistent).
  • Web Push (VAPID + service worker) is intentionally left as a separate larger follow-on.

#75 — per-location alert feeds (JSON / RSS / iCal)

  • GET /p/{location_id}.json|.rss|.ics, defined before the /p/{token} catch-all (PublicToken alphabet has no dots → no collision). Gated by Location.public_enabled + public_rate_limit, mirroring the existing per-location public page and exposing the same active alert set.
  • services/feeds.py: stdlib-only RSS 2.0 + RFC-5545 iCal builders (XML escaping; iCal escaping + 75-char folding). No new dependencies.
  • Note: SentAlert has no onset/area_desc (those live on NWSAlertSnapshot), so those are null / DTSTART falls back to sent_at — a snapshot-join backfill is a possible follow-up (also relevant to the upcoming #84).

Testing (dev server)

ruff clean; 30 new feature tests pass; bulk suite 700 passed; migration 0025 applied on a restored copy of the real dev DB (ntfy enum value present).

Closes #72, #75

🤖 Generated with Claude Code

Two independent "notification reach" features. ## #72 — ntfy channel Self-hosted push via ntfy, following the one-file-per-channel notifier pattern. - New `ChannelType.ntfy` + migration **0025** (`ALTER TYPE ADD VALUE`, per the 0021 pattern). - `notifiers/ntfy.py`: `send_alert`/`send_alert_lifted` POST the shared-formatter body to `{server_url}/{topic}` with `Title`/`Priority`/`Tags` headers; Bearer-token or Basic auth; severity→priority default. Raises Transient/Permanent delivery errors per the outbox contract (5xx/429/network → transient; other 4xx / missing topic → permanent). Non-ASCII header values are RFC-2047 encoded. - `NtfyConfig` schema (topic required, `server_url` http(s), priority 1-5) in the per-type validation; secret config keys redacted. - Wired into both dispatch chains; full web-form UI in `channels.html` (type option, config fieldset, payload builder, card icon 📣). - Like email/pushover/sms, ntfy carries NWS alerts + all-clears (the SPC/forecast/AI chains are discord+webhook-only by design, so this is consistent). - **Web Push** (VAPID + service worker) is intentionally left as a separate larger follow-on. ## #75 — per-location alert feeds (JSON / RSS / iCal) - `GET /p/{location_id}.json|.rss|.ics`, defined before the `/p/{token}` catch-all (PublicToken alphabet has no dots → no collision). Gated by `Location.public_enabled` + `public_rate_limit`, mirroring the existing per-location public page and exposing the same active alert set. - `services/feeds.py`: stdlib-only RSS 2.0 + RFC-5545 iCal builders (XML escaping; iCal escaping + 75-char folding). No new dependencies. - Note: `SentAlert` has no `onset`/`area_desc` (those live on `NWSAlertSnapshot`), so those are null / `DTSTART` falls back to `sent_at` — a snapshot-join backfill is a possible follow-up (also relevant to the upcoming #84). ## Testing (dev server) ruff clean; 30 new feature tests pass; bulk suite **700 passed**; migration 0025 applied on a **restored copy of the real dev DB** (ntfy enum value present). Closes #72, #75 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Self-hosted push via ntfy, following the one-file-per-channel notifier pattern.

- New ChannelType.ntfy + migration 0025 (ALTER TYPE ADD VALUE, per the 0021
  pattern / env.py single-transaction note).
- app/services/notifiers/ntfy.py: send_alert / send_alert_lifted POST the shared-
  formatter message body to {server_url}/{topic} with Title/Priority/Tags headers;
  Bearer-token or Basic auth; severity→priority default (advisory 3/watch 4/warn 5).
  Raises Transient/Permanent delivery errors per the outbox contract (5xx/429/network
  → transient, other 4xx / missing topic → permanent). Non-ASCII header values are
  RFC-2047 encoded.
- NtfyConfig schema (topic required, server_url http(s), priority 1-5) wired into the
  per-type config validation; topic/token/credentials treated as secret config.
- Wired into both dispatch chains (send_alert, send_alert_lifted). Like email/
  pushover/sms, ntfy carries NWS alerts + all-clears (the SPC/forecast/AI chains are
  discord+webhook-only by design).
- channels.html: type option, #config-ntfy fieldset, payload builder, card icon.

Web Push (VAPID + service worker + subscription mgmt) remains a separate larger
follow-on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add per-location alert feeds: JSON, RSS, iCal (#75)
All checks were successful
CI / test (pull_request) Successful in 4m26s
ae4538a592
Public locations are now consumable by homelab automations (Home Assistant, etc.).

- GET /p/{location_id}.json|.rss|.ics in public.py, defined before the /p/{token}
  catch-all so the extensions resolve first (PublicToken alphabet has no dots, so no
  collision). Gated by Location.public_enabled + the public_rate_limit dependency,
  mirroring the existing /api/locations/{id}/public page and exposing the same active
  dashboard-record alert set.
- app/services/feeds.py: stdlib-only RSS 2.0 and RFC-5545 iCal builders (proper XML
  escaping; iCal text escaping + 75-char line folding). No new dependencies.

Note: SentAlert has no onset/area_desc columns (those live on NWSAlertSnapshot), so
those fields are null / DTSTART falls back to sent_at — a snapshot-join backfill is a
possible follow-up.

Full suite green on the dev server: 700 passed; migration 0025 verified on a real-DB
copy.

Closes #72, #75

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude-bot deleted branch feat/v1.5.0-phase2 2026-07-19 14:53:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/WeatherBot!112
No description provided.