"""Gitea webhook receiver per §4.1. Both the webhook receiver and the reconciler are §4.1 cache writers. On a meaningful event — meta-repo push or PR change — we re-read just what changed from Gitea and update the cache. The signature is verified against the configured shared secret so spurious POSTs cannot poison the cache. """ from __future__ import annotations import hashlib import hmac import logging from fastapi import APIRouter, Header, HTTPException, Request from . import cache from .config import Config from .gitea import Gitea log = logging.getLogger(__name__) EVENTS_OF_INTEREST = { "push", # meta-repo or RFC-repo commits "pull_request", # opened / closed / merged "create", # branch or repo created "delete", # branch deleted "repository", # repo created or deleted } def make_router(config: Config, gitea: Gitea) -> APIRouter: router = APIRouter() @router.post("/api/webhooks/gitea") async def receive( request: Request, x_gitea_event: str = Header(default=""), x_gitea_signature: str = Header(default=""), ): body = await request.body() if config.webhook_secret: if not _verify_signature(body, x_gitea_signature, config.webhook_secret): raise HTTPException(status_code=401, detail="Invalid signature") event = x_gitea_event.lower() if event not in EVENTS_OF_INTEREST: return {"ok": True, "ignored": event} # Slice 1 only acts on meta-repo events; per-RFC-repo events # land in their respective slices. The handler is generous in # what it accepts — any meta-repo change is a cue to refresh # the whole meta-repo cache, since the cache is small and the # refresh is idempotent. try: await cache.refresh_meta_repo(config, gitea) await cache.refresh_meta_pulls(config, gitea) except Exception: log.exception("webhook refresh failed") raise HTTPException(status_code=500, detail="Refresh failed") return {"ok": True} return router def _verify_signature(body: bytes, header: str, secret: str) -> bool: if not header: return False expected = hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)