notification working

This commit is contained in:
2026-04-20 15:23:28 +05:30
parent 93dbf2023c
commit 237ba6b3c1
12 changed files with 523 additions and 86 deletions

View File

@@ -49,79 +49,109 @@ def init_firebase():
def send_reminder_notifications():
"""Check all users and send reminders where due."""
if not _firebase_initialized:
log.warning("Reminder check skipped — Firebase not initialized")
return
db = get_database()
now_utc = datetime.utcnow().replace(second=0, microsecond=0)
# Find all users with reminder enabled and at least one FCM token
users = db.users.find({
candidates = list(db.users.find({
"reminder.enabled": True,
"fcmTokens": {"$exists": True, "$not": {"$size": 0}},
"reminder.time": {"$exists": True},
})
}))
for user in users:
log.debug(f"Reminder check at {now_utc.strftime('%H:%M')} UTC — {len(candidates)} candidate(s)")
for user in candidates:
try:
_process_user(db, user, now_utc)
if user.get("reminder", {}).get("time"):
_process_user(db, user, now_utc)
_process_universal(db, user, now_utc)
except Exception as e:
log.error(f"Error processing reminder for user {user.get('_id')}: {e}")
def _get_user_local_time(now_utc: datetime, timezone_str: str):
"""Returns (now_local, today_str, user_tz)."""
try:
user_tz = pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
user_tz = pytz.utc
now_local = now_utc.replace(tzinfo=pytz.utc).astimezone(user_tz)
today_str = now_local.strftime("%Y-%m-%d")
return now_local, today_str, user_tz
def _wrote_today(db, user_id, now_local, user_tz) -> bool:
today_start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
today_start_utc = today_start_local.astimezone(pytz.utc).replace(tzinfo=None)
today_end_utc = today_start_utc + timedelta(days=1)
return db.entries.count_documents({
"userId": user_id,
"createdAt": {"$gte": today_start_utc, "$lt": today_end_utc},
}) > 0
def _process_user(db, user: dict, now_utc: datetime):
uid = user.get("_id")
reminder = user.get("reminder", {})
reminder_time_str = reminder.get("time") # "HH:MM"
reminder_time_str = reminder.get("time")
timezone_str = reminder.get("timezone", "UTC")
fcm_tokens: list = user.get("fcmTokens", [])
if not reminder_time_str or not fcm_tokens:
return
try:
user_tz = pytz.timezone(timezone_str)
except pytz.UnknownTimeZoneError:
user_tz = pytz.utc
# Current time in user's timezone
now_local = now_utc.replace(tzinfo=pytz.utc).astimezone(user_tz)
now_local, today_str, user_tz = _get_user_local_time(now_utc, timezone_str)
current_hm = now_local.strftime("%H:%M")
if current_hm != reminder_time_str:
return # Not the right minute
# Check if already notified today (in user's local date)
today_local_str = now_local.strftime("%Y-%m-%d")
last_notified = reminder.get("lastNotifiedDate", "")
if last_notified == today_local_str:
return # Already sent today
# Check if user has already written today (using createdAt in their timezone)
today_start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0)
today_start_utc = today_start_local.astimezone(pytz.utc).replace(tzinfo=None)
today_end_utc = today_start_utc + timedelta(days=1)
entry_count = db.entries.count_documents({
"userId": user["_id"],
"createdAt": {"$gte": today_start_utc, "$lt": today_end_utc},
})
if entry_count > 0:
# Already wrote today — mark notified to avoid repeated checks
db.users.update_one(
{"_id": user["_id"]},
{"$set": {"reminder.lastNotifiedDate": today_local_str}}
)
log.debug(f"User {uid}: skipped — current time {current_hm} != reminder time {reminder_time_str} ({timezone_str})")
return
# Send FCM notification
_send_push(user["_id"], fcm_tokens, db, today_local_str)
if _wrote_today(db, uid, now_local, user_tz):
log.debug(f"User {uid}: skipped — already wrote today")
return
log.info(f"User {uid}: sending reminder (time={reminder_time_str}, tz={timezone_str})")
_send_push(uid, fcm_tokens, db)
def _send_push(user_id, tokens: list, db, today_local_str: str):
def _process_universal(db, user: dict, now_utc: datetime):
"""Universal 11pm reminder — fires if enabled and no entry written today."""
uid = user.get("_id")
reminder = user.get("reminder", {})
timezone_str = reminder.get("timezone", "UTC")
fcm_tokens: list = user.get("fcmTokens", [])
if not fcm_tokens:
return
now_local, today_str, user_tz = _get_user_local_time(now_utc, timezone_str)
if now_local.strftime("%H:%M") != "23:00":
return
if reminder.get("lastUniversalDate") == today_str:
log.debug(f"User {uid}: universal reminder skipped — already sent today")
return
if _wrote_today(db, uid, now_local, user_tz):
log.debug(f"User {uid}: universal reminder skipped — already wrote today")
db.users.update_one({"_id": uid}, {"$set": {"reminder.lastUniversalDate": today_str}})
return
log.info(f"User {uid}: sending universal 11pm reminder (tz={timezone_str})")
_send_push(uid, fcm_tokens, db, universal=True)
db.users.update_one({"_id": uid}, {"$set": {"reminder.lastUniversalDate": today_str}})
def _send_push(user_id, tokens: list, db, universal: bool = False):
"""Send FCM multicast and prune stale tokens."""
title = "Last chance to journal today 🌙" if universal else "Time to journal 🌱"
message = messaging.MulticastMessage(
notification=messaging.Notification(
title="Time to journal 🌱",
title=title,
body="You haven't written today yet. Take a moment to reflect.",
),
tokens=tokens,
@@ -143,7 +173,6 @@ def _send_push(user_id, tokens: list, db, today_local_str: str):
response = messaging.send_each_for_multicast(message)
log.info(f"Reminder sent to user {user_id}: {response.success_count} ok, {response.failure_count} failed")
# Remove tokens that are no longer valid
stale_tokens = [
tokens[i] for i, r in enumerate(response.responses)
if not r.success and r.exception and "not-registered" in str(r.exception).lower()
@@ -155,12 +184,6 @@ def _send_push(user_id, tokens: list, db, today_local_str: str):
)
log.info(f"Removed {len(stale_tokens)} stale FCM tokens for user {user_id}")
# Mark today as notified
db.users.update_one(
{"_id": user_id},
{"$set": {"reminder.lastNotifiedDate": today_local_str}}
)
def start_scheduler() -> BackgroundScheduler:
"""Initialize Firebase and start the minute-by-minute scheduler."""