CodeReview/backend/app/api/v1/endpoints/webhooks.py

81 lines
3.0 KiB
Python

from fastapi import APIRouter, Header, Request, HTTPException, Depends, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
import hmac
import hashlib
import logging
import json
from app.api.deps import get_db
from app.db.session import AsyncSessionLocal
from app.core.config import settings
from app.services.ci_service import CIService
router = APIRouter()
logger = logging.getLogger(__name__)
async def process_gitea_event(event_type: str, payload: dict):
"""
Background task to process Gitea events without blocking the webhook response.
"""
async with AsyncSessionLocal() as db:
try:
ci_service = CIService(db)
if event_type == "pull_request":
action = payload.get("action")
if action in ["opened", "synchronize", "reopened"]:
logger.info(f"Starting background PR processing for action: {action}")
await ci_service.handle_pr_event(payload)
else:
logger.info(f"Ignoring PR Action in background: {action}")
elif event_type == "issue_comment":
logger.info(f"Starting background comment processing")
await ci_service.handle_comment_event(payload)
except Exception as e:
logger.error(f"Background Webhook Processing Error: {e}", exc_info=True)
@router.get("/health")
async def webhook_health():
return {"status": "ok", "module": "webhooks"}
@router.post("/gitea")
async def handle_gitea_webhook(
request: Request,
background_tasks: BackgroundTasks,
x_gitea_signature: str = Header(None),
x_gitea_event: str = Header(None),
):
"""
Handle incoming Gitea webhook events with background processing to prevent timeouts.
"""
# 1. Capture Raw Body for Signature Verification
payload_bytes = await request.body()
# 2. Verify Signature
if settings.GITEA_WEBHOOK_SECRET:
if not x_gitea_signature:
logger.warning("Missing Gitea Signature")
raise HTTPException(status_code=401, detail="Missing Signature")
computed_signature = hmac.new(
key=settings.GITEA_WEBHOOK_SECRET.encode(),
msg=payload_bytes,
digestmod=hashlib.sha256
).hexdigest()
if not hmac.compare_digest(computed_signature, x_gitea_signature):
logger.warning(f"Invalid Gitea Signature. Expected: {computed_signature}, Got: {x_gitea_signature}")
raise HTTPException(status_code=401, detail="Invalid Signature")
# 3. Parse Payload
try:
payload = json.loads(payload_bytes.decode())
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON")
# 4. Enqueue Background Task
logger.info(f"Enqueuing Gitea Event for background processing: {x_gitea_event}")
background_tasks.add_task(process_gitea_event, x_gitea_event, payload)
# 5. Return Success Immediately
return {"status": "accepted", "event": x_gitea_event, "message": "Task enqueued"}