feat(notifications): follow modes, milestone alerts, notification enhancements

Follow Modes (neutral / pocket_veto / pocket_boost):
- Alembic migration 0013 adds follow_mode column to follows table
- FollowButton rewritten as mode-aware dropdown for bills; simple toggle for members/topics
- PATCH /api/follows/{id}/mode endpoint with validation
- Dispatcher filters pocket_veto follows (suppress new_document/new_amendment events)
- Dispatcher adds ntfy Actions header for pocket_boost follows

Change-driven (milestone) Alerts:
- New notification_utils.py with shared emit helpers and 30-min dedup
- congress_poller emits bill_updated events on milestone action text
- llm_processor replaced with shared emit util (also notifies member/topic followers)

Notification Enhancements:
- ntfy priority levels (high for bill_updated, default for others)
- Quiet hours (UTC): dispatcher holds events outside allowed window
- Digest mode (daily/weekly): send_notification_digest Celery beat task
- Notification history endpoint + Recent Alerts UI section
- Enriched following page (bill titles, member photos/details via sub-components)
- Follow mode test buttons in admin settings panel

Infrastructure:
- nginx: switch upstream blocks to set $variable proxy_pass so Docker DNS
  re-resolves upstream IPs after container rebuilds (valid=10s)
- TROUBLESHOOTING.md documenting common Docker/nginx/postgres gotchas

Authored-By: Jack Levy
This commit is contained in:
Jack Levy
2026-03-01 15:09:13 -05:00
parent 22b205ff39
commit 73881b2404
21 changed files with 1412 additions and 250 deletions

View File

@@ -8,26 +8,20 @@ http {
sendfile on;
keepalive_timeout 65;
# Use Docker's internal DNS so upstream IPs re-resolve after container restarts
# Use Docker's internal DNS; valid=10s forces re-resolution after container restarts.
# Variables in proxy_pass activate this resolver (upstream blocks do not).
resolver 127.0.0.11 valid=10s ipv6=off;
upstream api {
server api:8000;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
server_name _;
client_max_body_size 10M;
# API
# API — variable forces re-resolution via resolver on each request cycle
location /api/ {
proxy_pass http://api;
set $api http://api:8000;
proxy_pass $api;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -38,14 +32,16 @@ http {
# Next.js static assets (long cache)
location /_next/static/ {
proxy_pass http://frontend;
set $frontend http://frontend:3000;
proxy_pass $frontend;
proxy_cache_valid 200 1d;
add_header Cache-Control "public, max-age=86400, immutable";
}
# Everything else → frontend
location / {
proxy_pass http://frontend;
set $frontend http://frontend:3000;
proxy_pass $frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;