You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

479 regels
14 KiB

  1. from fastapi import APIRouter, Depends, HTTPException, Query, Request
  2. import httpx
  3. import config_env
  4. from typing import List, Optional
  5. from schemas.reslevis import (
  6. BuildingItem,
  7. FloorItem,
  8. ZoneItem,
  9. GatewayItem,
  10. TrackerItem,
  11. OperatorItem,
  12. SubjectItem,
  13. AlarmItem,
  14. TrackItem,
  15. TrackHistoryItem,
  16. TrackerZoneItem,
  17. SettingItem,
  18. )
  19. from logica_reslevis.gateway import GatewayJsonRepository
  20. from logica_reslevis.building import BuildingJsonRepository
  21. from logica_reslevis.floor import FloorJsonRepository
  22. from logica_reslevis.zone import ZoneJsonRepository
  23. from logica_reslevis.tracker import TrackerJsonRepository
  24. from logica_reslevis.operator import OperatorJsonRepository
  25. from logica_reslevis.setting import SettingJsonRepository
  26. from logica_reslevis.subject import SubjectJsonRepository
  27. from logica_reslevis.alarm import AlarmJsonRepository
  28. from logica_reslevis.track import TrackJsonRepository
  29. from logica_reslevis.tracker_zone import TrackerZoneJsonRepository
  30. from security import get_current_user
  31. #CORE SYNC
  32. CORE_BASE_URL = config_env.CORE_API_URL.rstrip("/")
  33. CORE_TIMEOUT = 2.0 # secondi
  34. async def sync_core_get(request: Request) -> None:
  35. if request.method != "GET":
  36. return
  37. sync = CORE_GET_SYNC.get(request.url.path)
  38. if sync is None:
  39. return
  40. repo, normalizer = sync
  41. try:
  42. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  43. resp = await client.get(
  44. f"{CORE_BASE_URL}{request.url.path}",
  45. params=request.query_params,
  46. )
  47. if 200 <= resp.status_code < 300:
  48. data = resp.json()
  49. if isinstance(data, list):
  50. if normalizer:
  51. data = [normalizer(r) for r in data if isinstance(r, dict)]
  52. repo._write_all(data) # aggiorna i file locali
  53. except (httpx.RequestError, ValueError):
  54. # CORE giù o risposta non valida -> uso il file locale
  55. pass
  56. router = APIRouter(dependencies=[Depends(sync_core_get)])
  57. gateway_repo = GatewayJsonRepository()
  58. building_repo = BuildingJsonRepository()
  59. floor_repo = FloorJsonRepository()
  60. zone_repo = ZoneJsonRepository()
  61. tracker_repo = TrackerJsonRepository()
  62. operator_repo = OperatorJsonRepository()
  63. subject_repo = SubjectJsonRepository()
  64. alarm_repo = AlarmJsonRepository()
  65. track_repo = TrackJsonRepository()
  66. tracker_zone_repo = TrackerZoneJsonRepository()
  67. setting_repo = SettingJsonRepository()
  68. def _none_if_empty(v):
  69. return None if v in ("", None, 0, "0") else v
  70. def _str_or_none(v):
  71. if v in ("", None):
  72. return None
  73. if isinstance(v, (int, float, bool)):
  74. return str(v)
  75. return v
  76. def _uuid_list(values):
  77. if values in ("", None):
  78. return []
  79. if isinstance(values, str):
  80. values = [v for v in values.split(",") if v]
  81. if isinstance(values, (list, tuple, set)):
  82. cleaned = []
  83. for v in values:
  84. if isinstance(v, dict):
  85. v = v.get("id") or v.get("uuid")
  86. if v in ("", None, 0, "0"):
  87. continue
  88. cleaned.append(v)
  89. return cleaned
  90. return [values] if values not in ("", None, 0, "0") else []
  91. def _normalize_gateway(row: dict) -> dict:
  92. row = dict(row)
  93. row["floor"] = _none_if_empty(row.get("floor"))
  94. row["building"] = _none_if_empty(row.get("building"))
  95. return row
  96. def _normalize_tracker(row: dict) -> dict:
  97. row = dict(row)
  98. row["floor"] = _none_if_empty(row.get("floor"))
  99. row["building"] = _none_if_empty(row.get("building"))
  100. row["battery"] = _str_or_none(row.get("battery"))
  101. row["temperature"] = _str_or_none(row.get("temperature"))
  102. row["acceleration"] = _str_or_none(row.get("acceleration"))
  103. row["heartRate"] = _str_or_none(row.get("heartRate"))
  104. return row
  105. def _normalize_track(row: dict) -> dict:
  106. row = dict(row)
  107. row["ID"] = row.get("ID")
  108. row["gateway"] = _none_if_empty(row.get("gateway"))
  109. row["tracker"] = _none_if_empty(row.get("tracker"))
  110. row["subject"] = _none_if_empty(row.get("subject"))
  111. row["floor"] = _none_if_empty(row.get("floor"))
  112. row["building"] = _none_if_empty(row.get("building"))
  113. row["timestamp"] = _str_or_none(row.get("timestamp"))
  114. row["type"] = _str_or_none(row.get("type"))
  115. row["status"] = _str_or_none(row.get("status"))
  116. row["gatewayMac"] = _str_or_none(row.get("gatewayMac"))
  117. row["trackerMac"] = _str_or_none(row.get("trackerMac"))
  118. row["subjectName"] = _str_or_none(row.get("subjectName"))
  119. row["x"] = None if row.get("x") in ("", None) else row.get("x")
  120. row["y"] = None if row.get("y") in ("", None) else row.get("y")
  121. row["z"] = None if row.get("z") in ("", None) else row.get("z")
  122. # signal resta float o None
  123. row["signal"] = None if row.get("signal") in ("", None) else row.get("signal")
  124. return row
  125. def _normalize_zone(row: dict) -> dict:
  126. row = dict(row)
  127. row["floor"] = _none_if_empty(row.get("floor"))
  128. row["building"] = _none_if_empty(row.get("building"))
  129. row["groups"] = _uuid_list(row.get("groups"))
  130. return row
  131. CORE_GET_SYNC = {
  132. "/reslevis/getGateways": (gateway_repo, _normalize_gateway),
  133. "/reslevis/getZones": (zone_repo, _normalize_zone),
  134. "/reslevis/getTrackers": (tracker_repo, _normalize_tracker),
  135. "/reslevis/getTracks": (track_repo, _normalize_track),
  136. }
  137. @router.get(
  138. "/getGateways",
  139. response_model=List[GatewayItem],
  140. tags=["Reslevis"],
  141. dependencies=[Depends(get_current_user)],
  142. )
  143. def getGateways():
  144. return gateway_repo.list()
  145. @router.post("/postGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  146. def postGateway(item: GatewayItem):
  147. gateway_repo.add(item)
  148. return {"message": "OK"}
  149. @router.put("/updateGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  150. def updateGateway(item: GatewayItem):
  151. gateway_repo.update(item)
  152. return {"message": "OK"}
  153. @router.delete("/removeGateway/{gateway_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  154. def removeGateway(gateway_id: str):
  155. gateway_repo.remove(gateway_id)
  156. return {"message": "OK"}
  157. @router.get(
  158. "/getBuildings",
  159. response_model=List[BuildingItem],
  160. tags=["Reslevis"],
  161. dependencies=[Depends(get_current_user)],
  162. )
  163. def getBuildings():
  164. return building_repo.list()
  165. @router.post("/postBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  166. def postBuilding(item: BuildingItem):
  167. building_repo.add(item)
  168. return {"message": "OK"}
  169. @router.put("/updateBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  170. def updateBuilding(item: BuildingItem):
  171. building_repo.update(item)
  172. return {"message": "OK"}
  173. @router.delete("/removeBuilding/{building_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  174. def removeBuilding(building_id: str):
  175. building_repo.remove(building_id)
  176. return {"message": "OK"}
  177. @router.get(
  178. "/getFloors",
  179. response_model=List[FloorItem],
  180. tags=["Reslevis"],
  181. dependencies=[Depends(get_current_user)],
  182. )
  183. def getFloors():
  184. return floor_repo.list()
  185. @router.post("/postFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  186. def postFloor(item: FloorItem):
  187. floor_repo.add(item)
  188. return {"message": "OK"}
  189. @router.put("/updateFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  190. def updateFloor(item: FloorItem):
  191. floor_repo.update(item)
  192. return {"message": "OK"}
  193. @router.delete("/removeFloor/{floor_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  194. def removeFloor(floor_id: str):
  195. floor_repo.remove(floor_id)
  196. return {"message": "OK"}
  197. @router.get(
  198. "/getZones",
  199. response_model=List[ZoneItem],
  200. tags=["Reslevis"],
  201. dependencies=[Depends(get_current_user)],
  202. )
  203. def getZones():
  204. return zone_repo.list()
  205. @router.post("/postZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  206. def postZone(item: ZoneItem):
  207. zone_repo.add(item)
  208. return {"message": "OK"}
  209. @router.put("/updateZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  210. def updateZone(item: ZoneItem):
  211. zone_repo.update(item)
  212. return {"message": "OK"}
  213. @router.delete("/removeZone/{zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  214. def removeZone(zone_id: str):
  215. zone_repo.remove(zone_id)
  216. return {"message": "OK"}
  217. @router.get(
  218. "/getTrackers",
  219. response_model=List[TrackerItem],
  220. tags=["Reslevis"],
  221. dependencies=[Depends(get_current_user)],
  222. )
  223. def getTrackers():
  224. return tracker_repo.list()
  225. @router.post("/postTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  226. def postTracker(item: TrackerItem):
  227. tracker_repo.add(item)
  228. return {"message": "OK"}
  229. @router.put("/updateTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  230. def updateTracker(item: TrackerItem):
  231. tracker_repo.update(item)
  232. return {"message": "OK"}
  233. @router.delete("/removeTracker/{tracker_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  234. def removeTracker(tracker_id: str):
  235. tracker_repo.remove(tracker_id)
  236. return {"message": "OK"}
  237. @router.get(
  238. "/getTrackerZones",
  239. response_model=List[TrackerZoneItem],
  240. tags=["Reslevis"],
  241. dependencies=[Depends(get_current_user)],
  242. )
  243. def getTrackerZones():
  244. return tracker_zone_repo.list()
  245. @router.post("/postTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  246. def postTrackerZone(item: TrackerZoneItem):
  247. tracker_zone_repo.add(item)
  248. return {"message": "OK"}
  249. @router.put("/updateTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  250. def updateTrackerZone(item: TrackerZoneItem):
  251. tracker_zone_repo.update(item)
  252. return {"message": "OK"}
  253. @router.delete("/removeTrackerZone/{tracker_zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  254. def removeTrackerZone(tracker_zone_id: str):
  255. tracker_zone_repo.remove(tracker_zone_id)
  256. return {"message": "OK"}
  257. @router.get(
  258. "/getTracks",
  259. response_model=List[TrackItem],
  260. tags=["Reslevis"],
  261. dependencies=[Depends(get_current_user)],
  262. )
  263. def getTracks():
  264. return track_repo.list()
  265. @router.get(
  266. "/getTracks/{tracker_id}",
  267. response_model=List[TrackHistoryItem],
  268. tags=["Reslevis"],
  269. dependencies=[Depends(get_current_user)],
  270. )
  271. async def getTrack(
  272. tracker_id: str,
  273. limit: Optional[int] = Query(None, ge=1),
  274. from_: Optional[str] = Query(None, alias="from"),
  275. to: Optional[str] = Query(None),
  276. ):
  277. params = {}
  278. if limit is not None:
  279. params["limit"] = limit
  280. if from_:
  281. params["from"] = from_
  282. if to:
  283. params["to"] = to
  284. try:
  285. async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
  286. resp = await client.get(
  287. f"{CORE_BASE_URL}/reslevis/getTracks/{tracker_id}",
  288. params=params,
  289. )
  290. except httpx.RequestError as exc:
  291. raise HTTPException(status_code=502, detail=f"CORE request failed: {exc}") from exc
  292. if resp.status_code >= 400:
  293. detail = resp.text.strip() or "CORE request failed"
  294. raise HTTPException(status_code=resp.status_code, detail=detail)
  295. try:
  296. payload = resp.json()
  297. except ValueError as exc:
  298. raise HTTPException(status_code=502, detail="Invalid CORE response") from exc
  299. if not isinstance(payload, list):
  300. raise HTTPException(status_code=502, detail="Unexpected CORE response type")
  301. return [_normalize_track(row) for row in payload if isinstance(row, dict)]
  302. @router.post("/postTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  303. def postTrack(item: TrackItem):
  304. track_repo.add(item)
  305. return {"message": "OK"}
  306. @router.put("/updateTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  307. def updateTrack(item: TrackItem):
  308. track_repo.update(item)
  309. return {"message": "OK"}
  310. @router.delete("/removeTrack/{track_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  311. def removeTrack(track_id: str):
  312. track_repo.remove(track_id)
  313. return {"message": "OK"}
  314. @router.get(
  315. "/getOperators",
  316. response_model=List[OperatorItem],
  317. tags=["Reslevis"],
  318. dependencies=[Depends(get_current_user)],
  319. )
  320. def getOperators():
  321. return operator_repo.list()
  322. @router.post("/postOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  323. def postOperator(item: OperatorItem):
  324. operator_repo.add(item)
  325. return {"message": "OK"}
  326. @router.put("/updateOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  327. def updateOperator(item: OperatorItem):
  328. operator_repo.update(item)
  329. return {"message": "OK"}
  330. @router.delete("/removeOperator/{operator_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  331. def removeOperator(operator_id: str):
  332. operator_repo.remove(operator_id)
  333. return {"message": "OK"}
  334. @router.get(
  335. "/getSubjects",
  336. response_model=List[SubjectItem],
  337. tags=["Reslevis"],
  338. dependencies=[Depends(get_current_user)],
  339. )
  340. def getSubjects():
  341. return subject_repo.list()
  342. @router.post("/postSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  343. def postSubject(item: SubjectItem):
  344. subject_repo.add(item)
  345. return {"message": "OK"}
  346. @router.put("/updateSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  347. def updateSubject(item: SubjectItem):
  348. subject_repo.update(item)
  349. return {"message": "OK"}
  350. @router.delete("/removeSubject/{subject_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  351. def removeSubject(subject_id: str):
  352. subject_repo.remove(subject_id)
  353. return {"message": "OK"}
  354. @router.get(
  355. "/getSettings",
  356. response_model=List[SettingItem],
  357. tags=["Reslevis"],
  358. dependencies=[Depends(get_current_user)],
  359. )
  360. def getSettings():
  361. return setting_repo.list()
  362. @router.post("/postSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  363. def postSetting(item: SettingItem):
  364. setting_repo.add(item)
  365. return {"message": "OK"}
  366. @router.put("/updateSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  367. def updateSetting(item: SettingItem):
  368. setting_repo.update(item)
  369. return {"message": "OK"}
  370. @router.delete("/removeSetting/{setting_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  371. def removeSetting(setting_id: str):
  372. setting_repo.remove(setting_id)
  373. return {"message": "OK"}