Add cron container and per-household timezone for auto-reminders
CI / test-and-package (push) Successful in 36s
CI / test-and-package (push) Successful in 36s
- docker-compose.yml: meal-cron service loops send_reminders_cron.py every 60s - send_reminders_cron.py: checks each household's local time, sends ntfy reminders at 10:00 (lunch) and 16:00 (dinner), guarded against duplicates - models.py: timezone, last_lunch_reminder, last_dinner_reminder on households - meals.py: /admin/timezone route for admins to set household timezone - dashboard: timezone selector (20 common zones) in Account Settings - i18n: 5 new translation keys for timezone strings - Dockerfile: copy send_reminders_cron.py - tests: 4 new timezone tests (admin set, non-admin denied, default UTC, auth) - docs: updated AGENTS.md
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run by the cron container every ~60s to send meal reminders.
|
||||
|
||||
For each household, checks the current local time. If it's 10:00–10:04,
|
||||
sends lunch reminders; if 16:00–16:04, sends dinner reminders. A
|
||||
per-household sent-date guard prevents duplicate sends.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo, available_timezones
|
||||
|
||||
# Ensure we can import from the app directory
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from models import get_all_households, get_users_with_ntfy, update_reminder_sent, init_db
|
||||
from ntfy import send_meal_reminder
|
||||
|
||||
APP_BASE_URL = os.environ.get("APP_BASE_URL", "http://meal-tracker:5000")
|
||||
|
||||
# Reminder schedule (local time)
|
||||
REMINDERS = {
|
||||
"lunch": 10, # 10:00
|
||||
"dinner": 16, # 16:00
|
||||
}
|
||||
|
||||
|
||||
def _resolve_timezone(tz_str: str) -> ZoneInfo:
|
||||
"""Return a ZoneInfo for *tz_str*, falling back to UTC on error."""
|
||||
try:
|
||||
return ZoneInfo(tz_str)
|
||||
except Exception:
|
||||
return ZoneInfo("UTC")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
init_db() # ensure tables exist (first run before web container)
|
||||
now_utc = datetime.now(ZoneInfo("UTC"))
|
||||
sent = 0
|
||||
|
||||
for hh in get_all_households():
|
||||
tz = _resolve_timezone(hh.get("timezone", "UTC"))
|
||||
local_now = now_utc.astimezone(tz)
|
||||
today_str = local_now.strftime("%Y-%m-%d")
|
||||
hour = local_now.hour
|
||||
minute = local_now.minute
|
||||
|
||||
for meal_type, trigger_hour in REMINDERS.items():
|
||||
if hour != trigger_hour or minute >= 5:
|
||||
continue
|
||||
|
||||
last_col = f"last_{meal_type}_reminder"
|
||||
if hh.get(last_col) == today_str:
|
||||
continue # already sent today
|
||||
|
||||
users = get_users_with_ntfy(hh["id"])
|
||||
if not users:
|
||||
continue
|
||||
|
||||
for user in users:
|
||||
ok = send_meal_reminder(
|
||||
ntfy_url=user["ntfy_url"],
|
||||
ntfy_token=user["ntfy_token"],
|
||||
callback_token=user["callback_token"],
|
||||
meal_type=meal_type,
|
||||
for_date=local_now.date(),
|
||||
app_base_url=APP_BASE_URL,
|
||||
)
|
||||
if ok:
|
||||
print(f"[{local_now:%Y-%m-%d %H:%M}] sent {meal_type} → {user['username']}")
|
||||
sent += 1
|
||||
|
||||
update_reminder_sent(hh["id"], meal_type, today_str)
|
||||
|
||||
if sent:
|
||||
print(f"Sent {sent} reminder(s)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user