"""성장일지 — 매뉴얼 자동 업데이트 (라우터 변경 감지 → MD 재생성).""" from __future__ import annotations import logging from datetime import datetime from pathlib import Path from fastapi import APIRouter, BackgroundTasks, Depends from sqlalchemy.ext.asyncio import AsyncSession from core.auth import get_current_user, require_admin_role from database import get_db from models import User logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/manual-update", tags=["성장일지-매뉴얼"]) MANUAL_DIR = Path("/opt/guardia/workspace/guardia-docs") ITSM_DIR = Path("/opt/guardia/workspace/guardia-itsm") def _collect_router_endpoints() -> list[dict]: """routers/ 디렉토리에서 엔드포인트 목록 추출.""" endpoints = [] router_dir = ITSM_DIR / "routers" if not router_dir.exists(): return endpoints for py_file in sorted(router_dir.glob("*.py")): if py_file.name.startswith("_"): continue try: content = py_file.read_text(encoding="utf-8") for line in content.splitlines(): stripped = line.strip() for method in ("@router.get", "@router.post", "@router.put", "@router.delete", "@router.patch"): if stripped.startswith(method): path = stripped.split('"')[1] if '"' in stripped else stripped.split("'")[1] if "'" in stripped else "?" endpoints.append({"file": py_file.name, "method": method.split(".")[1].upper(), "path": path}) except Exception: pass return endpoints def _build_api_md(endpoints: list[dict]) -> str: now = datetime.now().strftime("%Y-%m-%d %H:%M") lines = [ f"# GUARDiA ITSM API 레퍼런스\n\n> 자동 생성: {now}\n", f"총 {len(endpoints)}개 엔드포인트\n", "| 파일 | Method | Path |", "|------|--------|------|", ] for ep in endpoints: lines.append(f"| {ep['file']} | {ep['method']} | `{ep['path']}` |") return "\n".join(lines) async def _do_update(): endpoints = _collect_router_endpoints() md = _build_api_md(endpoints) out_file = MANUAL_DIR / "api-reference-auto.md" try: out_file.parent.mkdir(parents=True, exist_ok=True) out_file.write_text(md, encoding="utf-8") logger.info("매뉴얼 업데이트 완료: %s (%d개)", out_file, len(endpoints)) except Exception as e: logger.error("매뉴얼 업데이트 실패: %s", e) return len(endpoints) @router.post("/run") async def run_update( background_tasks: BackgroundTasks, user: User = Depends(require_admin_role), ): """라우터 분석 → api-reference-auto.md 재생성.""" background_tasks.add_task(_do_update) return {"ok": True, "message": "매뉴얼 업데이트 시작됨 (백그라운드)"} @router.get("/preview") async def preview(user: User = Depends(get_current_user)): """현재 엔드포인트 목록 미리보기 (저장 없음).""" endpoints = _collect_router_endpoints() return { "total": len(endpoints), "endpoints": endpoints[:50], "note": "최대 50개 미리보기", } @router.get("/status") async def status(user: User = Depends(get_current_user)): """마지막 자동 업데이트 파일 정보.""" out_file = MANUAL_DIR / "api-reference-auto.md" if out_file.exists(): stat = out_file.stat() return { "exists": True, "path": str(out_file), "size_kb": round(stat.st_size / 1024, 1), "modified": datetime.fromtimestamp(stat.st_mtime).isoformat(), } return {"exists": False}