Преглед изворни кода

Enpoint mappe Specifico per la modalità filter

master
Lorenzo Pollutri пре 22 часа
родитељ
комит
a8da8f5359
4 измењених фајлова са 202 додато и 2 уклоњено
  1. +1
    -0
      app.py
  2. +9
    -0
      config_env.py
  3. +183
    -2
      routes/reslevis.py
  4. +9
    -0
      schemas/reslevis.py

+ 1
- 0
app.py Прегледај датотеку

@@ -155,6 +155,7 @@ async def local_then_core(request: Request, call_next):
"/reslevis/updateAlarm", "/reslevis/updateAlarm",
"/reslevis/updateCoreSettings", "/reslevis/updateCoreSettings",
"/reslevis/updateUserPreferences", "/reslevis/updateUserPreferences",
"/reslevis/uploadFloorMap",
} }
# only proxy CRUD for Reslevis (change prefix or methods if needed) # only proxy CRUD for Reslevis (change prefix or methods if needed)
if ( if (


+ 9
- 0
config_env.py Прегледај датотеку

@@ -167,3 +167,12 @@ BLE_AI_MAPS_DIR = os.getenv(
"/data/service/ble-ai-localizer/data/maps", "/data/service/ble-ai-localizer/data/maps",
) )


RESLEVIS_MAPS_DIR = os.getenv(
"RESLEVIS_MAPS_DIR",
str(Path(__file__).resolve().parent / "assets" / "maps"),
)

RESLEVIS_MAPS_PUBLIC_PATH = os.getenv(
"RESLEVIS_MAPS_PUBLIC_PATH",
"assets/maps",
).strip("/")

+ 183
- 2
routes/reslevis.py Прегледај датотеку

@@ -1,11 +1,13 @@
import asyncio import asyncio
import json import json
import os
import tempfile
from urllib.parse import urlencode from urllib.parse import urlencode


from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, Request, UploadFile
import httpx import httpx
import config_env import config_env
from typing import List, Optional
from typing import Any, Dict, List, Optional


from schemas.reslevis import ( from schemas.reslevis import (
BuildingItem, BuildingItem,
@@ -26,6 +28,7 @@ from schemas.reslevis import (
GuiConfigItem, GuiConfigItem,
UserPreferencesItem, UserPreferencesItem,
UserPreferencesUpdateItem, UserPreferencesUpdateItem,
FloorMapUploadResponseItem,
CoreSettingsItem, CoreSettingsItem,
CoreSettingsUpdateItem, CoreSettingsUpdateItem,
) )
@@ -54,6 +57,7 @@ ALERTS_CORE_BASE_URL = "http://localhost:1902"
TRACKS_CORE_BASE_URL = "http://localhost:1902" TRACKS_CORE_BASE_URL = "http://localhost:1902"
SETTINGS_CORE_BASE_URL = "http://127.0.0.1:1902" SETTINGS_CORE_BASE_URL = "http://127.0.0.1:1902"
CORE_TIMEOUT = 2.0 # secondi CORE_TIMEOUT = 2.0 # secondi
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"


async def sync_core_get(request: Request) -> None: async def sync_core_get(request: Request) -> None:
if request.method != "GET": if request.method != "GET":
@@ -171,6 +175,130 @@ def _uid_from_claims(claims: dict) -> str:
raise HTTPException(status_code=401, detail="User identity not found in token") raise HTTPException(status_code=401, detail="User identity not found in token")
return str(uid) return str(uid)



def _floor_maps_index_path() -> str:
return os.path.join(config_env.RESLEVIS_MAPS_DIR, "maps.json")


def _read_floor_maps_index() -> Dict[str, Any]:
path = _floor_maps_index_path()
if not os.path.isfile(path):
return {"items": [], "count": 0}

try:
with open(path, "r", encoding="utf-8") as fp:
data = json.load(fp)
except (OSError, ValueError):
return {"items": [], "count": 0}

if not isinstance(data, dict):
return {"items": [], "count": 0}

items = data.get("items")
if not isinstance(items, list):
items = []
return {"items": items, "count": len(items)}


def _write_floor_maps_index(index: Dict[str, Any]) -> None:
maps_dir = config_env.RESLEVIS_MAPS_DIR
os.makedirs(maps_dir, exist_ok=True)

index["count"] = len(index.get("items") or [])
payload = json.dumps(index, ensure_ascii=False, indent=2)

temp_name = None
try:
with tempfile.NamedTemporaryFile("w", dir=maps_dir, delete=False, encoding="utf-8") as tmp:
tmp.write(payload)
tmp.flush()
os.fsync(tmp.fileno())
temp_name = tmp.name

os.replace(temp_name, _floor_maps_index_path())
try:
os.chmod(_floor_maps_index_path(), 0o664)
except OSError:
pass
except OSError as exc:
if temp_name and os.path.exists(temp_name):
try:
os.remove(temp_name)
except OSError:
pass
raise HTTPException(status_code=500, detail="Unable to update maps.json") from exc


def _same_floor(row: Dict[str, Any], floor: int) -> bool:
try:
return int(row.get("floor")) == floor
except (TypeError, ValueError):
return False


def _floor_sort_key(row: Dict[str, Any]) -> int:
try:
return int(row.get("floor"))
except (TypeError, ValueError):
return 0


def _upsert_floor_map_record(floor: int, name: str, metadata: Dict[str, Any]) -> None:
index = _read_floor_maps_index()
item = {
"floor": floor,
"name": name,
"mime_type": "image/png",
"metadata": metadata,
}

items = [r for r in index["items"] if isinstance(r, dict) and not _same_floor(r, floor)]
items.append(item)
items.sort(key=_floor_sort_key)
index["items"] = items
_write_floor_maps_index(index)


def _public_map_path(name: str) -> str:
public_path = config_env.RESLEVIS_MAPS_PUBLIC_PATH
return f"{public_path}/{name}" if public_path else name


def _validate_map_target(maps_dir: str, name: str) -> str:
maps_dir_abs = os.path.abspath(maps_dir)
target_path = os.path.abspath(os.path.join(maps_dir_abs, name))
if os.path.commonpath([maps_dir_abs, target_path]) != maps_dir_abs:
raise HTTPException(status_code=400, detail="Invalid map filename")
return target_path


async def _write_upload_file(upload: UploadFile, target_path: str) -> None:
maps_dir = os.path.dirname(target_path)
temp_name = None
try:
with tempfile.NamedTemporaryFile("wb", dir=maps_dir, delete=False) as tmp:
temp_name = tmp.name
while True:
chunk = await upload.read(1024 * 1024)
if not chunk:
break
tmp.write(chunk)
tmp.flush()
os.fsync(tmp.fileno())

os.replace(temp_name, target_path)
try:
os.chmod(target_path, 0o664)
except OSError:
pass
except OSError as exc:
if temp_name and os.path.exists(temp_name):
try:
os.remove(temp_name)
except OSError:
pass
raise HTTPException(status_code=500, detail="Unable to save floor map") from exc

CORE_GET_SYNC = { CORE_GET_SYNC = {
"/reslevis/getGateways": (gateway_repo, _normalize_gateway), "/reslevis/getGateways": (gateway_repo, _normalize_gateway),
"/reslevis/getZones": (zone_repo, _normalize_zone), "/reslevis/getZones": (zone_repo, _normalize_zone),
@@ -645,6 +773,59 @@ def updateUserPreferences(
return user_preferences_repo.update(uid, item) return user_preferences_repo.update(uid, item)




@router.post(
"/uploadFloorMap",
response_model=FloorMapUploadResponseItem,
tags=["Reslevis"],
dependencies=[Depends(get_current_user)],
)
async def uploadFloorMap(
floor: int = Form(...),
file: UploadFile = File(...),
pixel_ratio: float = Form(1),
calibrated: bool = Form(False),
origin_x: int = Form(0),
origin_y: int = Form(0),
grid_size: int = Form(50),
):
if pixel_ratio <= 0:
raise HTTPException(status_code=400, detail="pixel_ratio must be greater than 0")
if grid_size <= 0:
raise HTTPException(status_code=400, detail="grid_size must be greater than 0")

content_type = (file.content_type or "").split(";")[0].strip().lower()
if content_type and content_type != "image/png":
raise HTTPException(status_code=400, detail="Only PNG files are allowed")

signature = await file.read(len(PNG_SIGNATURE))
if signature != PNG_SIGNATURE:
raise HTTPException(status_code=400, detail="Only PNG files are allowed")
await file.seek(0)

maps_dir = config_env.RESLEVIS_MAPS_DIR
os.makedirs(maps_dir, exist_ok=True)

name = f"floor_{floor}.png"
target_path = _validate_map_target(maps_dir, name)
await _write_upload_file(file, target_path)

metadata = {
"pixel_ratio": pixel_ratio,
"calibrated": calibrated,
"origin": [origin_x, origin_y],
"grid_size": grid_size,
}
_upsert_floor_map_record(floor, name, metadata)

return {
"floor": floor,
"name": name,
"path": _public_map_path(name),
"mime_type": "image/png",
"metadata": metadata,
}


@router.post("/postGuiConfig", tags=["Reslevis"], dependencies=[Depends(get_current_user)]) @router.post("/postGuiConfig", tags=["Reslevis"], dependencies=[Depends(get_current_user)])
def postGuiConfig(item: GuiConfigItem): def postGuiConfig(item: GuiConfigItem):
gui_config_repo.add(item) gui_config_repo.add(item)


+ 9
- 0
schemas/reslevis.py Прегледај датотеку

@@ -266,6 +266,15 @@ class CalibrationMetadata(BaseModel):
origin: Tuple[int, int] origin: Tuple[int, int]
grid_size: int grid_size: int



class FloorMapUploadResponseItem(BaseModel):
floor: int
name: str
path: str
mime_type: str
metadata: CalibrationMetadata


#??? Da verificate ??? #??? Da verificate ???
class DownloadFileImmage(): class DownloadFileImmage():
name : str name : str


Loading…
Откажи
Сачувај