您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

510 行
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"}