import logging from datetime import datetime, timezone from sqlalchemy.orm import Session from app.core.database import SessionLocal from app.models import Allarme, Sito, Utente from app.services.firebase import firebase_service logger = logging.getLogger(__name__) class AlarmHandler: """Handler per processare gli allarmi ricevuti via MQTT e inviarli tramite FCM""" def __init__(self): self.db: Session = None def _get_db(self) -> Session: """Ottiene una nuova sessione database""" if self.db is None or not self.db.is_active: self.db = SessionLocal() return self.db def handle_alarm(self, payload: dict): """ Processa un allarme ricevuto via MQTT Steps: 1. Valida il payload 2. Salva l'allarme nel database 3. Recupera gli utenti del cliente associato al sito 4. Invia notifiche push tramite FCM Args: payload: Dizionario con i dati dell'allarme """ db = self._get_db() try: # 1. Validazione sito_id = payload.get("sito_id") if not sito_id: logger.error("Payload senza sito_id") return # Verifica che il sito esista sito = db.query(Sito).filter(Sito.id == sito_id).first() if not sito: logger.error(f"Sito {sito_id} non trovato") return # 2. Salva allarme nel database timestamp_str = payload.get("timestamp") timestamp = datetime.fromisoformat( timestamp_str.replace("Z", "+00:00") ) if timestamp_str else datetime.now(timezone.utc) allarme = Allarme( sito_id=sito_id, tipo=payload.get("tipo"), severita=payload.get("severita"), titolo=payload.get("titolo"), descrizione=payload.get("descrizione"), messaggio=payload.get("messaggio"), valore_rilevato=payload.get("valore_rilevato"), valore_soglia=payload.get("valore_soglia"), unita_misura=payload.get("unita_misura"), dati_sensori=payload.get("dati_sensori"), timestamp_rilevamento=timestamp, timestamp_notifica=datetime.now(timezone.utc), ) db.add(allarme) db.commit() db.refresh(allarme) logger.info(f"Allarme {allarme.id} salvato per sito {sito.nome}") # 3. Recupera utenti del cliente utenti = ( db.query(Utente) .filter( Utente.cliente_id == sito.cliente_id, Utente.attivo == True, Utente.fcm_token.isnot(None), ) .all() ) if not utenti: logger.warning( f"Nessun utente attivo con FCM token per cliente {sito.cliente_id}" ) return # 4. Invia notifiche push self._send_notifications(allarme, sito, utenti) except Exception as e: logger.error(f"Errore nel processamento dell'allarme: {e}") db.rollback() finally: db.close() def _send_notifications(self, allarme: Allarme, sito: Sito, utenti: list[Utente]): """Invia notifiche push agli utenti""" # Determina priorità basata sulla severità priority = "high" if allarme.severita == "critical" else "normal" # Prepara dati per la notifica notification_data = { "alarm_id": str(allarme.id), "sito_id": str(sito.id), "sito_nome": sito.nome, "tipo": allarme.tipo, "severita": allarme.severita, "timestamp": allarme.timestamp_rilevamento.isoformat(), } # Prepara titolo e messaggio title = f"{allarme.severita.upper()}: {sito.nome}" body = allarme.titolo or allarme.descrizione or "Nuovo allarme rilevato" # Raccogli tutti i token tokens = [u.fcm_token for u in utenti if u.fcm_token] if not tokens: logger.warning("Nessun token FCM valido trovato") return logger.info(f"Invio notifica a {len(tokens)} dispositivi") # Invia notifica multicast result = firebase_service.send_multicast( tokens=tokens, title=title, body=body, data=notification_data, priority=priority, ) logger.info( f"Notifiche inviate per allarme {allarme.id}: " f"{result['success']} successi, {result['failure']} fallimenti" ) # Singleton instance alarm_handler = AlarmHandler()