app backend prima

This commit is contained in:
2025-10-20 19:10:08 +02:00
commit 438255d27b
42 changed files with 4622 additions and 0 deletions

150
app/mqtt/handler.py Normal file
View File

@@ -0,0 +1,150 @@
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()