feat(notifications): add Click link to all ntfy alerts (test + real)
Test notification:
- Click header -> {PUBLIC_URL}/notifications so tapping the test opens the app
Real bill alerts (dispatcher):
- Title reformatted: "New Bill Text: HR 1234" (event type + bill identifier)
- Body: bill full name on first line, AI summary below (300 chars)
- Tags updated per event type (page, memo, siren) instead of generic scroll
- Click header was already set from bill_url; no change needed there
Authored-By: Jack Levy
This commit is contained in:
@@ -11,6 +11,7 @@ from fastapi.responses import Response
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.config import settings as app_settings
|
||||||
from app.core.dependencies import get_current_user
|
from app.core.dependencies import get_current_user
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models.notification import NotificationEvent
|
from app.models.notification import NotificationEvent
|
||||||
@@ -115,10 +116,12 @@ async def test_ntfy(
|
|||||||
if not url:
|
if not url:
|
||||||
return NotificationTestResult(status="error", detail="Topic URL is required")
|
return NotificationTestResult(status="error", detail="Topic URL is required")
|
||||||
|
|
||||||
|
base_url = (app_settings.PUBLIC_URL or app_settings.LOCAL_URL).rstrip("/")
|
||||||
headers: dict[str, str] = {
|
headers: dict[str, str] = {
|
||||||
"Title": "PocketVeto: Test Notification",
|
"Title": "PocketVeto: Test Notification",
|
||||||
"Priority": "default",
|
"Priority": "default",
|
||||||
"Tags": "white_check_mark",
|
"Tags": "white_check_mark",
|
||||||
|
"Click": f"{base_url}/notifications",
|
||||||
}
|
}
|
||||||
if body.ntfy_auth_method == "token" and body.ntfy_token.strip():
|
if body.ntfy_auth_method == "token" and body.ntfy_token.strip():
|
||||||
headers["Authorization"] = f"Bearer {body.ntfy_token.strip()}"
|
headers["Authorization"] = f"Bearer {body.ntfy_token.strip()}"
|
||||||
@@ -132,7 +135,10 @@ async def test_ntfy(
|
|||||||
async with httpx.AsyncClient(timeout=10) as client:
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
resp = await client.post(
|
resp = await client.post(
|
||||||
url,
|
url,
|
||||||
content="Your PocketVeto notification settings are working correctly.".encode("utf-8"),
|
content=(
|
||||||
|
"Your PocketVeto notification settings are working correctly. "
|
||||||
|
"Real alerts will link directly to the relevant bill page."
|
||||||
|
).encode("utf-8"),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
)
|
)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|||||||
@@ -22,11 +22,17 @@ logger = logging.getLogger(__name__)
|
|||||||
NTFY_TIMEOUT = 10
|
NTFY_TIMEOUT = 10
|
||||||
|
|
||||||
_EVENT_TITLES = {
|
_EVENT_TITLES = {
|
||||||
"new_document": "New Bill Text Published",
|
"new_document": "New Bill Text",
|
||||||
"new_amendment": "Amendment Filed",
|
"new_amendment": "Amendment Filed",
|
||||||
"bill_updated": "Bill Updated",
|
"bill_updated": "Bill Updated",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_EVENT_TAGS = {
|
||||||
|
"new_document": "page_facing_up",
|
||||||
|
"new_amendment": "memo",
|
||||||
|
"bill_updated": "rotating_light",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@celery_app.task(bind=True, name="app.workers.notification_dispatcher.dispatch_notifications")
|
@celery_app.task(bind=True, name="app.workers.notification_dispatcher.dispatch_notifications")
|
||||||
def dispatch_notifications(self):
|
def dispatch_notifications(self):
|
||||||
@@ -93,14 +99,22 @@ def _send_ntfy(
|
|||||||
payload = event.payload or {}
|
payload = event.payload or {}
|
||||||
bill_label = payload.get("bill_label", event.bill_id.upper())
|
bill_label = payload.get("bill_label", event.bill_id.upper())
|
||||||
bill_title = payload.get("bill_title", "")
|
bill_title = payload.get("bill_title", "")
|
||||||
message = f"{bill_label}: {bill_title}"
|
event_label = _EVENT_TITLES.get(event.event_type, "Bill Update")
|
||||||
|
|
||||||
|
# Title line: event type + bill identifier (e.g. "New Bill Text: HR 1234")
|
||||||
|
title = f"{event_label}: {bill_label}"
|
||||||
|
|
||||||
|
# Body: full bill name, then AI summary if available
|
||||||
|
lines = [bill_title] if bill_title else []
|
||||||
if payload.get("brief_summary"):
|
if payload.get("brief_summary"):
|
||||||
message += f"\n\n{payload['brief_summary'][:280]}"
|
lines.append("")
|
||||||
|
lines.append(payload["brief_summary"][:300])
|
||||||
|
message = "\n".join(lines) or bill_label
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Title": _EVENT_TITLES.get(event.event_type, "Bill Update"),
|
"Title": title,
|
||||||
"Priority": "default",
|
"Priority": "default",
|
||||||
"Tags": "scroll",
|
"Tags": _EVENT_TAGS.get(event.event_type, "bell"),
|
||||||
}
|
}
|
||||||
if payload.get("bill_url"):
|
if payload.get("bill_url"):
|
||||||
headers["Click"] = payload["bill_url"]
|
headers["Click"] = payload["bill_url"]
|
||||||
|
|||||||
Reference in New Issue
Block a user