feat: WebSockets para ETA en tiempo real y endpoint alertas operativas
This commit is contained in:
Binary file not shown.
@@ -1,8 +1,11 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException
|
from fastapi import FastAPI, Depends, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from database import engine, get_db
|
from database import engine, get_db
|
||||||
|
from typing import Dict, Set
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
import models, schemas, auth, simulator
|
import models, schemas, auth, simulator
|
||||||
|
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
@@ -12,6 +15,32 @@ app = FastAPI(title="HackOnLinces 2026 - Recolección de Residuos")
|
|||||||
app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
||||||
allow_methods=["*"], allow_headers=["*"])
|
allow_methods=["*"], allow_headers=["*"])
|
||||||
|
|
||||||
|
class ConnectionManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.active_connections: Dict[str, Set[WebSocket]] = {}
|
||||||
|
|
||||||
|
async def connect(self, websocket: WebSocket, route_id: str):
|
||||||
|
await websocket.accept()
|
||||||
|
if route_id not in self.active_connections:
|
||||||
|
self.active_connections[route_id] = set()
|
||||||
|
self.active_connections[route_id].add(websocket)
|
||||||
|
|
||||||
|
def disconnect(self, websocket: WebSocket, route_id: str):
|
||||||
|
if route_id in self.active_connections:
|
||||||
|
self.active_connections[route_id].discard(websocket)
|
||||||
|
|
||||||
|
async def broadcast_to_route(self, route_id: str, message: dict):
|
||||||
|
if route_id in self.active_connections:
|
||||||
|
dead = set()
|
||||||
|
for ws in self.active_connections[route_id]:
|
||||||
|
try:
|
||||||
|
await ws.send_json(message)
|
||||||
|
except:
|
||||||
|
dead.add(ws)
|
||||||
|
self.active_connections[route_id] -= dead
|
||||||
|
|
||||||
|
manager = ConnectionManager()
|
||||||
|
|
||||||
scheduler = BackgroundScheduler()
|
scheduler = BackgroundScheduler()
|
||||||
scheduler.add_job(simulator.avanzar_rutas, "interval", minutes=2)
|
scheduler.add_job(simulator.avanzar_rutas, "interval", minutes=2)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
@@ -62,6 +91,14 @@ def crear_domicilio(data: schemas.DomicilioCreate,
|
|||||||
db.refresh(dom)
|
db.refresh(dom)
|
||||||
return dom
|
return dom
|
||||||
|
|
||||||
|
@app.get("/domicilios")
|
||||||
|
def listar_domicilios(
|
||||||
|
current_user=Depends(auth.get_current_user),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
domicilios = db.query(models.Domicilio).filter_by(usuario_id=current_user.id).all()
|
||||||
|
return [{"id": d.id, "direccion": d.direccion, "colonia": d.colonia, "route_id": d.route_id} for d in domicilios]
|
||||||
|
|
||||||
@app.get("/eta/{domicilio_id}", response_model=schemas.ETAResponse)
|
@app.get("/eta/{domicilio_id}", response_model=schemas.ETAResponse)
|
||||||
def get_eta(domicilio_id: int,
|
def get_eta(domicilio_id: int,
|
||||||
current_user=Depends(auth.get_current_user),
|
current_user=Depends(auth.get_current_user),
|
||||||
@@ -95,10 +132,51 @@ def crear_reporte(
|
|||||||
"estado": "PENDIENTE"
|
"estado": "PENDIENTE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.get("/domicilios")
|
@app.post("/alertas/operativa")
|
||||||
def listar_domicilios(
|
def crear_alerta_operativa(
|
||||||
current_user=Depends(auth.get_current_user),
|
route_id: str,
|
||||||
|
tipo: str,
|
||||||
|
mensaje: str,
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
domicilios = db.query(models.Domicilio).filter_by(usuario_id=current_user.id).all()
|
estado = db.query(models.EstadoRuta).filter_by(route_id=route_id).first()
|
||||||
return [{"id": d.id, "direccion": d.direccion, "colonia": d.colonia, "route_id": d.route_id} for d in domicilios]
|
if not estado:
|
||||||
|
raise HTTPException(status_code=404, detail="Ruta no encontrada")
|
||||||
|
return {
|
||||||
|
"route_id": route_id,
|
||||||
|
"tipo": tipo,
|
||||||
|
"mensaje": mensaje,
|
||||||
|
"evento": "ALERTA_OPERATIVA",
|
||||||
|
"estado": "ENVIADA"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.websocket("/ws/eta/{domicilio_id}")
|
||||||
|
async def websocket_eta(websocket: WebSocket, domicilio_id: int,
|
||||||
|
token: str, db: Session = Depends(get_db)):
|
||||||
|
try:
|
||||||
|
payload = auth.jwt.decode(token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM])
|
||||||
|
email = payload.get("sub")
|
||||||
|
user = db.query(models.Usuario).filter_by(email=email).first()
|
||||||
|
if not user:
|
||||||
|
await websocket.close(code=1008)
|
||||||
|
return
|
||||||
|
dom = db.query(models.Domicilio).filter_by(id=domicilio_id).first()
|
||||||
|
if not dom or dom.usuario_id != user.id:
|
||||||
|
await websocket.close(code=1008)
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
await websocket.close(code=1008)
|
||||||
|
return
|
||||||
|
|
||||||
|
await manager.connect(websocket, dom.route_id)
|
||||||
|
try:
|
||||||
|
eta = simulator.get_eta(dom.route_id, db)
|
||||||
|
if eta:
|
||||||
|
await websocket.send_json({**eta, "route_id": dom.route_id, "colonia": dom.colonia})
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(30)
|
||||||
|
eta = simulator.get_eta(dom.route_id, db)
|
||||||
|
if eta:
|
||||||
|
await websocket.send_json({**eta, "route_id": dom.route_id, "colonia": dom.colonia})
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
manager.disconnect(websocket, dom.route_id)
|
||||||
Reference in New Issue
Block a user