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
230 lines
7.4 KiB
Python
230 lines
7.4 KiB
Python
from datetime import date, datetime
|
|
from typing import Any, Generic, Optional, TypeVar
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
# ── Notifications ──────────────────────────────────────────────────────────────
|
|
|
|
class NotificationSettingsResponse(BaseModel):
|
|
ntfy_topic_url: str = ""
|
|
ntfy_auth_method: str = "none" # none | token | basic
|
|
ntfy_token: str = ""
|
|
ntfy_username: str = ""
|
|
ntfy_password: str = ""
|
|
ntfy_enabled: bool = False
|
|
rss_enabled: bool = False
|
|
rss_token: Optional[str] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class NotificationSettingsUpdate(BaseModel):
|
|
ntfy_topic_url: Optional[str] = None
|
|
ntfy_auth_method: Optional[str] = None
|
|
ntfy_token: Optional[str] = None
|
|
ntfy_username: Optional[str] = None
|
|
ntfy_password: Optional[str] = None
|
|
ntfy_enabled: Optional[bool] = None
|
|
rss_enabled: Optional[bool] = None
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class PaginatedResponse(BaseModel, Generic[T]):
|
|
items: list[T]
|
|
total: int
|
|
page: int
|
|
per_page: int
|
|
pages: int
|
|
|
|
|
|
# ── Member ────────────────────────────────────────────────────────────────────
|
|
|
|
class MemberSchema(BaseModel):
|
|
bioguide_id: str
|
|
name: str
|
|
first_name: Optional[str] = None
|
|
last_name: Optional[str] = None
|
|
party: Optional[str] = None
|
|
state: Optional[str] = None
|
|
chamber: Optional[str] = None
|
|
district: Optional[str] = None
|
|
photo_url: Optional[str] = None
|
|
official_url: Optional[str] = None
|
|
congress_url: Optional[str] = None
|
|
birth_year: Optional[str] = None
|
|
address: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
terms_json: Optional[list[Any]] = None
|
|
leadership_json: Optional[list[Any]] = None
|
|
sponsored_count: Optional[int] = None
|
|
cosponsored_count: Optional[int] = None
|
|
latest_trend: Optional["MemberTrendScoreSchema"] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Bill Brief ────────────────────────────────────────────────────────────────
|
|
|
|
class BriefSchema(BaseModel):
|
|
id: int
|
|
brief_type: str = "full"
|
|
summary: Optional[str] = None
|
|
key_points: Optional[list[Any]] = None
|
|
risks: Optional[list[Any]] = None
|
|
deadlines: Optional[list[dict[str, Any]]] = None
|
|
topic_tags: Optional[list[str]] = None
|
|
llm_provider: Optional[str] = None
|
|
llm_model: Optional[str] = None
|
|
govinfo_url: Optional[str] = None
|
|
created_at: Optional[datetime] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Bill Action ───────────────────────────────────────────────────────────────
|
|
|
|
class BillActionSchema(BaseModel):
|
|
id: int
|
|
action_date: Optional[date] = None
|
|
action_text: Optional[str] = None
|
|
action_type: Optional[str] = None
|
|
chamber: Optional[str] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── News Article ──────────────────────────────────────────────────────────────
|
|
|
|
class NewsArticleSchema(BaseModel):
|
|
id: int
|
|
source: Optional[str] = None
|
|
headline: Optional[str] = None
|
|
url: Optional[str] = None
|
|
published_at: Optional[datetime] = None
|
|
relevance_score: Optional[float] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Trend Score ───────────────────────────────────────────────────────────────
|
|
|
|
class TrendScoreSchema(BaseModel):
|
|
score_date: date
|
|
newsapi_count: int
|
|
gnews_count: int
|
|
gtrends_score: float
|
|
composite_score: float
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class MemberTrendScoreSchema(BaseModel):
|
|
score_date: date
|
|
newsapi_count: int
|
|
gnews_count: int
|
|
gtrends_score: float
|
|
composite_score: float
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class MemberNewsArticleSchema(BaseModel):
|
|
id: int
|
|
source: Optional[str] = None
|
|
headline: Optional[str] = None
|
|
url: Optional[str] = None
|
|
published_at: Optional[datetime] = None
|
|
relevance_score: Optional[float] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Bill ──────────────────────────────────────────────────────────────────────
|
|
|
|
class BillSchema(BaseModel):
|
|
bill_id: str
|
|
congress_number: int
|
|
bill_type: str
|
|
bill_number: int
|
|
title: Optional[str] = None
|
|
short_title: Optional[str] = None
|
|
introduced_date: Optional[date] = None
|
|
latest_action_date: Optional[date] = None
|
|
latest_action_text: Optional[str] = None
|
|
status: Optional[str] = None
|
|
chamber: Optional[str] = None
|
|
congress_url: Optional[str] = None
|
|
sponsor: Optional[MemberSchema] = None
|
|
latest_brief: Optional[BriefSchema] = None
|
|
latest_trend: Optional[TrendScoreSchema] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class BillDetailSchema(BillSchema):
|
|
actions: list[BillActionSchema] = []
|
|
news_articles: list[NewsArticleSchema] = []
|
|
trend_scores: list[TrendScoreSchema] = []
|
|
briefs: list[BriefSchema] = []
|
|
has_document: bool = False
|
|
|
|
|
|
# ── Follow ────────────────────────────────────────────────────────────────────
|
|
|
|
class FollowCreate(BaseModel):
|
|
follow_type: str # bill | member | topic
|
|
follow_value: str
|
|
|
|
|
|
class FollowSchema(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
follow_type: str
|
|
follow_value: str
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Settings ──────────────────────────────────────────────────────────────────
|
|
|
|
# ── Auth ──────────────────────────────────────────────────────────────────────
|
|
|
|
class UserCreate(BaseModel):
|
|
email: str
|
|
password: str
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
id: int
|
|
email: str
|
|
is_admin: bool
|
|
notification_prefs: dict
|
|
created_at: Optional[datetime] = None
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class TokenResponse(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
user: "UserResponse"
|
|
|
|
|
|
# ── Settings ──────────────────────────────────────────────────────────────────
|
|
|
|
class SettingUpdate(BaseModel):
|
|
key: str
|
|
value: str
|
|
|
|
|
|
class SettingsResponse(BaseModel):
|
|
llm_provider: str
|
|
llm_model: str
|
|
congress_poll_interval_minutes: int
|
|
newsapi_enabled: bool
|
|
pytrends_enabled: bool
|