"""Global notification policy: severity ≥ min → selected channels.""" from __future__ import annotations from dataclasses import dataclass from sqlalchemy import select from sqlalchemy.orm import Session from app.config import get_settings from app.models.notification_channel import NotificationChannel from app.models.notification_policy import POLICY_ROW_ID, NotificationPolicy from app.services.notification_settings import ( CHANNEL_EMAIL, CHANNEL_TELEGRAM, CHANNEL_WEBHOOK, VALID_SEVERITIES, ) CHANNEL_MOBILE = "mobile" DEFAULT_CHANNELS = frozenset({CHANNEL_TELEGRAM, CHANNEL_WEBHOOK, CHANNEL_EMAIL, CHANNEL_MOBILE}) @dataclass(frozen=True) class NotificationPolicyConfig: min_severity: str use_telegram: bool use_webhook: bool use_email: bool use_mobile: bool source: str # env | db def allows_channel(self, channel: str) -> bool: if channel == CHANNEL_TELEGRAM: return self.use_telegram if channel == CHANNEL_WEBHOOK: return self.use_webhook if channel == CHANNEL_EMAIL: return self.use_email if channel == CHANNEL_MOBILE: return self.use_mobile return False def _parse_channels_csv(value: str) -> tuple[bool, bool, bool, bool]: parts = {p.strip().lower() for p in value.split(",") if p.strip()} return ( CHANNEL_TELEGRAM in parts or "tg" in parts, CHANNEL_WEBHOOK in parts, CHANNEL_EMAIL in parts or "mail" in parts or "smtp" in parts, CHANNEL_MOBILE in parts or "push" in parts or "fcm" in parts, ) def _policy_from_env() -> NotificationPolicyConfig: settings = get_settings() use_tg, use_wh, use_em, use_mob = _parse_channels_csv(settings.notify_channels) min_sev = settings.notify_min_severity.strip() or "warning" if min_sev not in VALID_SEVERITIES: min_sev = "warning" return NotificationPolicyConfig( min_severity=min_sev, use_telegram=use_tg, use_webhook=use_wh, use_email=use_em, use_mobile=use_mob, source="env", ) def get_effective_notification_policy(db: Session | None = None) -> NotificationPolicyConfig: if db is None: from app.database import SessionLocal session = SessionLocal() try: return get_effective_notification_policy(session) finally: session.close() row = db.get(NotificationPolicy, POLICY_ROW_ID) env_cfg = _policy_from_env() if row is None: return env_cfg min_sev = (row.min_severity or "").strip() or env_cfg.min_severity if min_sev not in VALID_SEVERITIES: min_sev = env_cfg.min_severity return NotificationPolicyConfig( min_severity=min_sev, use_telegram=bool(row.use_telegram), use_webhook=bool(row.use_webhook), use_email=bool(row.use_email), use_mobile=bool(getattr(row, "use_mobile", False)), source="db", ) def _sync_channel_min_severity(db: Session, min_severity: str) -> None: for channel in (CHANNEL_TELEGRAM, CHANNEL_WEBHOOK, CHANNEL_EMAIL): row = db.get(NotificationChannel, channel) if row is not None: row.min_severity = min_severity def upsert_notification_policy( db: Session, *, min_severity: str, use_telegram: bool, use_webhook: bool, use_email: bool, use_mobile: bool = False, ) -> NotificationPolicyConfig: if min_severity not in VALID_SEVERITIES: raise ValueError(f"invalid min_severity: {min_severity}") row = db.get(NotificationPolicy, POLICY_ROW_ID) if row is None: row = NotificationPolicy( id=POLICY_ROW_ID, min_severity=min_severity, use_telegram=use_telegram, use_webhook=use_webhook, use_email=use_email, use_mobile=use_mobile, ) db.add(row) else: row.min_severity = min_severity row.use_telegram = use_telegram row.use_webhook = use_webhook row.use_email = use_email row.use_mobile = use_mobile _sync_channel_min_severity(db, min_severity) db.commit() db.refresh(row) return get_effective_notification_policy(db)