- Move backend/frontend/messenger/ old paths to _archive/ - Reorganize scripts into scripts/deploy, check, push, setup, misc - Move docs (pptx, docx) to docs/ - Add .claude agents and skills for fullstack/folder-cleanup harness - workspace/ projects remain intact Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.7 KiB
Python
106 lines
3.7 KiB
Python
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 취소되었습니다."}
|