fix(511-B /calc 경로 막기): 계산 창구가 브라우저 파일 경로를 안 씀 — 단계 격자 = {model_id · smooth · design} → 서버가 그 프로젝트 storage 안 DTM 격자 경로로 갈음(dtm_grid_npz · read_surface_grid 와 한 벌 · storage 밖 = 거절) · 경로 칸(stages.grid.path · stations.grid) = 400 · 창구 묶음 안 파일 읽기 = gridAt 하나 · 시험 test_511_b_calc_path_guard — 요구 320-4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 14:38:16 +09:00
co-authored by Claude Opus 5.5
parent 578b48f7ab
commit a7e2da2b9e
3 changed files with 224 additions and 21 deletions
+30 -21
View File
@@ -199,6 +199,32 @@ def _design_grid(
}
async def dtm_grid_npz(connection, project_id: UUID, model_id: int, smooth: bool) -> Path:
"""그 프로젝트 DTM 모델의 npz(평활 고르기 = 서버 표본기와 같음) — 없으면 LookupError(까닭).
경로 = 프로젝트 storage 안 DB 상대 경로만(밖으로 나가면 LookupError · 계산 창구 `/calc` 도 씀).
"""
stored = await get_project_storage_relative_path(connection, project_id)
async with connection.cursor() as cursor:
await cursor.execute(
"SELECT model_type, model_file_path FROM surface_models"
" WHERE id = %s AND project_id = %s",
(model_id, str(project_id)),
)
row = await cursor.fetchone()
if not stored or not row or row[0] != "dtm" or not row[1]:
raise LookupError("DTM 격자가 없는 지표면 모델입니다.")
root = Path(resolve_stored_project_path(stored))
model_path = (root / str(row[1])).resolve()
if not model_path.is_relative_to(root.resolve()):
raise LookupError("DTM 격자가 없는 지표면 모델입니다.")
smooth_path = model_path.with_name(f"{model_path.stem}_smooth.npz")
npz_path = smooth_path if smooth and smooth_path.is_file() else model_path
if not npz_path.is_file():
raise LookupError("DTM 격자 파일이 없습니다.")
return npz_path
@router.get("/{project_id}/surface/models/{model_id}/grid", response_model=None)
async def read_surface_grid(
request: Request, project_id: UUID, model_id: int, smooth: bool = False
@@ -209,26 +235,9 @@ async def read_surface_grid(
"""
pool = get_db_pool()
async with pool.acquire() as connection:
stored = await get_project_storage_relative_path(connection, project_id)
async with connection.cursor() as cursor:
await cursor.execute(
"SELECT model_type, model_file_path FROM surface_models"
" WHERE id = %s AND project_id = %s",
(model_id, str(project_id)),
)
row = await cursor.fetchone()
if not stored or not row or row[0] != "dtm" or not row[1]:
return JSONResponse(
status_code=404,
content={"status": "error", "message": "DTM 격자가 없는 지표면 모델입니다."},
)
model_path = Path(resolve_stored_project_path(stored)) / str(row[1])
smooth_path = model_path.with_name(f"{model_path.stem}_smooth.npz")
npz_path = smooth_path if smooth and smooth_path.is_file() else model_path
if not npz_path.is_file():
return JSONResponse(
status_code=404,
content={"status": "error", "message": "DTM 격자 파일이 없습니다."},
)
try:
npz_path = await dtm_grid_npz(connection, project_id, model_id, smooth)
except LookupError as exc:
return JSONResponse(status_code=404, content={"status": "error", "message": str(exc)})
grid_path = await asyncio.to_thread(dtm_grid_file, npz_path)
return cached_file_response(request, grid_path, "application/octet-stream")