Files
Alert-Hub-System/app/scheduler.py
T
2026-09-13 19:59:54 +01:00

80 lines
3.4 KiB
Python

"""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"""
<div style="font-family: -apple-system, sans-serif; max-width: 600px;">
<h2 style="color: #00d4aa;">⏰ AlertHub Reminder</h2>
<h3>{alert.title}</h3>
<p><strong>Category:</strong> {alert.category or '—'}<br>
<strong>Expiration date:</strong> {alert.expiration_date.isoformat()}<br>
<strong>Days remaining:</strong> <span style="color:#ff6b6b;font-size:1.2em;">{days_left}</span></p>
<p>{alert.description or ''}</p>
</div>
"""
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)