- B09_wf6_Estimation -> B09_Estimation (폴더·파일·라우트 키/슬러그)
- B07_Quantity_UI_Page.ts 신설: 워크플로우 셸 + 좌측 [확정] 버튼
(renderPendingWorkflow에 leftPanel 옵션 추가로 공용 셸 재사용)
- B07_Quantity_Router.py 신설: POST /api/projects/{id}/quantity/confirm
-> complete_stage(4) 전이만 수행(본문 미구현), main.py 등록
- STAGE_KEYS 재정의: FILE_INPUT/PREPROCESS/PROFILE/SECTION/QUANTITY/DESIGN_DETAIL/ESTIMATION
- 스텝바 라벨·아이콘 4↔5 스왑(수량산출이 4차, 상세설계가 5차), A02 소개 문구 스왑
- 라우터 테이블에 b07-quantity 등록(플레이스홀더 제거), locale 키 5종 추가
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.7 KiB
Python
62 lines
2.7 KiB
Python
"""프로젝트 영구저장소 경로 유틸리티."""
|
|
|
|
import os
|
|
from pathlib import PurePosixPath
|
|
|
|
from config.config_system import PROJECT_STORAGE_STAGE_DIRS, STORAGE_BASE_DIR
|
|
|
|
PROJECT_STORAGE_LAYOUT_V2 = (
|
|
("B03_FileInput", "input"),
|
|
("B04_PreProcess", "processed"),
|
|
("B04_PreProcess", "models"),
|
|
("B05_Profile", "route"),
|
|
("B06_Section", "longitudinal"),
|
|
("B06_Section", "cross_sections"),
|
|
("B08_DesignDetail", "structures"),
|
|
("B07_Quantity", "quantities"),
|
|
("B09_Estimation", "v1"),
|
|
)
|
|
|
|
|
|
def get_project_stage_path(project_root: str, stage: str) -> str:
|
|
"""프로젝트 루트 아래의 허용된 워크플로우 단계 폴더를 생성한다."""
|
|
if stage not in PROJECT_STORAGE_STAGE_DIRS:
|
|
raise ValueError(f"허용되지 않은 프로젝트 저장 단계입니다: {stage}")
|
|
|
|
root = os.path.abspath(project_root)
|
|
path = os.path.abspath(os.path.join(root, stage))
|
|
if os.path.commonpath((root, path)) != root:
|
|
raise ValueError("단계 저장 경로가 프로젝트 루트를 벗어났습니다.")
|
|
|
|
os.makedirs(path, exist_ok=True)
|
|
return path
|
|
|
|
|
|
def ensure_project_storage_layout(project_root: str) -> None:
|
|
"""신규 워크플로우 저장소 하위 폴더를 누락분만 생성한다."""
|
|
root = os.path.abspath(project_root)
|
|
for stage, subdir in PROJECT_STORAGE_LAYOUT_V2:
|
|
if stage not in PROJECT_STORAGE_STAGE_DIRS:
|
|
raise ValueError(f"허용되지 않은 프로젝트 저장 단계입니다: {stage}")
|
|
path = os.path.abspath(os.path.join(root, stage, subdir))
|
|
if os.path.commonpath((root, path)) != root:
|
|
raise ValueError("단계 저장 경로가 프로젝트 루트를 벗어났습니다.")
|
|
os.makedirs(path, exist_ok=True)
|
|
|
|
|
|
def resolve_stored_project_path(relative_path: str) -> str:
|
|
"""DB의 storage 기준 상대 경로를 검증해 실제 프로젝트 경로로 변환한다."""
|
|
normalized = PurePosixPath(relative_path.replace("\\", "/"))
|
|
if normalized.is_absolute() or ".." in normalized.parts:
|
|
raise ValueError("프로젝트 저장 경로는 안전한 상대 경로여야 합니다.")
|
|
if not normalized.parts or normalized.parts[0] != "storage":
|
|
raise ValueError("프로젝트 저장 경로는 storage/로 시작해야 합니다.")
|
|
|
|
storage_root = os.path.abspath(STORAGE_BASE_DIR)
|
|
path = os.path.abspath(os.path.join(storage_root, *normalized.parts[1:]))
|
|
if os.path.commonpath((storage_root, path)) != storage_root or path == storage_root:
|
|
raise ValueError("프로젝트 저장 경로가 저장소 루트를 벗어났습니다.")
|
|
os.makedirs(path, exist_ok=True)
|
|
ensure_project_storage_layout(path)
|
|
return path
|