feat(api): Phase 13.1 #74 GitHub Webhook → OpenClaw 整合
- POST /api/v1/webhooks/github endpoint - 處理 pull_request 和 push 事件 - 驗證 X-Hub-Signature-256 - Telegram 通知整合 - GitHubWebhookService 封裝 Redis 操作 (leWOOOgo 合規) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
121
apps/api/src/services/github_webhook_service.py
Normal file
121
apps/api/src/services/github_webhook_service.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
GitHub Webhook Service - Phase 13.1
|
||||
====================================
|
||||
封裝 GitHub Webhook 相關的 Redis 操作
|
||||
|
||||
遵循 leWOOOgo 積木化原則:
|
||||
- Router 層不直接存取 Redis
|
||||
- 透過 Service 層封裝資料存取邏輯
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Protocol
|
||||
|
||||
import structlog
|
||||
|
||||
from src.core.redis_client import get_redis
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
# Redis TTL: 7 天
|
||||
GITHUB_REVIEW_TTL_SECONDS = 7 * 24 * 60 * 60
|
||||
|
||||
|
||||
class IGitHubReviewRepository(Protocol):
|
||||
"""GitHub Review Repository Interface"""
|
||||
|
||||
async def save_review(self, review_id: str, review_data: dict) -> bool:
|
||||
"""儲存審查結果"""
|
||||
...
|
||||
|
||||
async def get_review(self, review_id: str) -> dict | None:
|
||||
"""取得審查結果"""
|
||||
...
|
||||
|
||||
|
||||
class GitHubReviewRedisRepository:
|
||||
"""Redis 實作的 GitHub Review Repository"""
|
||||
|
||||
KEY_PREFIX = "github_review:"
|
||||
|
||||
async def save_review(self, review_id: str, review_data: dict) -> bool:
|
||||
"""
|
||||
儲存審查結果到 Redis
|
||||
|
||||
Args:
|
||||
review_id: 審查 ID
|
||||
review_data: 審查結果資料
|
||||
|
||||
Returns:
|
||||
bool: 儲存是否成功
|
||||
"""
|
||||
try:
|
||||
redis_client = get_redis()
|
||||
key = f"{self.KEY_PREFIX}{review_id}"
|
||||
await redis_client.set(
|
||||
key,
|
||||
json.dumps(review_data, ensure_ascii=False),
|
||||
ex=GITHUB_REVIEW_TTL_SECONDS,
|
||||
)
|
||||
logger.debug("github_review_saved", review_id=review_id)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("github_review_save_failed", review_id=review_id, error=str(e))
|
||||
return False
|
||||
|
||||
async def get_review(self, review_id: str) -> dict | None:
|
||||
"""
|
||||
取得審查結果
|
||||
|
||||
Args:
|
||||
review_id: 審查 ID
|
||||
|
||||
Returns:
|
||||
審查結果資料,或 None
|
||||
"""
|
||||
try:
|
||||
redis_client = get_redis()
|
||||
key = f"{self.KEY_PREFIX}{review_id}"
|
||||
result = await redis_client.get(key)
|
||||
if result:
|
||||
return json.loads(result)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error("github_review_get_failed", review_id=review_id, error=str(e))
|
||||
return None
|
||||
|
||||
|
||||
class GitHubWebhookService:
|
||||
"""
|
||||
GitHub Webhook 服務
|
||||
|
||||
封裝審查結果的儲存與查詢
|
||||
"""
|
||||
|
||||
def __init__(self, repository: IGitHubReviewRepository | None = None):
|
||||
self._repository = repository or GitHubReviewRedisRepository()
|
||||
|
||||
async def save_review_result(
|
||||
self,
|
||||
review_id: str,
|
||||
review_data: dict,
|
||||
) -> bool:
|
||||
"""儲存審查結果"""
|
||||
return await self._repository.save_review(review_id, review_data)
|
||||
|
||||
async def get_review_result(self, review_id: str) -> dict | None:
|
||||
"""取得審查結果"""
|
||||
return await self._repository.get_review(review_id)
|
||||
|
||||
|
||||
# 單例
|
||||
_service: GitHubWebhookService | None = None
|
||||
|
||||
|
||||
def get_github_webhook_service() -> GitHubWebhookService:
|
||||
"""取得 GitHubWebhookService 單例"""
|
||||
global _service
|
||||
if _service is None:
|
||||
_service = GitHubWebhookService()
|
||||
return _service
|
||||
Reference in New Issue
Block a user