|
- import asyncio
- import json
- from fastapi import APIRouter, Depends, HTTPException, Query, Request
- import httpx
- import config_env
- from typing import List, Optional
- from urllib.parse import urlencode
-
- 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 _core_get_json_via_curl(
- path: str,
- params: Optional[dict] = None,
- ):
- query_string = urlencode(params or {})
- url = f"{CORE_BASE_URL}{path}"
- if query_string:
- url = f"{url}?{query_string}"
-
- cmd = ["curl", "-sS", "-X", "GET"]
- if CORE_BASE_URL.startswith("https://"):
- cmd.append("-k")
- cmd.append(url)
-
- process = await asyncio.create_subprocess_exec(
- *cmd,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- )
- stdout, stderr = await process.communicate()
-
- if process.returncode != 0:
- detail = (stderr or stdout).decode("utf-8", errors="replace").strip() or "CORE curl request failed"
- raise HTTPException(status_code=502, detail=detail)
-
- try:
- return json.loads(stdout.decode("utf-8"))
- except ValueError as exc:
- raise HTTPException(status_code=502, detail="Invalid CORE response") from exc
-
-
- async def _fetch_tracks_for_tracker(
- tracker_id: str,
- params: Optional[dict] = None,
- ) -> List[dict]:
- payload = await _core_get_json_via_curl(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]:
- trackers_payload = await _core_get_json_via_curl("/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(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(
- request: Request,
- tracker_id: Optional[str] = Query(None),
- 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
-
- selected_tracker_id = tracker_id or request.query_params.get("id")
-
- if selected_tracker_id:
- return await _fetch_tracks_for_tracker(selected_tracker_id, params)
- return await _fetch_all_tracks(params)
-
-
- @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
-
- return await _fetch_tracks_for_tracker(tracker_id, params)
-
-
- @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"}
|