25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

510 lines
15 KiB

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