# -*- coding: utf-8 -*- """파일 입출력 끼워 넣기 API — STC 검사 · 복사등록 · 해당분만 압축 · 새 공사 · 백업 목록/복원. 근거: 24_모듈구조_전수.md(ST_U_Archive Chk_STC_Ver·ComP_Data · wM_D_Restore) · 08번 §2 · 16번 §4.6 · 27번 §2 쓰기는 tools/stmate-test/work_dbf/ 아래만(server.WORK_DBF_ROOT). """ from __future__ import annotations import pathlib from fastapi import APIRouter, HTTPException from pydantic import BaseModel from stc_archive import build_stc, check_stc from stc_project import ( copy_register, list_backups, new_project, pack_selected, safe_name, write_output, ) from stc_read import Source router = APIRouter() def _root() -> pathlib.Path: import server # 실행 중 바뀔 수 있는 작업 폴더(시험은 임시 폴더로 바꿈) return server.WORK_DBF_ROOT def _src(path: str) -> Source: try: return Source(path) except FileNotFoundError: raise HTTPException(404, f"경로 없음: {path}") except ValueError as e: raise HTTPException(400, str(e)) def _guard(fn, *a, **k): try: return fn(*a, **k) except (ValueError, KeyError) as e: raise HTTPException(400, str(e)) class PathReq(BaseModel): source_path: str class CopyRegReq(BaseModel): source_path: str src_pcode: str = "11" mode: str = "copy" # copy · change · contract pname: str = "" rates: dict = {} # contract — {"M": %, "L": %, "S": %} exclude_recnos: list[int] = [] target_folder_name: str = "" class PackReq(BaseModel): source_path: str pcode: str = "11" target_name: str = "" class NewPrjReq(BaseModel): template_path: str pname: str pnum: str = "" target_folder_name: str = "" class RestoreReq(BaseModel): backup_path: str target_folder_name: str @router.post("/api/file/check") def api_file_check(req: PathReq): """Chk_STC_Ver 대응 — ZIP·필수 엔트리·DBF 머리·SMT 서명·판 추정.""" s = _src(req.source_path) b = s.path.read_bytes() if s.kind == "stc" else build_stc([], s.files) return {"kind": s.kind, **check_stc(b)} @router.post("/api/file/copy-register") def api_file_copy_register(req: CopyRegReq): s = _src(req.source_path) files, pcode, changes = _guard( copy_register, s.files, req.src_pcode, req.mode, req.pname, req.rates, req.exclude_recnos, ) target = req.target_folder_name or f"{s.path.stem}_복사{pcode}" names = dict(s.names, **{f"BOQ{pcode}.DBF": f"BOQ{pcode}.DBF"}) res = write_output(_root(), target, files, names, s.entries) return {**res, "new_pcode": pcode, "change_count": len(changes)} @router.post("/api/file/pack") def api_file_pack(req: PackReq): """해당분만 압축 — 고른 공사(PCODE)만 담은 STC 를 work_dbf/ 에 만듦.""" s = _src(req.source_path) files = _guard(pack_selected, s.files, req.pcode) name = safe_name(req.target_name or f"{s.path.stem}_{req.pcode}") out = _root() / f"{name}.STC" out.parent.mkdir(parents=True, exist_ok=True) out.write_bytes(build_stc(s.entries, files)) return {"ok": True, "saved_stc": str(out), "stc_size": out.stat().st_size} @router.post("/api/file/new") def api_file_new(req: NewPrjReq): s = _src(req.template_path) files = _guard(new_project, s.files, req.pname, req.pnum) res = write_output(_root(), req.target_folder_name or req.pname, files, s.names, s.entries) return res @router.get("/api/file/backups") def api_file_backups(name: str = ""): return {"backups": list_backups(_root(), safe_name(name) if name else "")} @router.post("/api/file/restore") def api_file_restore(req: RestoreReq): """wM_D_Restore 대응 — 백업 STC 를 작업본으로 되돌림(지금 작업본은 다시 백업).""" p = pathlib.Path(req.backup_path).resolve() if _root().resolve() not in p.parents or not p.exists(): raise HTTPException(400, "작업 폴더 백업만 복원") s = _src(str(p)) return write_output(_root(), req.target_folder_name, s.files, s.names, s.entries)