Add ntfy push notifications with action buttons for meal reminders
CI / test-and-package (push) Successful in 37s

- ntfy.py: send_meal_reminder() with Home/Out HTTP action buttons, test helper
- models.py: new columns ntfy_url, ntfy_token, callback_token on users table
- meals.py: /api/ntfy-callback (public, token-authenticated), /ntfy-settings, /ntfy-test
- app.py: flask send-reminders CLI command (cron-schedulable)
- Dashboard: ntfy config form in Account Settings
- i18n: translations for all ntfy strings (en/fr/de)
- Dockerfile: include ntfy.py in COPY
- tests: 15 new tests covering callback, settings, and auth
- docs: updated AGENTS.md and doc/index.md
This commit is contained in:
agentbox
2026-08-01 06:17:26 +00:00
parent 7bf00ee2e3
commit 3681f9dfce
10 changed files with 650 additions and 4 deletions
+52
View File
@@ -69,6 +69,12 @@ def init_db() -> None:
if "household_id" not in cols:
db.execute("ALTER TABLE users ADD COLUMN household_id INTEGER REFERENCES households(id)")
db.execute("CREATE INDEX IF NOT EXISTS idx_users_household ON users(household_id)")
if "ntfy_url" not in cols:
db.execute("ALTER TABLE users ADD COLUMN ntfy_url TEXT DEFAULT ''")
if "ntfy_token" not in cols:
db.execute("ALTER TABLE users ADD COLUMN ntfy_token TEXT DEFAULT ''")
if "callback_token" not in cols:
db.execute("ALTER TABLE users ADD COLUMN callback_token TEXT DEFAULT ''")
# ── Households ────────────────────────────────────────────────────────────────
@@ -174,6 +180,52 @@ def get_household_users(household_id: int) -> list[dict]:
return [dict(r) for r in rows]
# ── Ntfy / notifications ─────────────────────────────────────────────────────
def set_user_ntfy(user_id: int, ntfy_url: str, ntfy_token: str,
callback_token: str) -> None:
"""Save ntfy settings for a user."""
with get_db() as db:
db.execute(
"UPDATE users SET ntfy_url = ?, ntfy_token = ?, callback_token = ? "
"WHERE id = ?",
(ntfy_url, ntfy_token, callback_token, user_id),
)
def get_user_ntfy(user_id: int) -> dict:
"""Get ntfy settings for a user."""
with get_db() as db:
row = db.execute(
"SELECT ntfy_url, ntfy_token, callback_token FROM users WHERE id = ?",
(user_id,),
).fetchone()
return dict(row) if row else {}
def get_user_by_callback_token(token: str) -> dict | None:
"""Look up a user by their ntfy callback token."""
with get_db() as db:
row = db.execute(
"SELECT id, username, household_id FROM users WHERE callback_token = ?",
(token,),
).fetchone()
return dict(row) if row else None
def get_users_with_ntfy(household_id: int) -> list[dict]:
"""Return all users in a household who have ntfy configured."""
with get_db() as db:
rows = db.execute(
"SELECT id, username, ntfy_url, ntfy_token, callback_token "
"FROM users WHERE household_id = ? "
"AND ntfy_url != '' AND callback_token != '' "
"ORDER BY username",
(household_id,),
).fetchall()
return [dict(r) for r in rows]
# ── Meal periods ──────────────────────────────────────────────────────────────
def ensure_meal_periods(for_date: date) -> list[dict]: