import hashlib import hmac import json from datetime import datetime from uuid import uuid4 from fastapi import APIRouter, HTTPException, Request # ws_relay의 broadcast 함수 임포트 (순환 참조 방지를 위해 지연 임포트) router = APIRouter(prefix="/api") WEBHOOK_SECRET = "guardia-webhook-secret-2026" ITSM_URL = "http://localhost:8001" # ITSM 서버 주소 TYPE_KR = {"DEPLOY":"배포", "RESTART":"재기동", "LOG":"로그 분석", "INQUIRY":"문의", "OTHER":"기타"} def _verify(signature: str, body: bytes) -> bool: expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) def _new_msg_id() -> str: return f"MSG-{datetime.now().strftime('%Y%m%d')}-{str(uuid4())[:8].upper()}" def _new_sr() -> str: return f"SR-{datetime.now().strftime('%Y%m%d')}-{str(uuid4())[:6].upper()}" @router.post("/messenger/webhook") async def receive_webhook(request: Request): from routers.ws_relay import broadcast, room_channels from core.bot import store_message body = await request.body() payload = json.loads(body) # ── ITSM 완료 이벤트 처리 ───────────────────────────────────────── if payload.get("event") == "itsm_complete": room = payload.get("room", "ops") sr_id = payload.get("sr_id", "") title = payload.get("title", "SR 처리 완료") s_type = TYPE_KR.get(payload.get("sr_type",""), payload.get("sr_type","")) req_by = payload.get("requested_by", "고객") server = payload.get("target_server", "—") summary = payload.get("result_summary", "처리 완료") content = ( f"✅ **{title}** 처리 완료\n" f"• SR: `{sr_id}` 유형: {s_type}\n" f"• 요청자: {req_by} 대상: {server}\n" f"• 결과: {summary}\n\n" f"서비스에 만족하셨나요? 아래에서 평가해 주세요." ) bot_msg = { "message_id": _new_msg_id(), "timestamp": datetime.now().isoformat(), "room_id": room, "sender": "GUARDiA-Bot", "sender_type": "BOT", "msg_type": "CHAT", "content": content, "is_widget": True, "interactive_action": { "type": "STAR_RATING", "sr_id": sr_id, "customer": req_by, "itsm_url": ITSM_URL, }, } store_message(room, bot_msg) await broadcast(room, bot_msg) return {"status": "NOTIFIED", "room": room, "sr_id": sr_id} # ── 일반 웹훅 (기존 로직) ───────────────────────────────────────── if payload.get("bot") or not payload.get("text", "").strip(): return {"status": "IGNORED"} sr_id = _new_sr() return {"status": "ACCEPTED", "sr_id": sr_id} @router.post("/sr/approve/{sr_id}") async def approve_sr(sr_id: str): return {"status": "APPROVED", "sr_id": sr_id, "message": f"SR {sr_id} 승인 완료 — 실행을 시작합니다."} @router.post("/sr/reject/{sr_id}") async def reject_sr(sr_id: str): return {"status": "REJECTED", "sr_id": sr_id, "message": f"SR {sr_id} 반려 처리되었습니다."} @router.post("/killswitch/confirm") async def killswitch_confirm(): return {"status": "STOPPED", "message": "모든 진행 중인 작업이 중단되었습니다."} @router.post("/killswitch/cancel") async def killswitch_cancel(): return {"status": "CANCELLED", "message": "Kill-Switch 취소되었습니다."}