import asyncio from fastapi import APIRouter, Depends, HTTPException, Query, Request import httpx import config_env from typing import List, Optional from schemas.reslevis import ( BuildingItem, FloorItem, ZoneItem, GatewayItem, TrackerItem, OperatorItem, SubjectItem, AlarmItem, TrackItem, TrackHistoryItem, TrackerZoneItem, SettingItem, ) from logica_reslevis.gateway import GatewayJsonRepository from logica_reslevis.building import BuildingJsonRepository from logica_reslevis.floor import FloorJsonRepository from logica_reslevis.zone import ZoneJsonRepository from logica_reslevis.tracker import TrackerJsonRepository from logica_reslevis.operator import OperatorJsonRepository from logica_reslevis.setting import SettingJsonRepository from logica_reslevis.subject import SubjectJsonRepository from logica_reslevis.alarm import AlarmJsonRepository from logica_reslevis.track import TrackJsonRepository from logica_reslevis.tracker_zone import TrackerZoneJsonRepository from security import get_current_user #CORE SYNC CORE_BASE_URL = config_env.CORE_API_URL.rstrip("/") CORE_TIMEOUT = 2.0 # secondi async def sync_core_get(request: Request) -> None: if request.method != "GET": return sync = CORE_GET_SYNC.get(request.url.path) if sync is None: return repo, normalizer = sync try: async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client: resp = await client.get( f"{CORE_BASE_URL}{request.url.path}", params=request.query_params, ) if 200 <= resp.status_code < 300: data = resp.json() if isinstance(data, list): if normalizer: data = [normalizer(r) for r in data if isinstance(r, dict)] repo._write_all(data) # aggiorna i file locali except (httpx.RequestError, ValueError): # CORE giù o risposta non valida -> uso il file locale pass router = APIRouter(dependencies=[Depends(sync_core_get)]) gateway_repo = GatewayJsonRepository() building_repo = BuildingJsonRepository() floor_repo = FloorJsonRepository() zone_repo = ZoneJsonRepository() tracker_repo = TrackerJsonRepository() operator_repo = OperatorJsonRepository() subject_repo = SubjectJsonRepository() alarm_repo = AlarmJsonRepository() track_repo = TrackJsonRepository() tracker_zone_repo = TrackerZoneJsonRepository() setting_repo = SettingJsonRepository() def _none_if_empty(v): return None if v in ("", None, 0, "0") else v def _str_or_none(v): if v in ("", None): return None if isinstance(v, (int, float, bool)): return str(v) return v def _uuid_list(values): if values in ("", None): return [] if isinstance(values, str): values = [v for v in values.split(",") if v] if isinstance(values, (list, tuple, set)): cleaned = [] for v in values: if isinstance(v, dict): v = v.get("id") or v.get("uuid") if v in ("", None, 0, "0"): continue cleaned.append(v) return cleaned return [values] if values not in ("", None, 0, "0") else [] def _normalize_gateway(row: dict) -> dict: row = dict(row) row["floor"] = _none_if_empty(row.get("floor")) row["building"] = _none_if_empty(row.get("building")) return row def _normalize_tracker(row: dict) -> dict: row = dict(row) row["floor"] = _none_if_empty(row.get("floor")) row["building"] = _none_if_empty(row.get("building")) row["battery"] = _str_or_none(row.get("battery")) row["temperature"] = _str_or_none(row.get("temperature")) row["acceleration"] = _str_or_none(row.get("acceleration")) row["heartRate"] = _str_or_none(row.get("heartRate")) return row def _normalize_track(row: dict) -> dict: row = dict(row) row["ID"] = row.get("ID") row["gateway"] = _none_if_empty(row.get("gateway")) row["tracker"] = _none_if_empty(row.get("tracker")) row["subject"] = _none_if_empty(row.get("subject")) row["floor"] = _none_if_empty(row.get("floor")) row["building"] = _none_if_empty(row.get("building")) row["timestamp"] = _str_or_none(row.get("timestamp")) row["type"] = _str_or_none(row.get("type")) row["status"] = _str_or_none(row.get("status")) row["gatewayMac"] = _str_or_none(row.get("gatewayMac")) row["trackerMac"] = _str_or_none(row.get("trackerMac")) row["subjectName"] = _str_or_none(row.get("subjectName")) row["x"] = None if row.get("x") in ("", None) else row.get("x") row["y"] = None if row.get("y") in ("", None) else row.get("y") row["z"] = None if row.get("z") in ("", None) else row.get("z") # signal resta float o None row["signal"] = None if row.get("signal") in ("", None) else row.get("signal") return row def _normalize_zone(row: dict) -> dict: row = dict(row) row["floor"] = _none_if_empty(row.get("floor")) row["building"] = _none_if_empty(row.get("building")) row["groups"] = _uuid_list(row.get("groups")) return row CORE_GET_SYNC = { "/reslevis/getGateways": (gateway_repo, _normalize_gateway), "/reslevis/getZones": (zone_repo, _normalize_zone), "/reslevis/getTrackers": (tracker_repo, _normalize_tracker), } async def _core_get_json( client: httpx.AsyncClient, path: str, params: Optional[dict] = None, ): resp = await client.get(f"{CORE_BASE_URL}{path}", params=params) if resp.status_code >= 400: detail = resp.text.strip() or "CORE request failed" raise HTTPException(status_code=resp.status_code, detail=detail) try: return resp.json() except ValueError as exc: raise HTTPException(status_code=502, detail="Invalid CORE response") from exc async def _fetch_tracks_for_tracker( client: httpx.AsyncClient, tracker_id: str, params: Optional[dict] = None, ) -> List[dict]: query_params = dict(params or {}) query_params["id"] = tracker_id try: payload = await _core_get_json(client, "/reslevis/getTracks", params=query_params) if not isinstance(payload, list): raise HTTPException(status_code=502, detail="Unexpected CORE response type") direct_rows = [_normalize_track(row) for row in payload if isinstance(row, dict)] if direct_rows: return direct_rows except HTTPException as exc: if exc.status_code != 404: raise payload = await _core_get_json(client, f"/reslevis/getTracks/{tracker_id}", params=params) if not isinstance(payload, list): raise HTTPException(status_code=502, detail="Unexpected CORE response type") return [_normalize_track(row) for row in payload if isinstance(row, dict)] def _sort_tracks_desc(rows: List[dict]) -> List[dict]: return sorted(rows, key=lambda row: row.get("timestamp") or "", reverse=True) async def _fetch_all_tracks(params: dict) -> List[dict]: async with httpx.AsyncClient(timeout=30.0, verify=False) as client: try: payload = await _core_get_json(client, "/reslevis/getTracks", params=params) if not isinstance(payload, list): raise HTTPException(status_code=502, detail="Unexpected CORE response type") direct_rows = [_normalize_track(row) for row in payload if isinstance(row, dict)] if direct_rows: return direct_rows except HTTPException as exc: if exc.status_code != 404: raise trackers_payload = await _core_get_json(client, "/reslevis/getTrackers") if not isinstance(trackers_payload, list): raise HTTPException(status_code=502, detail="Unexpected CORE tracker response type") tracker_ids = [] for item in trackers_payload: if not isinstance(item, dict): continue tracker_id = item.get("id") if tracker_id: tracker_ids.append(str(tracker_id)) batches = await asyncio.gather( *[_fetch_tracks_for_tracker(client, tracker_id, params) for tracker_id in tracker_ids] ) merged = [row for batch in batches for row in batch] merged = _sort_tracks_desc(merged) limit = params.get("limit") if isinstance(limit, int): return merged[:limit] return merged @router.get( "/getGateways", response_model=List[GatewayItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getGateways(): return gateway_repo.list() @router.post("/postGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postGateway(item: GatewayItem): gateway_repo.add(item) return {"message": "OK"} @router.put("/updateGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateGateway(item: GatewayItem): gateway_repo.update(item) return {"message": "OK"} @router.delete("/removeGateway/{gateway_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeGateway(gateway_id: str): gateway_repo.remove(gateway_id) return {"message": "OK"} @router.get( "/getBuildings", response_model=List[BuildingItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getBuildings(): return building_repo.list() @router.post("/postBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postBuilding(item: BuildingItem): building_repo.add(item) return {"message": "OK"} @router.put("/updateBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateBuilding(item: BuildingItem): building_repo.update(item) return {"message": "OK"} @router.delete("/removeBuilding/{building_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeBuilding(building_id: str): building_repo.remove(building_id) return {"message": "OK"} @router.get( "/getFloors", response_model=List[FloorItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getFloors(): return floor_repo.list() @router.post("/postFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postFloor(item: FloorItem): floor_repo.add(item) return {"message": "OK"} @router.put("/updateFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateFloor(item: FloorItem): floor_repo.update(item) return {"message": "OK"} @router.delete("/removeFloor/{floor_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeFloor(floor_id: str): floor_repo.remove(floor_id) return {"message": "OK"} @router.get( "/getZones", response_model=List[ZoneItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getZones(): return zone_repo.list() @router.post("/postZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postZone(item: ZoneItem): zone_repo.add(item) return {"message": "OK"} @router.put("/updateZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateZone(item: ZoneItem): zone_repo.update(item) return {"message": "OK"} @router.delete("/removeZone/{zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeZone(zone_id: str): zone_repo.remove(zone_id) return {"message": "OK"} @router.get( "/getTrackers", response_model=List[TrackerItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getTrackers(): return tracker_repo.list() @router.post("/postTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postTracker(item: TrackerItem): tracker_repo.add(item) return {"message": "OK"} @router.put("/updateTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateTracker(item: TrackerItem): tracker_repo.update(item) return {"message": "OK"} @router.delete("/removeTracker/{tracker_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeTracker(tracker_id: str): tracker_repo.remove(tracker_id) return {"message": "OK"} @router.get( "/getTrackerZones", response_model=List[TrackerZoneItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getTrackerZones(): return tracker_zone_repo.list() @router.post("/postTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postTrackerZone(item: TrackerZoneItem): tracker_zone_repo.add(item) return {"message": "OK"} @router.put("/updateTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateTrackerZone(item: TrackerZoneItem): tracker_zone_repo.update(item) return {"message": "OK"} @router.delete("/removeTrackerZone/{tracker_zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeTrackerZone(tracker_zone_id: str): tracker_zone_repo.remove(tracker_zone_id) return {"message": "OK"} @router.get( "/getTracks", response_model=List[TrackHistoryItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) async def getTracks( limit: Optional[int] = Query(None, ge=1), from_: Optional[str] = Query(None, alias="from"), to: Optional[str] = Query(None), ): params = {} if limit is not None: params["limit"] = limit if from_: params["from"] = from_ if to: params["to"] = to try: return await _fetch_all_tracks(params) except httpx.RequestError as exc: raise HTTPException(status_code=502, detail=f"CORE request failed: {exc}") from exc @router.get( "/getTracks/{tracker_id}", response_model=List[TrackHistoryItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) async def getTrack( tracker_id: str, limit: Optional[int] = Query(None, ge=1), from_: Optional[str] = Query(None, alias="from"), to: Optional[str] = Query(None), ): params = {} if limit is not None: params["limit"] = limit if from_: params["from"] = from_ if to: params["to"] = to try: async with httpx.AsyncClient(timeout=30.0, verify=False) as client: return await _fetch_tracks_for_tracker(client, tracker_id, params) except httpx.RequestError as exc: raise HTTPException(status_code=502, detail=f"CORE request failed: {exc}") from exc @router.post("/postTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postTrack(item: TrackItem): track_repo.add(item) return {"message": "OK"} @router.put("/updateTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateTrack(item: TrackItem): track_repo.update(item) return {"message": "OK"} @router.delete("/removeTrack/{track_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeTrack(track_id: str): track_repo.remove(track_id) return {"message": "OK"} @router.get( "/getOperators", response_model=List[OperatorItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getOperators(): return operator_repo.list() @router.post("/postOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postOperator(item: OperatorItem): operator_repo.add(item) return {"message": "OK"} @router.put("/updateOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateOperator(item: OperatorItem): operator_repo.update(item) return {"message": "OK"} @router.delete("/removeOperator/{operator_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeOperator(operator_id: str): operator_repo.remove(operator_id) return {"message": "OK"} @router.get( "/getSubjects", response_model=List[SubjectItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getSubjects(): return subject_repo.list() @router.post("/postSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postSubject(item: SubjectItem): subject_repo.add(item) return {"message": "OK"} @router.put("/updateSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateSubject(item: SubjectItem): subject_repo.update(item) return {"message": "OK"} @router.delete("/removeSubject/{subject_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeSubject(subject_id: str): subject_repo.remove(subject_id) return {"message": "OK"} @router.get( "/getSettings", response_model=List[SettingItem], tags=["Reslevis"], dependencies=[Depends(get_current_user)], ) def getSettings(): return setting_repo.list() @router.post("/postSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def postSetting(item: SettingItem): setting_repo.add(item) return {"message": "OK"} @router.put("/updateSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def updateSetting(item: SettingItem): setting_repo.update(item) return {"message": "OK"} @router.delete("/removeSetting/{setting_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) def removeSetting(setting_id: str): setting_repo.remove(setting_id) return {"message": "OK"}