nikhmr1235 commited on
Commit
942a4f3
·
verified ·
1 Parent(s): c6097bd

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -0
app.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import hmac
3
+ import hashlib
4
+ from typing import Optional
5
+ from dotenv import load_dotenv
6
+ from fastapi import FastAPI, Request, HTTPException, Header, status
7
+ from pydantic import BaseModel, Field
8
+
9
+ # Load environment variables from .env file
10
+ load_dotenv()
11
+
12
+ app = FastAPI(
13
+ title="GitHub PR Reviewer Bot Webhook Listener",
14
+ description="Listens for GitHub PR events and initiates Langgraph workflows."
15
+ )
16
+
17
+ # --- Configuration ---
18
+ #GITHUB_WEBHOOK_SECRET = os.getenv("GITHUB_WEBHOOK_SECRET")
19
+ GITHUB_WEBHOOK_SECRET ="d62UMC1iQ6n5PHq9w9bSvPHiWXBqhKX4"
20
+
21
+ if not GITHUB_WEBHOOK_SECRET:
22
+ raise ValueError("GITHUB_WEBHOOK_SECRET environment variable not set. Please create a .env file.")
23
+
24
+ # --- Pydantic Models for Payload Parsing ---
25
+
26
+ class Repository(BaseModel):
27
+ id: int
28
+ full_name: str
29
+ html_url: str
30
+ clone_url: str
31
+
32
+ class PullRequestUser(BaseModel):
33
+ login: str
34
+ id: int
35
+ type: str
36
+
37
+ class PullRequest(BaseModel):
38
+ id: int
39
+ url: str
40
+ html_url: str
41
+ diff_url: str # This is the changelist URL (diff URL)
42
+ state: str
43
+ title: str
44
+ user: PullRequestUser
45
+ base: dict # Contains info about the base branch/repo
46
+ head: dict # Contains info about the head branch/repo
47
+
48
+ class GitHubWebhookPayload(BaseModel):
49
+ action: str
50
+ pull_request: PullRequest
51
+ repository: Repository
52
+ sender: PullRequestUser
53
+
54
+ # --- Helper Function for Signature Verification ---
55
+
56
+ def verify_signature(x_hub_signature_256: str, payload_body: bytes) -> bool:
57
+ """
58
+ Verifies the GitHub webhook signature.
59
+ """
60
+ if not GITHUB_WEBHOOK_SECRET:
61
+ return False # Should be caught by the initial check, but for safety
62
+
63
+ # GitHub sends x-hub-signature-256 in the format 'sha256=<signature>'
64
+ # We need to extract just the signature part
65
+ expected_signature = x_hub_signature_256.split('sha256=')[1]
66
+
67
+ # Calculate the HMAC SHA256 signature
68
+ mac = hmac.new(
69
+ GITHUB_WEBHOOK_SECRET.encode('utf-8'),
70
+ msg=payload_body,
71
+ digestmod=hashlib.sha256
72
+ )
73
+ calculated_signature = mac.hexdigest()
74
+
75
+ # Use hmac.compare_digest for constant-time comparison to prevent timing attacks
76
+ return hmac.compare_digest(calculated_signature, expected_signature)
77
+
78
+ # --- Webhook Endpoint ---
79
+
80
+ @app.post("/webhook")
81
+ async def github_webhook(
82
+ request: Request,
83
+ x_github_event: str = Header(..., alias="X-GitHub-Event"),
84
+ x_hub_signature_256: str = Header(..., alias="X-Hub-Signature-256"),
85
+ ):
86
+ """
87
+ Receives and processes GitHub webhook events for new Pull Request creation.
88
+ """
89
+ # Ensure it's a pull_request event first
90
+ if x_github_event != "pull_request":
91
+ print(f"Received non-pull_request event: {x_github_event}. Skipping.")
92
+ return {"message": f"Event type '{x_github_event}' ignored."}
93
+
94
+ payload_body = await request.body()
95
+
96
+ # 1. Signature Verification
97
+ if not verify_signature(x_hub_signature_256, payload_body):
98
+ print("Webhook signature verification failed!")
99
+ raise HTTPException(
100
+ status_code=status.HTTP_401_UNAUTHORIZED,
101
+ detail="Invalid signature"
102
+ )
103
+ print("Webhook signature verified successfully.")
104
+
105
+ # 2. Payload Parsing
106
+ try:
107
+ payload = GitHubWebhookPayload.model_validate_json(payload_body)
108
+ print("Payload parsed successfully.")
109
+ except Exception as e:
110
+ print(f"Error parsing JSON payload: {e}")
111
+ raise HTTPException(
112
+ status_code=status.HTTP_400_BAD_REQUEST,
113
+ detail=f"Invalid JSON payload: {e}"
114
+ )
115
+
116
+ # --- Filter for only 'opened' PR actions ---
117
+ if payload.action != "opened":
118
+ print(f"Received PR event with action '{payload.action}'. Only 'opened' events are processed. Skipping.")
119
+ return {"message": f"PR event action '{payload.action}' ignored."}
120
+
121
+ # If we reach here, it's an 'opened' pull_request event
122
+ print(f"New PR creation event detected! Action: {payload.action}")
123
+
124
+ # Extract crucial information
125
+ #pr_id = payload.pull_request.id
126
+ #pr_id = payload.pull_request.number
127
+ repo_id = payload.repository.id
128
+ pr_repo_name = payload.repository.full_name
129
+ changelist_url = payload.pull_request.diff_url
130
+ pr_id = int(changelist_url.split('/pull/')[1].split('.')[0])
131
+
132
+ repository_clone_url = payload.repository.clone_url
133
+ pr_action = payload.action # This will now always be "opened"
134
+ pr_title = payload.pull_request.title
135
+
136
+
137
+ print(f"Received PR event: Action={pr_action}, PR ID={pr_id}, Repo ID={repo_id}")
138
+ print(f"Changelist URL (Diff URL): {changelist_url}")
139
+ print(f"Repository Clone URL: {repository_clone_url}")
140
+ print(f"PR Title: {pr_title}")
141
+ print(f"pr_repo_name: {pr_repo_name} ")
142
+
143
+
144
+ @app.get("/")
145
+ async def read_root():
146
+ return {"message": "PR Reviewer Bot is running!"}