Files
awoooi/apps/api/src/services/github_api_service.py
OG T 8313a3787b
Some checks failed
E2E Health Check / e2e-health (push) Has been cancelled
refactor(api): Phase 22 P0 leWOOOgo 模組化修復
Router 層禁止直接 httpx.AsyncClient,抽取到 Service 層:

新增 Services:
- OpenClawHttpService: Error 分析/Code Review/CI 診斷
- GitHubApiService: PR Diff 取得
- HealthCheckService: HTTP/PostgreSQL/Redis 健康檢查

修改 Routers:
- sentry_webhook.py: 使用 OpenClawHttpService
- github_webhook.py: 使用 GitHubApiService + OpenClawHttpService
- health.py: 使用 HealthCheckService

遵循規範:
- Skill 09: Router 層禁止直接外部 API 呼叫
- feedback_lewooogo_modular_enforcement.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-31 16:06:35 +08:00

118 lines
3.3 KiB
Python

"""
GitHub API Service - 統一 GitHub API 呼叫
=========================================
Phase 22 P0 修復: Router 層禁止直接 httpx.AsyncClient
遵循規範:
- Skill 09: Router 層禁止直接外部 API 呼叫
- feedback_lewooogo_modular_enforcement.md: Service 層封裝
功能:
- Fetch PR Diff (取得 PR 差異內容)
- 未來擴展: PR Comments, Status Checks, etc.
版本: v1.0
建立: 2026-03-31 (台北時區)
建立者: Claude Code (首席架構師 P0 修復)
"""
import httpx
import structlog
logger = structlog.get_logger(__name__)
# =============================================================================
# Constants
# =============================================================================
# 最大 diff 字元數 (約 12500 tokens)
MAX_DIFF_CHARS = 50000
# =============================================================================
# GitHub API Service
# =============================================================================
class GitHubApiService:
"""
GitHub API Service
統一 GitHub API 呼叫,符合 leWOOOgo 積木化原則
2026-03-31 Claude Code (Phase 22 P0 修復)
"""
def __init__(
self,
default_timeout: float = 30.0,
max_diff_chars: int = MAX_DIFF_CHARS,
):
self._default_timeout = default_timeout
self._max_diff_chars = max_diff_chars
async def fetch_pr_diff(
self,
diff_url: str,
timeout: float | None = None,
) -> str:
"""
取得 PR diff 內容
Args:
diff_url: GitHub diff URL
timeout: 超時秒數 (預設 30s)
Returns:
str: diff 內容 (超過限制會截斷)
"""
try:
async with httpx.AsyncClient(
timeout=timeout or self._default_timeout
) as client:
response = await client.get(
diff_url,
headers={"Accept": "application/vnd.github.v3.diff"},
)
if response.status_code == 200:
diff_content = response.text
# 限制 diff 大小 (避免 LLM token 過多)
if len(diff_content) > self._max_diff_chars:
diff_content = (
diff_content[: self._max_diff_chars] + "\n... (truncated)"
)
return diff_content
else:
logger.warning(
"github_diff_fetch_failed",
url=diff_url,
status=response.status_code,
)
return ""
except httpx.TimeoutException:
logger.warning("github_diff_fetch_timeout", url=diff_url)
return ""
except Exception as e:
logger.warning("github_diff_fetch_error", error=str(e))
return ""
# =============================================================================
# Singleton
# =============================================================================
_github_api_service: GitHubApiService | None = None
def get_github_api_service() -> GitHubApiService:
"""取得 GitHubApiService singleton"""
global _github_api_service
if _github_api_service is None:
_github_api_service = GitHubApiService()
return _github_api_service