"""Background scheduler that scans alerts and dispatches reminder emails.""" import logging from datetime import date, timezone from apscheduler.schedulers.background import BackgroundScheduler from .models import Alert, User, db from .mailer import send_mail logger = logging.getLogger(__name__) _scheduler: BackgroundScheduler | None = None def _check_and_send(app): """Iterate active alerts; for each reminder threshold reached, email all users.""" with app.app_context(): today = date.today() active_alerts = Alert.query.filter_by(is_active=True).all() recipients = [u.email for u in User.query.filter_by(is_active=True).all() if u.email] if not recipients: logger.info("Scheduler tick: no recipients.") return for alert in active_alerts: days_left = (alert.expiration_date - today).days already_sent = set(alert.reminders_sent) newly_sent = [] for threshold in alert.reminder_days: if days_left <= threshold and threshold not in already_sent: subject = f"[AlertHub] Reminder: {alert.title} expires in {days_left} day(s)" body_text = ( f"Alert: {alert.title}\n" f"Category: {alert.category or '—'}\n" f"Expiration date: {alert.expiration_date.isoformat()}\n" f"Days remaining: {days_left}\n\n" f"{alert.description or ''}\n" ) body_html = f"""

⏰ AlertHub Reminder

{alert.title}

Category: {alert.category or '—'}
Expiration date: {alert.expiration_date.isoformat()}
Days remaining: {days_left}

{alert.description or ''}

""" ok, info = send_mail(recipients, subject, body_text, body_html, kind="reminder", triggered_by="system") logger.info("Alert %s threshold %d: send=%s (%s)", alert.id, threshold, ok, info) if ok: newly_sent.append(threshold) if newly_sent: alert.reminders_sent = list(already_sent.union(newly_sent)) db.session.commit() def init_scheduler(app): """Start the scheduler if not already running. Called once at app startup.""" global _scheduler if _scheduler is not None and _scheduler.running: return _scheduler interval = max(1, int(app.config.get("SCHEDULER_INTERVAL_MINUTES", 60))) _scheduler = BackgroundScheduler(timezone="UTC") _scheduler.add_job( _check_and_send, "interval", minutes=interval, args=[app], id="alert_reminder_job", replace_existing=True, next_run_time=None, # don't run immediately on boot ) _scheduler.start() logger.info("Scheduler started; reminder check every %d minute(s).", interval) return _scheduler def trigger_now(app): """Manual trigger (for the 'Run check now' button).""" _check_and_send(app)