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:
@@ -15,6 +15,12 @@ class NotificationSettingsResponse(BaseModel):
|
||||
ntfy_enabled: bool = False
|
||||
rss_enabled: bool = False
|
||||
rss_token: Optional[str] = None
|
||||
# Digest
|
||||
digest_enabled: bool = False
|
||||
digest_frequency: str = "daily" # daily | weekly
|
||||
# Quiet hours (UTC hour integers 0-23, None = disabled)
|
||||
quiet_hours_start: Optional[int] = None
|
||||
quiet_hours_end: Optional[int] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
@@ -27,6 +33,21 @@ class NotificationSettingsUpdate(BaseModel):
|
||||
ntfy_password: Optional[str] = None
|
||||
ntfy_enabled: Optional[bool] = None
|
||||
rss_enabled: Optional[bool] = None
|
||||
digest_enabled: Optional[bool] = None
|
||||
digest_frequency: Optional[str] = None
|
||||
quiet_hours_start: Optional[int] = None
|
||||
quiet_hours_end: Optional[int] = None
|
||||
|
||||
|
||||
class NotificationEventSchema(BaseModel):
|
||||
id: int
|
||||
bill_id: str
|
||||
event_type: str
|
||||
payload: Optional[Any] = None
|
||||
dispatched_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class NtfyTestRequest(BaseModel):
|
||||
@@ -37,6 +58,11 @@ class NtfyTestRequest(BaseModel):
|
||||
ntfy_password: str = ""
|
||||
|
||||
|
||||
class FollowModeTestRequest(BaseModel):
|
||||
mode: str # pocket_veto | pocket_boost
|
||||
event_type: str # new_document | new_amendment | bill_updated
|
||||
|
||||
|
||||
class NotificationTestResult(BaseModel):
|
||||
status: str # "ok" | "error"
|
||||
detail: str
|
||||
@@ -198,11 +224,16 @@ class FollowSchema(BaseModel):
|
||||
user_id: int
|
||||
follow_type: str
|
||||
follow_value: str
|
||||
follow_mode: str = "neutral"
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class FollowModeUpdate(BaseModel):
|
||||
follow_mode: str
|
||||
|
||||
|
||||
# ── Settings ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user