mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
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]>
65 lines
2.6 KiB
Python
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)
|