Files
PocketVeto/backend/app/models/notification.py
Jack Levy 2e2fefb795 feat: per-user notifications (ntfy + RSS), deduplicated actions, backfill task
Notifications:
- New /notifications page accessible to all users (ntfy + RSS config)
- ntfy now supports no-auth, Bearer token, and HTTP Basic auth (for ACL-protected self-hosted servers)
- RSS enabled/disabled independently of ntfy; token auto-generated on first GET
- Notification settings removed from admin-only Settings page; replaced with link card
- Sidebar adds Notifications nav link for all users
- notification_dispatcher.py: fan-out now marks RSS events dispatched independently

Action history:
- Migration 0012: deduplicates existing bill_actions rows and adds UNIQUE(bill_id, action_date, action_text)
- congress_poller.py: replaces existence-check inserts with ON CONFLICT DO NOTHING (race-condition safe)
- Added backfill_all_bill_actions task (no date filter) + admin endpoint POST /backfill-all-actions

Authored-By: Jack Levy
2026-03-01 12:04:13 -05:00

28 lines
1.1 KiB
Python

from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
class NotificationEvent(Base):
__tablename__ = "notification_events"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
bill_id = Column(String, ForeignKey("bills.bill_id", ondelete="CASCADE"), nullable=False)
# new_document | new_amendment | bill_updated
event_type = Column(String(50), nullable=False)
# {bill_title, bill_label, brief_summary, bill_url}
payload = Column(JSONB)
created_at = Column(DateTime(timezone=True), server_default=func.now())
dispatched_at = Column(DateTime(timezone=True), nullable=True)
user = relationship("User", back_populates="notification_events")
__table_args__ = (
Index("ix_notification_events_user_id", "user_id"),
Index("ix_notification_events_dispatched_at", "dispatched_at"),
)