Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

712 righe
21 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. ZoneAreaDefinitionItem,
  13. GatewayItem,
  14. TrackerItem,
  15. OperatorItem,
  16. SubjectItem,
  17. AlarmItem,
  18. AlarmCoreItem,
  19. AlarmStatusUpdateItem,
  20. TrackItem,
  21. TrackHistoryItem,
  22. TrackerZoneItem,
  23. SettingItem,
  24. GuiConfigItem,
  25. UserPreferencesItem,
  26. UserPreferencesUpdateItem,
  27. CoreSettingsItem,
  28. CoreSettingsUpdateItem,
  29. )
  30. from logica_reslevis.gateway import GatewayJsonRepository
  31. from logica_reslevis.building import BuildingJsonRepository
  32. from logica_reslevis.floor import FloorJsonRepository
  33. from logica_reslevis.zone import ZoneJsonRepository
  34. from logica_reslevis.zone_area_definition import ZoneAreaDefinitionJsonRepository
  35. from logica_reslevis.tracker import TrackerJsonRepository
  36. from logica_reslevis.operator import OperatorJsonRepository
  37. from logica_reslevis.setting import SettingJsonRepository
  38. from logica_reslevis.gui_config import GuiConfigJsonRepository
  39. from logica_reslevis.user_preferences import UserPreferencesJsonRepository
  40. from logica_reslevis.subject import SubjectJsonRepository
  41. from logica_reslevis.alarm import AlarmJsonRepository
  42. from logica_reslevis.track import TrackJsonRepository
  43. from logica_reslevis.tracker_zone import TrackerZoneJsonRepository
  44. from logica_reslevis.tracker_mode import get_mode_aware_trackers
  45. from security import get_current_user
  46. #CORE SYNC
  47. CORE_BASE_URL = config_env.CORE_API_URL.rstrip("/")
  48. ALERTS_CORE_BASE_URL = "http://localhost:1902"
  49. TRACKS_CORE_BASE_URL = "http://localhost:1902"
  50. SETTINGS_CORE_BASE_URL = "http://127.0.0.1:1902"
  51. CORE_TIMEOUT = 2.0 # secondi
  52. async def sync_core_get(request: Request) -> None:
  53. if request.method != "GET":
  54. return
  55. sync = CORE_GET_SYNC.get(request.url.path)
  56. if sync is None:
  57. return
  58. repo, normalizer = sync
  59. try:
  60. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  61. resp = await client.get(
  62. f"{CORE_BASE_URL}{request.url.path}",
  63. params=request.query_params,
  64. )
  65. if 200 <= resp.status_code < 300:
  66. data = resp.json()
  67. if isinstance(data, list):
  68. if normalizer:
  69. data = [normalizer(r) for r in data if isinstance(r, dict)]
  70. repo._write_all(data) # aggiorna i file locali
  71. except (httpx.RequestError, ValueError):
  72. # CORE giù o risposta non valida -> uso il file locale
  73. pass
  74. router = APIRouter(dependencies=[Depends(sync_core_get)])
  75. gateway_repo = GatewayJsonRepository()
  76. building_repo = BuildingJsonRepository()
  77. floor_repo = FloorJsonRepository()
  78. zone_repo = ZoneJsonRepository()
  79. zone_area_definition_repo = ZoneAreaDefinitionJsonRepository()
  80. tracker_repo = TrackerJsonRepository()
  81. operator_repo = OperatorJsonRepository()
  82. subject_repo = SubjectJsonRepository()
  83. alarm_repo = AlarmJsonRepository()
  84. track_repo = TrackJsonRepository()
  85. tracker_zone_repo = TrackerZoneJsonRepository()
  86. setting_repo = SettingJsonRepository()
  87. gui_config_repo = GuiConfigJsonRepository()
  88. user_preferences_repo = UserPreferencesJsonRepository()
  89. def _none_if_empty(v):
  90. return None if v in ("", None, 0, "0") else v
  91. def _str_or_none(v):
  92. if v in ("", None):
  93. return None
  94. if isinstance(v, (int, float, bool)):
  95. return str(v)
  96. return v
  97. def _uuid_list(values):
  98. if values in ("", None):
  99. return []
  100. if isinstance(values, str):
  101. values = [v for v in values.split(",") if v]
  102. if isinstance(values, (list, tuple, set)):
  103. cleaned = []
  104. for v in values:
  105. if isinstance(v, dict):
  106. v = v.get("id") or v.get("uuid")
  107. if v in ("", None, 0, "0"):
  108. continue
  109. cleaned.append(v)
  110. return cleaned
  111. return [values] if values not in ("", None, 0, "0") else []
  112. def _normalize_gateway(row: dict) -> dict:
  113. row = dict(row)
  114. row["floor"] = _none_if_empty(row.get("floor"))
  115. row["building"] = _none_if_empty(row.get("building"))
  116. return row
  117. def _normalize_track(row: dict) -> dict:
  118. row = dict(row)
  119. row["ID"] = row.get("ID")
  120. row["gateway"] = _none_if_empty(row.get("gateway"))
  121. row["tracker"] = _none_if_empty(row.get("tracker"))
  122. row["subject"] = _none_if_empty(row.get("subject"))
  123. row["floor"] = _none_if_empty(row.get("floor"))
  124. row["building"] = _none_if_empty(row.get("building"))
  125. row["timestamp"] = _str_or_none(row.get("timestamp"))
  126. row["type"] = _str_or_none(row.get("type"))
  127. row["status"] = _str_or_none(row.get("status"))
  128. row["gatewayMac"] = _str_or_none(row.get("gatewayMac"))
  129. row["trackerMac"] = _str_or_none(row.get("trackerMac"))
  130. row["subjectName"] = _str_or_none(row.get("subjectName"))
  131. row["x"] = None if row.get("x") in ("", None) else row.get("x")
  132. row["y"] = None if row.get("y") in ("", None) else row.get("y")
  133. row["z"] = None if row.get("z") in ("", None) else row.get("z")
  134. # signal resta float o None
  135. row["signal"] = None if row.get("signal") in ("", None) else row.get("signal")
  136. return row
  137. def _normalize_zone(row: dict) -> dict:
  138. row = dict(row)
  139. row["floor"] = _none_if_empty(row.get("floor"))
  140. row["building"] = _none_if_empty(row.get("building"))
  141. row["groups"] = _uuid_list(row.get("groups"))
  142. return row
  143. def _uid_from_claims(claims: dict) -> str:
  144. uid = (
  145. claims.get("preferred_username")
  146. or claims.get("username")
  147. or claims.get("email")
  148. or claims.get("sub")
  149. )
  150. if not uid:
  151. raise HTTPException(status_code=401, detail="User identity not found in token")
  152. return str(uid)
  153. CORE_GET_SYNC = {
  154. "/reslevis/getGateways": (gateway_repo, _normalize_gateway),
  155. "/reslevis/getZones": (zone_repo, _normalize_zone),
  156. }
  157. async def _fetch_tracks_for_tracker(
  158. tracker_id: str,
  159. params: Optional[dict] = None,
  160. ) -> List[dict]:
  161. query_string = urlencode(params or {})
  162. url = f"{TRACKS_CORE_BASE_URL}/reslevis/getTracks/{tracker_id}"
  163. if query_string:
  164. url = f"{url}?{query_string}"
  165. process = await asyncio.create_subprocess_exec(
  166. "curl",
  167. "-sS",
  168. "-X",
  169. "GET",
  170. url,
  171. stdout=asyncio.subprocess.PIPE,
  172. stderr=asyncio.subprocess.PIPE,
  173. )
  174. stdout, stderr = await process.communicate()
  175. if process.returncode != 0:
  176. detail = (stderr or stdout).decode("utf-8", errors="replace").strip() or "CORE curl request failed"
  177. raise HTTPException(status_code=502, detail=detail)
  178. try:
  179. payload = json.loads(stdout.decode("utf-8"))
  180. except ValueError as exc:
  181. raise HTTPException(status_code=502, detail="Invalid CORE response") from exc
  182. if not isinstance(payload, list):
  183. raise HTTPException(status_code=502, detail="Unexpected CORE response type")
  184. return [_normalize_track(row) for row in payload if isinstance(row, dict)]
  185. @router.get(
  186. "/getGateways",
  187. response_model=List[GatewayItem],
  188. tags=["Reslevis"],
  189. dependencies=[Depends(get_current_user)],
  190. )
  191. def getGateways():
  192. return gateway_repo.list()
  193. @router.post("/postGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  194. def postGateway(item: GatewayItem):
  195. gateway_repo.add(item)
  196. return {"message": "OK"}
  197. @router.put("/updateGateway", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  198. def updateGateway(item: GatewayItem):
  199. gateway_repo.update(item)
  200. return {"message": "OK"}
  201. @router.delete("/removeGateway/{gateway_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  202. def removeGateway(gateway_id: str):
  203. gateway_repo.remove(gateway_id)
  204. return {"message": "OK"}
  205. @router.get(
  206. "/getBuildings",
  207. response_model=List[BuildingItem],
  208. tags=["Reslevis"],
  209. dependencies=[Depends(get_current_user)],
  210. )
  211. def getBuildings():
  212. return building_repo.list()
  213. @router.post("/postBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  214. def postBuilding(item: BuildingItem):
  215. building_repo.add(item)
  216. return {"message": "OK"}
  217. @router.put("/updateBuilding", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  218. def updateBuilding(item: BuildingItem):
  219. building_repo.update(item)
  220. return {"message": "OK"}
  221. @router.delete("/removeBuilding/{building_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  222. def removeBuilding(building_id: str):
  223. building_repo.remove(building_id)
  224. return {"message": "OK"}
  225. @router.get(
  226. "/getFloors",
  227. response_model=List[FloorItem],
  228. tags=["Reslevis"],
  229. dependencies=[Depends(get_current_user)],
  230. )
  231. def getFloors():
  232. return floor_repo.list()
  233. @router.post("/postFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  234. def postFloor(item: FloorItem):
  235. floor_repo.add(item)
  236. return {"message": "OK"}
  237. @router.put("/updateFloor", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  238. def updateFloor(item: FloorItem):
  239. floor_repo.update(item)
  240. return {"message": "OK"}
  241. @router.delete("/removeFloor/{floor_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  242. def removeFloor(floor_id: str):
  243. floor_repo.remove(floor_id)
  244. return {"message": "OK"}
  245. @router.get(
  246. "/getZones",
  247. response_model=List[ZoneItem],
  248. tags=["Reslevis"],
  249. dependencies=[Depends(get_current_user)],
  250. )
  251. def getZones():
  252. return zone_repo.list()
  253. @router.post("/postZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  254. def postZone(item: ZoneItem):
  255. zone_repo.add(item)
  256. return {"message": "OK"}
  257. @router.put("/updateZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  258. def updateZone(item: ZoneItem):
  259. zone_repo.update(item)
  260. return {"message": "OK"}
  261. @router.delete("/removeZone/{zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  262. def removeZone(zone_id: str):
  263. zone_repo.remove(zone_id)
  264. return {"message": "OK"}
  265. @router.get(
  266. "/getZoneAreaDefinitions",
  267. response_model=List[ZoneAreaDefinitionItem],
  268. tags=["Reslevis"],
  269. dependencies=[Depends(get_current_user)],
  270. )
  271. def getZoneAreaDefinitions(UUID: str | None = None):
  272. return zone_area_definition_repo.list(UUID)
  273. @router.post("/postZoneAreaDefinition", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  274. def postZoneAreaDefinition(item: ZoneAreaDefinitionItem):
  275. zone_area_definition_repo.add(item)
  276. return {"message": "OK"}
  277. @router.put("/updateZoneAreaDefinition", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  278. def updateZoneAreaDefinition(item: ZoneAreaDefinitionItem):
  279. zone_area_definition_repo.update(item)
  280. return {"message": "OK"}
  281. @router.delete("/removeZoneAreaDefinition/{zone_area_definition_uuid}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  282. def removeZoneAreaDefinition(zone_area_definition_uuid: str):
  283. zone_area_definition_repo.remove(zone_area_definition_uuid)
  284. return {"message": "OK"}
  285. @router.get(
  286. "/getTrackers",
  287. response_model=List[TrackerItem],
  288. tags=["Reslevis"],
  289. dependencies=[Depends(get_current_user)],
  290. )
  291. async def getTrackers():
  292. return await get_mode_aware_trackers(
  293. tracker_repo,
  294. SETTINGS_CORE_BASE_URL,
  295. config_env.BLE_AI_INFER_CSV,
  296. CORE_TIMEOUT,
  297. )
  298. @router.post("/postTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  299. def postTracker(item: TrackerItem):
  300. tracker_repo.add(item)
  301. return {"message": "OK"}
  302. @router.put("/updateTracker", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  303. def updateTracker(item: TrackerItem):
  304. tracker_repo.update(item)
  305. return {"message": "OK"}
  306. @router.delete("/removeTracker/{tracker_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  307. def removeTracker(tracker_id: str):
  308. tracker_repo.remove(tracker_id)
  309. return {"message": "OK"}
  310. @router.get(
  311. "/getTrackerZones",
  312. response_model=List[TrackerZoneItem],
  313. tags=["Reslevis"],
  314. dependencies=[Depends(get_current_user)],
  315. )
  316. def getTrackerZones():
  317. return tracker_zone_repo.list()
  318. @router.post("/postTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  319. def postTrackerZone(item: TrackerZoneItem):
  320. tracker_zone_repo.add(item)
  321. return {"message": "OK"}
  322. @router.put("/updateTrackerZone", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  323. def updateTrackerZone(item: TrackerZoneItem):
  324. tracker_zone_repo.update(item)
  325. return {"message": "OK"}
  326. @router.delete("/removeTrackerZone/{tracker_zone_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  327. def removeTrackerZone(tracker_zone_id: str):
  328. tracker_zone_repo.remove(tracker_zone_id)
  329. return {"message": "OK"}
  330. @router.get(
  331. "/getTracks",
  332. response_model=List[TrackHistoryItem],
  333. tags=["Reslevis"],
  334. dependencies=[Depends(get_current_user)],
  335. )
  336. async def getTracks(
  337. tracker_id: str = Query(..., alias="id"),
  338. limit: Optional[int] = Query(None, ge=1),
  339. from_: Optional[str] = Query(None, alias="from"),
  340. to: Optional[str] = Query(None),
  341. ):
  342. params = {}
  343. if limit is not None:
  344. params["limit"] = limit
  345. if from_:
  346. params["from"] = from_
  347. if to:
  348. params["to"] = to
  349. return await _fetch_tracks_for_tracker(tracker_id, params)
  350. @router.get(
  351. "/getTracks/{tracker_id}",
  352. response_model=List[TrackHistoryItem],
  353. tags=["Reslevis"],
  354. dependencies=[Depends(get_current_user)],
  355. )
  356. async def getTrack(
  357. tracker_id: str,
  358. limit: Optional[int] = Query(None, ge=1),
  359. from_: Optional[str] = Query(None, alias="from"),
  360. to: Optional[str] = Query(None),
  361. ):
  362. params = {}
  363. if limit is not None:
  364. params["limit"] = limit
  365. if from_:
  366. params["from"] = from_
  367. if to:
  368. params["to"] = to
  369. return await _fetch_tracks_for_tracker(tracker_id, params)
  370. @router.post("/postTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  371. def postTrack(item: TrackItem):
  372. track_repo.add(item)
  373. return {"message": "OK"}
  374. @router.put("/updateTrack", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  375. def updateTrack(item: TrackItem):
  376. track_repo.update(item)
  377. return {"message": "OK"}
  378. @router.delete("/removeTrack/{track_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  379. def removeTrack(track_id: str):
  380. track_repo.remove(track_id)
  381. return {"message": "OK"}
  382. @router.get(
  383. "/getAlarms",
  384. response_model=List[AlarmCoreItem],
  385. tags=["Reslevis"],
  386. dependencies=[Depends(get_current_user)],
  387. )
  388. async def getAlarms():
  389. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  390. resp = await client.get(f"{ALERTS_CORE_BASE_URL}/reslevis/alerts")
  391. if resp.status_code >= 400:
  392. detail = resp.text.strip() or "CORE alerts request failed"
  393. raise HTTPException(status_code=resp.status_code, detail=detail)
  394. try:
  395. payload = resp.json()
  396. except ValueError as exc:
  397. raise HTTPException(status_code=502, detail="Invalid CORE response") from exc
  398. if not isinstance(payload, list):
  399. raise HTTPException(status_code=502, detail="Unexpected CORE response type")
  400. return payload
  401. @router.put(
  402. "/updateAlarm",
  403. tags=["Reslevis"],
  404. dependencies=[Depends(get_current_user)],
  405. )
  406. async def updateAlarm(item: AlarmStatusUpdateItem):
  407. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  408. resp = await client.patch(
  409. f"{ALERTS_CORE_BASE_URL}/reslevis/alerts/{item.id}",
  410. json={"status": item.status},
  411. )
  412. if resp.status_code >= 400:
  413. detail = resp.text.strip() or "CORE alert update failed"
  414. raise HTTPException(status_code=resp.status_code, detail=detail)
  415. if not resp.content:
  416. return {"message": "OK"}
  417. try:
  418. return resp.json()
  419. except ValueError:
  420. return {"message": "OK"}
  421. @router.get(
  422. "/getOperators",
  423. response_model=List[OperatorItem],
  424. tags=["Reslevis"],
  425. dependencies=[Depends(get_current_user)],
  426. )
  427. def getOperators():
  428. return operator_repo.list()
  429. @router.post("/postOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  430. def postOperator(item: OperatorItem):
  431. operator_repo.add(item)
  432. return {"message": "OK"}
  433. @router.put("/updateOperator", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  434. def updateOperator(item: OperatorItem):
  435. operator_repo.update(item)
  436. return {"message": "OK"}
  437. @router.delete("/removeOperator/{operator_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  438. def removeOperator(operator_id: str):
  439. operator_repo.remove(operator_id)
  440. return {"message": "OK"}
  441. @router.get(
  442. "/getSubjects",
  443. response_model=List[SubjectItem],
  444. tags=["Reslevis"],
  445. dependencies=[Depends(get_current_user)],
  446. )
  447. def getSubjects():
  448. return subject_repo.list()
  449. @router.post("/postSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  450. def postSubject(item: SubjectItem):
  451. subject_repo.add(item)
  452. return {"message": "OK"}
  453. @router.put("/updateSubject", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  454. def updateSubject(item: SubjectItem):
  455. subject_repo.update(item)
  456. return {"message": "OK"}
  457. @router.delete("/removeSubject/{subject_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  458. def removeSubject(subject_id: str):
  459. subject_repo.remove(subject_id)
  460. return {"message": "OK"}
  461. @router.get(
  462. "/getSettings",
  463. response_model=List[SettingItem],
  464. tags=["Reslevis"],
  465. dependencies=[Depends(get_current_user)],
  466. )
  467. def getSettings():
  468. return setting_repo.list()
  469. @router.post("/postSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  470. def postSetting(item: SettingItem):
  471. setting_repo.add(item)
  472. return {"message": "OK"}
  473. @router.put("/updateSetting", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  474. def updateSetting(item: SettingItem):
  475. setting_repo.update(item)
  476. return {"message": "OK"}
  477. @router.delete("/removeSetting/{setting_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  478. def removeSetting(setting_id: str):
  479. setting_repo.remove(setting_id)
  480. return {"message": "OK"}
  481. @router.get(
  482. "/getGuiConfigs",
  483. response_model=List[GuiConfigItem],
  484. tags=["Reslevis"],
  485. dependencies=[Depends(get_current_user)],
  486. )
  487. def getGuiConfigs():
  488. return gui_config_repo.list()
  489. @router.get(
  490. "/getUserPreferences",
  491. response_model=UserPreferencesItem,
  492. tags=["Reslevis"],
  493. )
  494. def getUserPreferences(current_user: dict = Depends(get_current_user)):
  495. uid = _uid_from_claims(current_user)
  496. return user_preferences_repo.get(uid)
  497. @router.put(
  498. "/updateUserPreferences",
  499. response_model=UserPreferencesItem,
  500. tags=["Reslevis"],
  501. )
  502. def updateUserPreferences(
  503. item: UserPreferencesUpdateItem,
  504. current_user: dict = Depends(get_current_user),
  505. ):
  506. uid = _uid_from_claims(current_user)
  507. return user_preferences_repo.update(uid, item)
  508. @router.post("/postGuiConfig", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  509. def postGuiConfig(item: GuiConfigItem):
  510. gui_config_repo.add(item)
  511. return {"message": "OK"}
  512. @router.put("/updateGuiConfig", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  513. def updateGuiConfig(item: GuiConfigItem):
  514. gui_config_repo.update(item)
  515. return {"message": "OK"}
  516. @router.delete("/removeGuiConfig/{gui_config_id}", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
  517. def removeGuiConfig(gui_config_id: str):
  518. gui_config_repo.remove(gui_config_id)
  519. return {"message": "OK"}
  520. @router.get(
  521. "/getCoreSettings",
  522. response_model=List[CoreSettingsItem],
  523. tags=["Reslevis"],
  524. dependencies=[Depends(get_current_user)],
  525. )
  526. async def getCoreSettings():
  527. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  528. resp = await client.get(f"{SETTINGS_CORE_BASE_URL}/reslevis/settings")
  529. if resp.status_code >= 400:
  530. detail = resp.text.strip() or "CORE settings request failed"
  531. raise HTTPException(status_code=resp.status_code, detail=detail)
  532. try:
  533. payload = resp.json()
  534. except ValueError as exc:
  535. raise HTTPException(status_code=502, detail="Invalid CORE response") from exc
  536. if not isinstance(payload, list):
  537. raise HTTPException(status_code=502, detail="Unexpected CORE response type")
  538. return payload
  539. @router.put(
  540. "/updateCoreSettings",
  541. tags=["Reslevis"],
  542. dependencies=[Depends(get_current_user)],
  543. )
  544. async def updateCoreSettings(item: CoreSettingsUpdateItem):
  545. async with httpx.AsyncClient(timeout=CORE_TIMEOUT) as client:
  546. resp = await client.patch(
  547. f"{SETTINGS_CORE_BASE_URL}/reslevis/settings",
  548. json=item.model_dump(exclude_none=True),
  549. )
  550. if resp.status_code >= 400:
  551. detail = resp.text.strip() or "CORE settings update failed"
  552. raise HTTPException(status_code=resp.status_code, detail=detail)
  553. if not resp.content:
  554. return {"message": "OK"}
  555. try:
  556. return resp.json()
  557. except ValueError:
  558. return {"message": "OK"}