Files
zeekayandHanzo Dev b497511c04 no hogs: stamphog and review_hog are stamp and review
Both are Django apps, and both pin `label` in apps.py — "stamp" and "review".
django_migrations keys a row on (app_label, name), and app_label comes from that
pin and not from the import path, so moving the directory does not orphan a
single migration. All six stay where they are, untouched.

  products/stamphog -> products/stamp     name = products.stamp.backend
  products/review_hog -> products/review  name = products.review.backend
  StamphogScene/ -> StampScene/, and stamphogSceneLogic.ts with it

189 files follow. An earlier attempt at this move failed twice for reasons worth
recording: `git mv A B` where B exists puts A INSIDE B, and a stray
products/stamp/node_modules from an abandoned run was enough to make B exist —
the app ended up nested two deep. And `git ls-files` does not list a file moved
with plain mv and not yet staged, so a pass over "every tracked file" silently
skips it; that is how StampScene.tsx kept importing ./stampSceneLogic after the
file beside it had been renamed back.

bin/check-imports-are-committed: all first-party imports across 15,843 files
resolve.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:18:18 -07:00

65 lines
2.6 KiB
Python

"""Inbound webhook for the dedicated Stamp GitHub App.
This is a standalone endpoint for Stamp's own GitHub App — it does not share the
unified ``insights.urls.github_webhook`` fan-out. The integration layer wires the URL.
"""
import hmac
import json
import hashlib
from typing import Any, cast
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from django.views.decorators.csrf import csrf_exempt
import structlog
from products.stamp.backend.tasks.tasks import process_installation_event, process_pull_request_event
logger = structlog.get_logger(__name__)
# Installation lifecycle events keep the repo-config rows in step with GitHub (repos added/removed,
# app uninstalled) so newly installed repos appear in the toggle list without a manual re-sync.
_INSTALLATION_EVENTS = frozenset({"installation", "installation_repositories"})
def _verify_signature(body: bytes, signature: str | None, secret: str) -> bool:
"""Constant-time check of the ``X-Hub-Signature-256`` header (``sha256=<hex>``)."""
if not signature or not signature.startswith("sha256="):
return False
expected = "sha256=" + hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@csrf_exempt
def stamp_github_webhook(request: HttpRequest) -> HttpResponse:
"""Verify, filter, and enqueue an inbound Stamp GitHub App delivery."""
if request.method != "POST":
return HttpResponse(status=405)
secret = getattr(settings, "STAMPFN_GITHUB_APP_WEBHOOK_SECRET", "")
if not secret:
logger.error("stamp_webhook_not_configured")
return HttpResponse("Webhook not configured", status=500)
signature = request.headers.get("X-Hub-Signature-256")
if not _verify_signature(request.body, signature, secret):
return HttpResponse("Invalid signature", status=403)
# pull_request drives reviews; installation lifecycle events maintain the repo-config rows.
# Ack everything else so GitHub stops retrying.
event_type = request.headers.get("X-GitHub-Event", "")
if event_type != "pull_request" and event_type not in _INSTALLATION_EVENTS:
return HttpResponse(status=200)
try:
payload = json.loads(request.body)
except json.JSONDecodeError:
return HttpResponse("Invalid JSON", status=400)
delivery_id = request.headers.get("X-GitHub-Delivery", "")
task = process_installation_event if event_type in _INSTALLATION_EVENTS else process_pull_request_event
cast(Any, task).delay(payload=payload, delivery_id=delivery_id)
return HttpResponse(status=202)