Skip to content

feat: Active Push-Notification Subscriptions for Climate Threats (#72)#275

Open
arcgod-design wants to merge 1 commit into
thetechguardians:mainfrom
arcgod-design:feat/issue-72-active-alert-subscriptions
Open

feat: Active Push-Notification Subscriptions for Climate Threats (#72)#275
arcgod-design wants to merge 1 commit into
thetechguardians:mainfrom
arcgod-design:feat/issue-72-active-alert-subscriptions

Conversation

@arcgod-design

Copy link
Copy Markdown

Active Push-Notification Subscriptions for Climate Threats

Closes #72.

Summary

Adds an end-to-end subscription pipeline that lets a visitor subscribe their email + location to twice-daily climate-threat alerts. When the existing /weather risk engine detects a high flood / heat / wildfire / cyclone / drought risk for the subscriber's city, an email is sent through a pluggable backend (SendGrid SMTP relay or stdout fallback). Subscribers can self-manage via a 256-bit management token.

Backend — backend/subscriptions.py

  • SubscriberStore — SQLite DAO at instance/climate_shield.db (auto-created, schema idempotent). Fields: email, city, state (optional), country, management_token (256-bit secrets.token_hex), subscribed_at, last_alert_sent_at, last_alert_risks (JSON), last_alert_summary, alerts_sent counter, is_active (soft delete).
  • REST API (Flask blueprint, registered lazily on app):
    • POST /subscriptions — validates email regex + length, lowercases email. Returns 201 with the management token.
    • GET /subscriptions/<token> — fetch one's own record by token.
    • DELETE /subscriptions/<token> — soft-delete (is_active = 0); token is kept so the user can verify they're unsubscribed.
    • POST /subscriptions/<token>/test — re-evaluate weather right now, bypasses the throttle so users can verify alerts are arriving.
    • POST|GET /admin/subscriptions/sweep — manual sweep. Optional ADMIN_TOKEN env gate (X-Admin-Token header or ?admin_token=).
  • Email backendsEmailBackend interface with SMTPEmailBackend (SendGrid SMTP relay, smtp.sendgrid.net:587, STARTTLS) and LoggingEmailBackend (stdout). get_email_backend() picks SMTP only when SENDGRID_SMTP_USER + SENDGRID_SMTP_PASS are set — so dev / tests never accidentally touch SMTP.
  • SubscriptionSweeper — daemon threading.Thread started lazily by a before_request hook. Interval default 43200s (12h); SUBSCRIPTION_SWEEP_INTERVAL_SECONDS env override. Per-subscriber throttle SUBSCRIPTION_MIN_NOTIFY_GAP_SECONDS (default 6h) prevents email spam across sweep runs.
  • Risk engine reuse — the sweeper calls alertsystem.get_weather_insights() inside a Flask test-request context rather than duplicating the threshold logic; thresholds are pulled lazily from alertsystem.FLOOD_RISK_THRESHOLD etc. with safe fallback constants.
  • register_on(app) — single entry point wired at the top of alertsystem.py. The hook is short-circuited when app.config["TESTING"] so tests stay synchronous.

Frontend — Frontend/weather-alerts/

  • weather-alerts.css — glassmorphism card using the repo's existing --panel, --accent, --border-color variables so it inherits the light/dark theme automatically. Honours prefers-reduced-motion. Adds a .theme-light override.
  • weather-alerts.js — vanilla JS, no dependencies, auto-mounts on any page where <div id="cs-alerts-root"> exists OR falls back to appending a <section> to <main class="container">. Detects the analysis subpath so the API base resolves correctly (/ vs ../). Uses the readJsonSafe pattern from EduFlow-AI FEATURE: Real-Time Weather Alert WebSocket System with SMS Fallback #122 so a 500-HTML response renders "Cannot reach the Climate Shield subscription service…" instead of leaking Unexpected token '<'.
  • Wired into Frontend/index.html (between #features and footer) and Frontend/Analysis/analysis.html (after the main report grid).

Tests — backend/test_subscriptions.py (35 cases)

Suite Coverage
TestSubscriberStore (6) schema idempotency, insert/retrieve, get-by-unknown, list-active filter, deactivate unknown, record-alert counter + JSON risks round-trip
TestEmailBackends (4) logging captures, SMTP-no-creds returns ok=False, get_email_backend selects SMTP when creds present and falls back to logging otherwise
TestSummariseRisks (5) empty risks, multiple high, threshold-edge >=, missing fields, garbage values raise
TestValidation (9 cases, parametrised) email / city / state / country rejected on missing/empty/long/garbage; valid payload with optional state inserts; email lowercased; >254-char email rejected
TestRESTEndpoints (4) 404 on unknown token, fetch after create, soft-delete then GET shows is_active=0, delete-unknown 404s
TestSweepEndpoint (7) no subscribers returns empty summary, no-alerts path skips email, high-risk triggers email + records last_alert_sent_at, second sweep within throttle is skipped, weather-fetch failure surfaces in result, ADMIN_TOKEN env gate 403s without header and 200s with it, /test endpoint 404s on unknown token and evaluates current weather bypass-throttle on a known one

Plus existing backend/test_alertsystem.py2 GIS fallback tests still pass. Total: 37 passed in 1.62 seconds.

Operational notes

  • DB locationinstance/climate_shield.db (auto-created by os.makedirs). Override with CLIMATE_SHIELD_DB_PATH env var. Tracked in .gitignore along with *.db and .pytest_cache/.
  • Cron integration — for the Render free tier where background threads may be paused, deploy a cron service (UptimeRobot, GitHub Actions, or Render Cron Job) to POST https://<app>/admin/subscriptions/sweep every 12h. Set ADMIN_TOKEN and pass it as X-Admin-Token if you want to lock down the endpoint.
  • SendGrid setup — set SENDGRID_SMTP_USER=apikey and SENDGRID_SMTP_PASS=<sendgrid_api_key> in the Render environment. Without these, the sweeper "sends" via stdout (visible in Render logs) — useful for first-deploy smoke testing.
  • from address — overridable with MAIL_FROM env (default Climate Shield <no-reply@climate-shield.local>).

Files changed

.gitignore                                  |  4 +-
Frontend/Analysis/analysis.html             |  5 +
Frontend/index.html                        |  5 +
Frontend/weather-alerts/weather-alerts.css  | (new) glassmorphism card
Frontend/weather-alerts/weather-alerts.html | (new) reference markup
Frontend/weather-alerts/weather-alerts.js   | (new) vanilla JS auto-mount
backend/alertsystem.py                       |  6 +  (wire-in)
backend/subscriptions.py                     | (new) 540 lines
backend/test_subscriptions.py                | (new) 35 tests
requirements.txt                             | (new) flask + flask-cors + gunicorn

🤖 Generated with opencode

…te threats (thetechguardians#72)

Backend (backend/subscriptions.py):
- SQLite-backed SubscriberStore (instance/climate_shield.db) with management tokens (256-bit), soft-delete, per-subscriber throttle (MIN_NOTIFY_GAP_SECONDS=6h).
- REST endpoints: POST /subscriptions, GET/DELETE /subscriptions/<token>, POST /subscriptions/<token>/test (throttle-bypass), POST/GET /admin/subscriptions/sweep (optionally gated by ADMIN_TOKEN env).
- Email backend abstraction: SMTP via SendGrid SMTP relay when SENDGRID_SMTP_USER/PASS configured, else LoggingEmailBackend (stdout) so dev/test never tries SMTP.
- SubscriptionSweeper runs in a daemon thread, twice-daily default (SWEEP_INTERVAL_SECONDS=43200). Reuses alertsystem thresholds via lazy import. Sweeps call existing get_weather_insights() with a Flask test-request context so risk-detection logic stays in one place.
- register_on(app) attaches blueprint + lazy before_request hook (skipped in TESTING).

Frontend (Frontend/weather-alerts/*):
- Glassmorphism subscribe card, vanilla JS, auto-mounts on any page with main.container; picks up existing --panel/--accent theme vars + a .theme-light override.
- Reuses the readJsonSafe pattern from EduFlow-AI thetechguardians#122 so a 500-HTML response renders a friendly "Cannot reach the subscription service..." string instead of leaking the parser error.
- Wired into both index.html (above footer) and Analysis/analysis.html (after report grid).

Tests: backend/test_subscriptions.py -- 35 cases for store / email backends / risk summariser / payload validation / REST endpoints / manual sweep / admin-token gating / throttle bypass on /test. Plus existing backend/test_alertsystem.py -- 2 GIS tests. 37 passed.

Also: requirements.txt added (flask, flask-cors, requests, python-dotenv, gunicorn) so Render can install deps; .gitignore extended with instance/ + *.db + .pytest_cache/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEATURE:Active Push-Notification Subscriptions for Climate Threats

1 participant