feat: replace Web Push with ntfy for medication reminders

Single notification path via ntfy HTTP publish for reliable Android delivery;
remove VAPID, push subscriptions, and SW push handlers. PWA settings show
topic subscribe link; humor texts and deep-link actions unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-06 10:42:58 +02:00
parent 24ac7f2f48
commit 1cada42370
17 changed files with 251 additions and 323 deletions
+12 -17
View File
@@ -16,7 +16,6 @@ from server.auth import create_token, require_auth, verify_pin
from server.config_loader import load_meds_config, slot_to_dict, today_str
from server.db import ensure_schema, get_db, new_id, utc_now_iso
from server.messages import pick
from server.push import save_subscription, send_slot_reminder
from server.scheduler import schedule_snooze, start_scheduler
from server.settings import settings
from server.slots import build_today
@@ -73,9 +72,18 @@ async def auth_pin(payload: dict[str, Any]) -> JSONResponse:
return JSONResponse({"token": create_token()}, headers=NO_STORE)
@app.get("/api/vapid-public-key")
async def vapid_public_key() -> JSONResponse:
return JSONResponse({"key": settings.vapid_public_key}, headers=NO_STORE)
@app.get("/api/notify-config")
async def notify_config(_: dict = Depends(require_auth)) -> JSONResponse:
base = settings.ntfy_url.rstrip("/") if settings.ntfy_url else ""
topic = settings.ntfy_topic
return JSONResponse(
{
"topic": topic,
"subscribe_url": f"{base}/{topic}" if base and topic else "",
"configured": bool(base and topic),
},
headers=NO_STORE,
)
@app.get("/api/config")
@@ -166,19 +174,6 @@ async def get_stats(_: dict = Depends(require_auth)) -> JSONResponse:
return JSONResponse(stats, headers=NO_STORE)
@app.post("/api/push/subscribe")
async def push_subscribe(payload: dict[str, Any], _: dict = Depends(require_auth)) -> JSONResponse:
sub = payload.get("subscription")
if not sub or not sub.get("endpoint"):
raise HTTPException(status_code=400, detail="Invalid subscription")
db = await get_db()
try:
await save_subscription(db, sub)
finally:
await db.close()
return JSONResponse({"ok": True}, headers=NO_STORE)
@app.post("/api/snooze")
async def post_snooze(payload: dict[str, Any], _: dict = Depends(require_auth)) -> JSONResponse:
slot_id = str(payload.get("slot_id", ""))