diff --git a/remarkbox/scripts/send_node_digest_notifications.py b/remarkbox/scripts/send_node_digest_notifications.py index cf61eec..e99e851 100644 --- a/remarkbox/scripts/send_node_digest_notifications.py +++ b/remarkbox/scripts/send_node_digest_notifications.py @@ -14,7 +14,7 @@ import logging import transaction from pyramid.paster import bootstrap, setup_logging -from ..lib.notify import deliver_scheduled_notifications +from ..lib.notify import get_email_notifications, send_digest_notifications, filter_orphaned_notifications from ..models import get_tm_session from ..models.notification import NodeEventNotification from ..models.meta import now_timestamp @@ -55,6 +55,32 @@ def should_have_been_sent(notification): return False +def safe_deliver_scheduled_notifications(request, dbsession): + """ + Deliver scheduled notifications with protection against orphaned notifications. + """ + from datetime import datetime + + # Temporarily replace request.dbsession with our transaction-managed session + old_dbsession = request.dbsession + request.dbsession = dbsession + + try: + # Get daily notifications and filter out orphaned ones + notification_dict = get_email_notifications(dbsession, "daily") + filtered_dict = filter_orphaned_notifications(notification_dict) + send_digest_notifications(request, filtered_dict, "daily") + + # Send weekly on Monday + if datetime.today().weekday() == 0: + notification_dict = get_email_notifications(dbsession, "weekly") + filtered_dict = filter_orphaned_notifications(notification_dict) + send_digest_notifications(request, filtered_dict, "weekly") + finally: + # Restore original dbsession + request.dbsession = old_dbsession + + def get_unsent_notification_count(request): """ Get count of unsent notifications using a fresh database session. @@ -77,6 +103,7 @@ def get_unsent_notification_count(request): def mark_ready_notifications_as_sent(request): """ Mark notifications as sent if they were ready to be delivered based on frequency rules. + Only marks non-orphaned notifications (those with valid node_event). Args: request: Pyramid request object @@ -88,36 +115,42 @@ def mark_ready_notifications_as_sent(request): dbsession = get_tm_session( request.registry["dbsession_factory"], transaction.manager ) - + # Get all unsent notifications unsent_notifications = dbsession.query(NodeEventNotification).filter( NodeEventNotification.sent == False ).all() - + total_unsent = len(unsent_notifications) - + if total_unsent == 0: return total_unsent, 0, 0 - + # Only mark notifications that should have been sent based on frequency + # and are not orphaned (have valid node_event) ready_notification_ids = [ - n.id for n in unsent_notifications - if should_have_been_sent(n) + n.id for n in unsent_notifications + if should_have_been_sent(n) and n.node_event is not None ] - + + # Count orphaned notifications separately + orphaned_count = sum(1 for n in unsent_notifications if n.node_event is None) + if orphaned_count > 0: + log.warning(f"Found {orphaned_count} orphaned notifications that will not be marked as sent") + marked_as_sent = 0 if ready_notification_ids: marked_as_sent = dbsession.query(NodeEventNotification).filter( NodeEventNotification.id.in_(ready_notification_ids) ).update({ - 'sent': True, + 'sent': True, 'updated_timestamp': now_timestamp() }, synchronize_session=False) - + # Ensure changes are persisted dbsession.flush() transaction.commit() - + left_for_later = total_unsent - marked_as_sent return total_unsent, marked_as_sent, left_for_later @@ -142,10 +175,14 @@ def main(): log.info("No notifications to process") return - # Deliver scheduled notifications + # Deliver scheduled notifications with orphaned notification protection log.info("Delivering scheduled notifications...") - deliver_scheduled_notifications(request) - transaction.commit() + with transaction.manager: + dbsession = get_tm_session( + request.registry["dbsession_factory"], transaction.manager + ) + safe_deliver_scheduled_notifications(request, dbsession) + transaction.commit() # Check final state and mark ready notifications as sent log.info("Checking for notifications that need to be marked as sent...")