app backend prima
This commit is contained in:
0
app/api/__init__.py
Normal file
0
app/api/__init__.py
Normal file
171
app/api/allarmi.py
Normal file
171
app/api/allarmi.py
Normal file
@@ -0,0 +1,171 @@
|
||||
from typing import Annotated, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import desc
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models import Allarme, Utente, Sito
|
||||
from app.schemas.allarme import AllarmeResponse, AllarmeList, AllarmeUpdate
|
||||
from app.api.auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/allarmi", tags=["Allarmi"])
|
||||
|
||||
|
||||
@router.get("", response_model=AllarmeList)
|
||||
async def get_allarmi(
|
||||
current_user: Annotated[Utente, Depends(get_current_user)],
|
||||
db: Session = Depends(get_db),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
sito_id: Optional[int] = None,
|
||||
severita: Optional[str] = None,
|
||||
stato: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Recupera gli allarmi per il cliente dell'utente autenticato.
|
||||
Supporta filtri e paginazione.
|
||||
"""
|
||||
|
||||
# Query base: solo allarmi dei siti del cliente dell'utente
|
||||
query = (
|
||||
db.query(Allarme)
|
||||
.join(Sito)
|
||||
.filter(Sito.cliente_id == current_user.cliente_id)
|
||||
)
|
||||
|
||||
# Applica filtri opzionali
|
||||
if sito_id:
|
||||
query = query.filter(Allarme.sito_id == sito_id)
|
||||
if severita:
|
||||
query = query.filter(Allarme.severita == severita)
|
||||
if stato:
|
||||
query = query.filter(Allarme.stato == stato)
|
||||
|
||||
# Conta totale
|
||||
total = query.count()
|
||||
|
||||
# Applica paginazione e ordinamento
|
||||
allarmi = (
|
||||
query.order_by(desc(Allarme.timestamp_rilevamento))
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
|
||||
return AllarmeList(
|
||||
total=total,
|
||||
items=allarmi,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{allarme_id}", response_model=AllarmeResponse)
|
||||
async def get_allarme(
|
||||
allarme_id: int,
|
||||
current_user: Annotated[Utente, Depends(get_current_user)],
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Recupera un singolo allarme per ID"""
|
||||
|
||||
allarme = (
|
||||
db.query(Allarme)
|
||||
.join(Sito)
|
||||
.filter(
|
||||
Allarme.id == allarme_id,
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not allarme:
|
||||
raise HTTPException(status_code=404, detail="Allarme non trovato")
|
||||
|
||||
return allarme
|
||||
|
||||
|
||||
@router.patch("/{allarme_id}", response_model=AllarmeResponse)
|
||||
async def update_allarme(
|
||||
allarme_id: int,
|
||||
update_data: AllarmeUpdate,
|
||||
current_user: Annotated[Utente, Depends(get_current_user)],
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Aggiorna lo stato o le note di un allarme"""
|
||||
|
||||
allarme = (
|
||||
db.query(Allarme)
|
||||
.join(Sito)
|
||||
.filter(
|
||||
Allarme.id == allarme_id,
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not allarme:
|
||||
raise HTTPException(status_code=404, detail="Allarme non trovato")
|
||||
|
||||
# Aggiorna campi se forniti
|
||||
if update_data.stato is not None:
|
||||
allarme.stato = update_data.stato
|
||||
|
||||
# Se viene risolto, registra chi e quando
|
||||
if update_data.stato == "risolto":
|
||||
from datetime import datetime, timezone
|
||||
allarme.risolto_da = f"{current_user.nome} {current_user.cognome}"
|
||||
allarme.timestamp_risoluzione = datetime.now(timezone.utc)
|
||||
|
||||
if update_data.note is not None:
|
||||
allarme.note = update_data.note
|
||||
|
||||
if update_data.risolto_da is not None:
|
||||
allarme.risolto_da = update_data.risolto_da
|
||||
|
||||
db.commit()
|
||||
db.refresh(allarme)
|
||||
|
||||
return allarme
|
||||
|
||||
|
||||
@router.get("/sito/{sito_id}", response_model=AllarmeList)
|
||||
async def get_allarmi_by_sito(
|
||||
sito_id: int,
|
||||
current_user: Annotated[Utente, Depends(get_current_user)],
|
||||
db: Session = Depends(get_db),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
):
|
||||
"""Recupera tutti gli allarmi per un sito specifico"""
|
||||
|
||||
# Verifica che il sito appartenga al cliente dell'utente
|
||||
sito = (
|
||||
db.query(Sito)
|
||||
.filter(
|
||||
Sito.id == sito_id,
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not sito:
|
||||
raise HTTPException(status_code=404, detail="Sito non trovato")
|
||||
|
||||
query = db.query(Allarme).filter(Allarme.sito_id == sito_id)
|
||||
|
||||
total = query.count()
|
||||
|
||||
allarmi = (
|
||||
query.order_by(desc(Allarme.timestamp_rilevamento))
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
|
||||
return AllarmeList(
|
||||
total=total,
|
||||
items=allarmi,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
130
app/api/auth.py
Normal file
130
app/api/auth.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from datetime import timedelta
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import get_db
|
||||
from app.core.security import verify_password, create_access_token, decode_access_token
|
||||
from app.models import Utente
|
||||
from app.schemas.auth import Token, LoginRequest, RegisterFCMToken
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)],
|
||||
db: Session = Depends(get_db)
|
||||
) -> Utente:
|
||||
"""Dependency per ottenere l'utente corrente dal token JWT"""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Credenziali non valide",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
payload = decode_access_token(token)
|
||||
if payload is None:
|
||||
raise credentials_exception
|
||||
|
||||
email: str = payload.get("sub")
|
||||
if email is None:
|
||||
raise credentials_exception
|
||||
|
||||
user = db.query(Utente).filter(Utente.email == email).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
if not user.attivo:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Utente non attivo"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/token", response_model=Token)
|
||||
async def login(
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Endpoint per login con OAuth2 password flow"""
|
||||
user = db.query(Utente).filter(Utente.email == form_data.username).first()
|
||||
|
||||
if not user or not verify_password(form_data.password, user.password_hash):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Email o password non corretti",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if not user.attivo:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Utente non attivo"
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.email, "cliente_id": user.cliente_id},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login_json(login_data: LoginRequest, db: Session = Depends(get_db)):
|
||||
"""Endpoint per login con JSON"""
|
||||
user = db.query(Utente).filter(Utente.email == login_data.email).first()
|
||||
|
||||
if not user or not verify_password(login_data.password, user.password_hash):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Email o password non corretti"
|
||||
)
|
||||
|
||||
if not user.attivo:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Utente non attivo"
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.email, "cliente_id": user.cliente_id},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@router.post("/register-fcm-token")
|
||||
async def register_fcm_token(
|
||||
token_data: RegisterFCMToken,
|
||||
current_user: Annotated[Utente, Depends(get_current_user)],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Registra o aggiorna il FCM token per l'utente corrente"""
|
||||
current_user.fcm_token = token_data.fcm_token
|
||||
db.commit()
|
||||
|
||||
return {"message": "FCM token registrato con successo"}
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def get_me(current_user: Annotated[Utente, Depends(get_current_user)]):
|
||||
"""Restituisce le informazioni dell'utente corrente"""
|
||||
return {
|
||||
"id": current_user.id,
|
||||
"email": current_user.email,
|
||||
"nome": current_user.nome,
|
||||
"cognome": current_user.cognome,
|
||||
"ruolo": current_user.ruolo,
|
||||
"cliente_id": current_user.cliente_id,
|
||||
}
|
||||
50
app/api/siti.py
Normal file
50
app/api/siti.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Endpoint API per la gestione dei siti monitorati
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models import Sito, Utente
|
||||
from app.api.auth import get_current_user
|
||||
from app.schemas.sito import SitoResponse, SitoListResponse
|
||||
|
||||
router = APIRouter(prefix="/siti", tags=["siti"])
|
||||
|
||||
|
||||
@router.get("/{sito_id}", response_model=SitoResponse)
|
||||
async def get_sito(
|
||||
sito_id: int,
|
||||
current_user: Utente = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Recupera dettagli di un sito specifico"""
|
||||
sito = db.query(Sito).filter(
|
||||
Sito.id == sito_id,
|
||||
Sito.cliente_id == current_user.cliente_id,
|
||||
).first()
|
||||
|
||||
if not sito:
|
||||
raise HTTPException(status_code=404, detail="Sito non trovato")
|
||||
|
||||
return sito
|
||||
|
||||
|
||||
@router.get("", response_model=SitoListResponse)
|
||||
async def get_siti(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
tipo: str | None = None,
|
||||
current_user: Utente = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Recupera lista siti del cliente"""
|
||||
query = db.query(Sito).filter(Sito.cliente_id == current_user.cliente_id)
|
||||
|
||||
if tipo:
|
||||
query = query.filter(Sito.tipo == tipo)
|
||||
|
||||
total = query.count()
|
||||
siti = query.offset(skip).limit(limit).all()
|
||||
|
||||
return {"total": total, "items": siti}
|
||||
127
app/api/statistiche.py
Normal file
127
app/api/statistiche.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Endpoint API per statistiche e dashboard
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.models import Allarme, Sito, Utente
|
||||
from app.api.auth import get_current_user
|
||||
from app.schemas.statistiche import StatisticheResponse, AllarmiPerGiornoResponse
|
||||
|
||||
router = APIRouter(prefix="/statistiche", tags=["statistiche"])
|
||||
|
||||
|
||||
@router.get("", response_model=StatisticheResponse)
|
||||
async def get_statistiche(
|
||||
current_user: Utente = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Recupera statistiche generali per il dashboard"""
|
||||
|
||||
# Totali
|
||||
totale_allarmi = db.query(Allarme).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
).count()
|
||||
|
||||
totale_siti = db.query(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
).count()
|
||||
|
||||
# Allarmi per severità
|
||||
allarmi_per_severita = db.query(
|
||||
Allarme.severita,
|
||||
func.count(Allarme.id).label('count')
|
||||
).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
).group_by(Allarme.severita).all()
|
||||
|
||||
severita_dict = {s: c for s, c in allarmi_per_severita}
|
||||
|
||||
# Allarmi per stato
|
||||
allarmi_per_stato = db.query(
|
||||
Allarme.stato,
|
||||
func.count(Allarme.id).label('count')
|
||||
).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
).group_by(Allarme.stato).all()
|
||||
|
||||
stato_dict = {s: c for s, c in allarmi_per_stato}
|
||||
|
||||
# Allarmi aperti (non risolti)
|
||||
allarmi_aperti = db.query(Allarme).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id,
|
||||
Allarme.stato != 'risolto'
|
||||
).count()
|
||||
|
||||
# Allarmi ultimi 7 giorni
|
||||
seven_days_ago = datetime.now() - timedelta(days=7)
|
||||
allarmi_recenti = db.query(Allarme).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id,
|
||||
Allarme.created_at >= seven_days_ago
|
||||
).count()
|
||||
|
||||
# Siti per tipo
|
||||
siti_per_tipo = db.query(
|
||||
Sito.tipo,
|
||||
func.count(Sito.id).label('count')
|
||||
).filter(
|
||||
Sito.cliente_id == current_user.cliente_id
|
||||
).group_by(Sito.tipo).all()
|
||||
|
||||
tipo_dict = {t: c for t, c in siti_per_tipo}
|
||||
|
||||
return {
|
||||
"totale_allarmi": totale_allarmi,
|
||||
"totale_siti": totale_siti,
|
||||
"allarmi_aperti": allarmi_aperti,
|
||||
"allarmi_recenti_7gg": allarmi_recenti,
|
||||
"allarmi_critical": severita_dict.get('critical', 0),
|
||||
"allarmi_warning": severita_dict.get('warning', 0),
|
||||
"allarmi_info": severita_dict.get('info', 0),
|
||||
"allarmi_nuovo": stato_dict.get('nuovo', 0),
|
||||
"allarmi_in_gestione": stato_dict.get('in_gestione', 0),
|
||||
"allarmi_risolto": stato_dict.get('risolto', 0),
|
||||
"siti_ponte": tipo_dict.get('ponte', 0),
|
||||
"siti_galleria": tipo_dict.get('galleria', 0),
|
||||
"siti_diga": tipo_dict.get('diga', 0),
|
||||
"siti_frana": tipo_dict.get('frana', 0),
|
||||
"siti_versante": tipo_dict.get('versante', 0),
|
||||
"siti_edificio": tipo_dict.get('edificio', 0),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/allarmi-per-giorno", response_model=AllarmiPerGiornoResponse)
|
||||
async def get_allarmi_per_giorno(
|
||||
giorni: int = 30,
|
||||
current_user: Utente = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Recupera statistiche allarmi per giorno (per grafici temporali)"""
|
||||
|
||||
start_date = datetime.now() - timedelta(days=giorni)
|
||||
|
||||
# Query allarmi raggruppati per giorno
|
||||
allarmi_giornalieri = db.query(
|
||||
func.date(Allarme.created_at).label('data'),
|
||||
func.count(Allarme.id).label('count')
|
||||
).join(Sito).filter(
|
||||
Sito.cliente_id == current_user.cliente_id,
|
||||
Allarme.created_at >= start_date
|
||||
).group_by(
|
||||
func.date(Allarme.created_at)
|
||||
).order_by(
|
||||
func.date(Allarme.created_at)
|
||||
).all()
|
||||
|
||||
# Converti in lista di dict
|
||||
dati = []
|
||||
for data, count in allarmi_giornalieri:
|
||||
dati.append({
|
||||
"data": data.isoformat(),
|
||||
"count": count
|
||||
})
|
||||
|
||||
return {"dati": dati}
|
||||
Reference in New Issue
Block a user