- FastAPI + WebSocket 백엔드 (ws_relay, webhook, messages 라우터) - GUARDiA-Bot: @bot 명령 응답 + 선제적 맥락 분석 (DB 지연, 디스크, 장애 감지) - 승인 워크플로우: 재기동/배포 SR → 승인/반려 인터랙티브 버튼 - 다크 테마 Slack형 프론트엔드 (5개 채널, 실시간 메시지) - 채널: 일반/배포/운영/PM관리/알림 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
1.8 KiB
Python
61 lines
1.8 KiB
Python
import hashlib
|
|
import hmac
|
|
import json
|
|
from datetime import datetime
|
|
from uuid import uuid4
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
|
|
router = APIRouter(prefix="/api")
|
|
|
|
WEBHOOK_SECRET = "guardia-webhook-secret-2026"
|
|
|
|
|
|
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_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):
|
|
body = await request.body()
|
|
sig = request.headers.get("X-Messenger-Signature", "")
|
|
|
|
# 개발 환경에서 서명 검증 생략 (프로덕션에서 활성화)
|
|
# if sig and not _verify(sig, body):
|
|
# raise HTTPException(status_code=403, detail="Invalid signature")
|
|
|
|
payload = json.loads(body)
|
|
|
|
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 취소되었습니다."}
|