feat(M02): 개인 · 회사 층 양식 지우기 — DELETE /layers/{층}/templates/{종류}/{이름} · 개인 본인만 · 회사 관리자만 · 시스템 · 프로젝트 거절 · 판 다르면 409 · 이름 검사 400 + 까닭

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LuapLYqN1GGFD8Y1PStD5
This commit is contained in:
2026-09-25 16:52:52 +09:00
co-authored by Claude Sonnet 5
parent 2aa4c80577
commit 576b012eae
3 changed files with 96 additions and 2 deletions
@@ -279,6 +279,39 @@ async def save_layer_template(
return {"종류": kind, "이름": name, "판": version, "층": layer}
@router.delete("/layers/{layer}/templates/{kind}/{name}")
async def delete_layer_template(
layer: Layer,
kind: str,
name: str,
판: str | None = Query(None),
project_id: str | None = Query(None),
session: dict[str, Any] = Depends(verify_session),
) -> dict[str, Any]:
"""개인 = 본인만 · 회사 = 회사 관리자만 · 시스템 · 프로젝트 층은 거절 · 판이 다르면 409."""
if layer in ("system", "project"):
raise HTTPException(status_code=403, detail="개인 · 회사 층 양식만 지울 수 있습니다.")
folder = await _layer_dir(session, layer, project_id=project_id, write=True)
try:
layers.check_kind(kind)
layers.check_name(name)
except ValueError as error:
raise _bad(error) from error
if not 판:
raise HTTPException(status_code=400, detail="지울 양식의 판이 필요합니다.")
try:
found = await asyncio.to_thread(
layers.delete_template, folder, kind, name, version=판, check_version=True
)
except layers.StaleTemplate as error:
raise HTTPException(
status_code=409, detail={"message": str(error), "판": error.current}
) from error
if not found:
raise HTTPException(status_code=404, detail="양식을 찾을 수 없습니다.")
return {"종류": kind, "이름": name, "층": layer, "지움": True}
# ── 프로젝트 길 ───────────────────────────────────────
+15 -2
View File
@@ -100,7 +100,10 @@ def check_name(name: str) -> str:
text = str(name or "").strip()
bad = not text or text != name or text.startswith((".", "_")) or len(text) > 80
if bad or any(ch in text for ch in '/\\:*?"<>|') or ".." in text:
raise ValueError("양식 이름이 올바르지 않습니다.")
raise ValueError(
"양식 이름이 올바르지 않습니다 — 빈 이름 · 앞뒤 빈칸 · 80자 초과 · 앞머리 . _ · "
'`/ \ .. : * ? " < > |` 는 못 씁니다.'
)
return text
@@ -189,10 +192,20 @@ def write_template(
return version_of(path) or ""
def delete_template(layer_dir: str | Path, kind: str, name: str) -> bool:
def delete_template(
layer_dir: str | Path,
kind: str,
name: str,
*,
version: str | None = None,
check_version: bool = False,
) -> bool:
"""양식 지움 · manifest 줄도 뺌. `check_version` 이면 지금 판과 달라 `StaleTemplate`."""
path = template_path(layer_dir, kind, name)
if not path.is_file():
return False
if check_version and version_of(path) != version:
raise StaleTemplate(version_of(path))
path.unlink()
manifest = read_manifest(layer_dir)
if manifest.pop(f"{kind}/{name}", None) is not None: