feat: Active Push-Notification Subscriptions for Climate Threats (#72)#275
Open
arcgod-design wants to merge 1 commit into
Open
Conversation
…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/.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
/weatherrisk 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.pyinstance/climate_shield.db(auto-created, schema idempotent). Fields:email,city,state(optional),country,management_token(256-bitsecrets.token_hex),subscribed_at,last_alert_sent_at,last_alert_risks(JSON),last_alert_summary,alerts_sentcounter,is_active(soft delete).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. OptionalADMIN_TOKENenv gate (X-Admin-Tokenheader or?admin_token=).EmailBackendinterface withSMTPEmailBackend(SendGrid SMTP relay,smtp.sendgrid.net:587, STARTTLS) andLoggingEmailBackend(stdout).get_email_backend()picks SMTP only whenSENDGRID_SMTP_USER+SENDGRID_SMTP_PASSare set — so dev / tests never accidentally touch SMTP.threading.Threadstarted lazily by abefore_requesthook. Interval default43200s(12h);SUBSCRIPTION_SWEEP_INTERVAL_SECONDSenv override. Per-subscriber throttleSUBSCRIPTION_MIN_NOTIFY_GAP_SECONDS(default 6h) prevents email spam across sweep runs.alertsystem.get_weather_insights()inside a Flask test-request context rather than duplicating the threshold logic; thresholds are pulled lazily fromalertsystem.FLOOD_RISK_THRESHOLDetc. with safe fallback constants.register_on(app)— single entry point wired at the top ofalertsystem.py. The hook is short-circuited whenapp.config["TESTING"]so tests stay synchronous.Frontend —
Frontend/weather-alerts/weather-alerts.css— glassmorphism card using the repo's existing--panel,--accent,--border-colorvariables so it inherits the light/dark theme automatically. Honoursprefers-reduced-motion. Adds a.theme-lightoverride.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 thereadJsonSafepattern 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 leakingUnexpected token '<'.Frontend/index.html(between#featuresand footer) andFrontend/Analysis/analysis.html(after the main report grid).Tests —
backend/test_subscriptions.py(35 cases)TestSubscriberStore(6)TestEmailBackends(4)get_email_backendselects SMTP when creds present and falls back to logging otherwiseTestSummariseRisks(5)>=, missing fields, garbage values raiseTestValidation(9 cases, parametrised)TestRESTEndpoints(4)TestSweepEndpoint(7)last_alert_sent_at, second sweep within throttle is skipped, weather-fetch failure surfaces in result,ADMIN_TOKENenv gate 403s without header and 200s with it,/testendpoint 404s on unknown token and evaluates current weather bypass-throttle on a known onePlus existing
backend/test_alertsystem.py— 2 GIS fallback tests still pass. Total: 37 passed in 1.62 seconds.Operational notes
instance/climate_shield.db(auto-created byos.makedirs). Override withCLIMATE_SHIELD_DB_PATHenv var. Tracked in.gitignorealong with*.dband.pytest_cache/.POST https://<app>/admin/subscriptions/sweepevery 12h. SetADMIN_TOKENand pass it asX-Admin-Tokenif you want to lock down the endpoint.SENDGRID_SMTP_USER=apikeyandSENDGRID_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.fromaddress — overridable withMAIL_FROMenv (defaultClimate Shield <no-reply@climate-shield.local>).Files changed
🤖 Generated with opencode