From a9626f6c0e951b3844a8f2d8f01b659383fdd738 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 11:22:32 +0000 Subject: [PATCH 01/10] refactor: readme minor change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35b6064..fd2808a 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ erpnext_github_integration/ ## Installation ### Prerequisites -- ERPNext v13+ or Frappe v13+ +- ERPNext v14+ or Frappe v14+ - GitHub Personal Access Token or OAuth App - Python 3.7+ From 06e438615bde271021ee56719a781ae5a17ede48 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 11:41:22 +0000 Subject: [PATCH 02/10] bug: webhook secret fetching error --- erpnext_github_integration/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index b5f5432..238e7cc 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -6,7 +6,7 @@ def github_webhook(): """Handle GitHub webhook events""" try: settings = frappe.get_single('GitHub Settings') - secret = settings.webhook_secret or frappe.conf.get('github_webhook_secret') + secret = settings.get_password("webhook_secret") or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() signature = frappe.request.headers.get('X-Hub-Signature-256') or '' From 227d25d374dd55dceba043c965f790db8bd47f1a Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 11:49:52 +0000 Subject: [PATCH 03/10] bug: App _process_github_webhook is not installed bug fix --- erpnext_github_integration/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 238e7cc..096fb18 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -40,7 +40,7 @@ def github_webhook(): # Queue background job to handle the webhook frappe.enqueue( - '_process_github_webhook', + "erpnext_github_integration.webhooks._process_github_webhook", event=event, data=data, repo_full_name=repo_full_name, From aed425d9274c0255fb47a3ee30cf24e8c101cd92 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 11:53:44 +0000 Subject: [PATCH 04/10] bug: _process_github_webhook() missing 1 required positional argument: 'event' bug fix --- erpnext_github_integration/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 096fb18..38dd798 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -53,7 +53,7 @@ def github_webhook(): frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') return {'error': str(e)} -def _process_github_webhook(event, data, repo_full_name): +def _process_github_webhook(event=None, data=None, repo_full_name=None): """Process GitHub webhook events in background""" try: if event == 'issues': From 3beb6a930265df1ac85ca6df01e1ec500286c988 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:10:24 +0000 Subject: [PATCH 05/10] bug: Unhandled webhook event: None bug fix --- erpnext_github_integration/webhooks.py | 53 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 38dd798..7e068dd 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -8,37 +8,54 @@ def github_webhook(): settings = frappe.get_single('GitHub Settings') secret = settings.get_password("webhook_secret") or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() - signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + # GitHub signature + signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + # Verify signature if secret is configured if secret: expected_signature = 'sha256=' + hmac.new( - secret.encode(), - msg=payload, + secret.encode(), + msg=payload, digestmod=hashlib.sha256 ).hexdigest() - + if not hmac.compare_digest(expected_signature, signature): - frappe.log_error('Invalid webhook signature', 'GitHub Webhook') + frappe.log_error({ + "expected": expected_signature, + "got": signature + }, 'Invalid webhook signature') frappe.throw(_('Invalid webhook signature'), exc=frappe.PermissionError) - - event = frappe.request.headers.get('X-GitHub-Event') + + # Get event type safely + event = ( + frappe.request.headers.get('X-GitHub-Event') + or frappe.request.headers.get('x-github-event') + or None + ) + + if not event: + frappe.log_error(f"Missing X-GitHub-Event header. Headers: {dict(frappe.request.headers)}", + "GitHub Webhook") + return 'ok' + + # Parse JSON payload data = json.loads(payload.decode('utf-8')) - + # Get repository info repo_info = data.get('repository', {}) repo_full_name = repo_info.get('full_name') - + if not repo_full_name: - frappe.log_error('No repository information in webhook payload', 'GitHub Webhook') + frappe.log_error(f"No repository info in payload. Event={event}", "GitHub Webhook") return 'ok' - - # Check if we have this repository in our system + + # Ensure repository exists if not frappe.db.exists('Repository', {'full_name': repo_full_name}): - frappe.log_error(f'Repository {repo_full_name} not found in system', 'GitHub Webhook') + frappe.log_error(f"Repository {repo_full_name} not found. Event={event}", "GitHub Webhook") return 'ok' - - # Queue background job to handle the webhook + + # Queue background job frappe.enqueue( "erpnext_github_integration.webhooks._process_github_webhook", event=event, @@ -46,11 +63,11 @@ def github_webhook(): repo_full_name=repo_full_name, queue='default' ) - + return 'ok' - + except Exception as e: - frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') + frappe.log_error(f"Webhook processing error: {str(e)}", "GitHub Webhook Error") return {'error': str(e)} def _process_github_webhook(event=None, data=None, repo_full_name=None): From 67714647840f16ba3c832f7f5b503fd45d155088 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:14:23 +0000 Subject: [PATCH 06/10] bug: Unhandled webhook event: None bug fix v1.0 --- erpnext_github_integration/webhooks.py | 53 ++++++++++---------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 7e068dd..0da5679 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -3,59 +3,46 @@ @frappe.whitelist(allow_guest=True) def github_webhook(): - """Handle GitHub webhook events""" try: settings = frappe.get_single('GitHub Settings') secret = settings.get_password("webhook_secret") or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() - # GitHub signature - signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + # Log headers for debugging + headers = {k: v for k, v in frappe.request.headers.items()} + frappe.log_error(json.dumps(headers, indent=2), "GitHub Webhook Headers") - # Verify signature if secret is configured + signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + if secret: expected_signature = 'sha256=' + hmac.new( secret.encode(), msg=payload, digestmod=hashlib.sha256 ).hexdigest() - if not hmac.compare_digest(expected_signature, signature): - frappe.log_error({ - "expected": expected_signature, - "got": signature - }, 'Invalid webhook signature') + frappe.log_error('Invalid webhook signature', 'GitHub Webhook') frappe.throw(_('Invalid webhook signature'), exc=frappe.PermissionError) - - # Get event type safely - event = ( - frappe.request.headers.get('X-GitHub-Event') - or frappe.request.headers.get('x-github-event') - or None - ) - + + # HERE is the problem: event might be missing + event = frappe.request.headers.get('X-GitHub-Event') if not event: - frappe.log_error(f"Missing X-GitHub-Event header. Headers: {dict(frappe.request.headers)}", - "GitHub Webhook") + frappe.log_error("No X-GitHub-Event header found", "GitHub Webhook") return 'ok' - - # Parse JSON payload + data = json.loads(payload.decode('utf-8')) - - # Get repository info + repo_info = data.get('repository', {}) repo_full_name = repo_info.get('full_name') - + if not repo_full_name: - frappe.log_error(f"No repository info in payload. Event={event}", "GitHub Webhook") + frappe.log_error('No repository information in webhook payload', 'GitHub Webhook') return 'ok' - - # Ensure repository exists + if not frappe.db.exists('Repository', {'full_name': repo_full_name}): - frappe.log_error(f"Repository {repo_full_name} not found. Event={event}", "GitHub Webhook") + frappe.log_error(f'Repository {repo_full_name} not found in system', 'GitHub Webhook') return 'ok' - - # Queue background job + frappe.enqueue( "erpnext_github_integration.webhooks._process_github_webhook", event=event, @@ -63,11 +50,11 @@ def github_webhook(): repo_full_name=repo_full_name, queue='default' ) - + return 'ok' - + except Exception as e: - frappe.log_error(f"Webhook processing error: {str(e)}", "GitHub Webhook Error") + frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') return {'error': str(e)} def _process_github_webhook(event=None, data=None, repo_full_name=None): From 8ffee7d8684b43dfceb44c1158894f698528177b Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:21:23 +0000 Subject: [PATCH 07/10] bug: Unhandled webhook event: None bug fix v2.0 --- erpnext_github_integration/webhooks.py | 31 +++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 0da5679..249c8c3 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -7,12 +7,12 @@ def github_webhook(): settings = frappe.get_single('GitHub Settings') secret = settings.get_password("webhook_secret") or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() - - # Log headers for debugging - headers = {k: v for k, v in frappe.request.headers.items()} + + # Normalize all headers to lowercase + headers = {k.lower(): v for k, v in frappe.request.headers.items()} frappe.log_error(json.dumps(headers, indent=2), "GitHub Webhook Headers") - signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + signature = headers.get('x-hub-signature-256') or headers.get('x-hub-signature') or '' if secret: expected_signature = 'sha256=' + hmac.new( @@ -21,28 +21,27 @@ def github_webhook(): digestmod=hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected_signature, signature): - frappe.log_error('Invalid webhook signature', 'GitHub Webhook') + frappe.log_error({'expected': expected_signature, 'got': signature}, 'GitHub Webhook Invalid Signature') frappe.throw(_('Invalid webhook signature'), exc=frappe.PermissionError) - - # HERE is the problem: event might be missing - event = frappe.request.headers.get('X-GitHub-Event') + + # Check for GitHub event type + event = headers.get('x-github-event') if not event: - frappe.log_error("No X-GitHub-Event header found", "GitHub Webhook") + frappe.log_error({"headers": headers}, "GitHub Webhook Missing Event") return 'ok' - + data = json.loads(payload.decode('utf-8')) - repo_info = data.get('repository', {}) repo_full_name = repo_info.get('full_name') - + if not repo_full_name: frappe.log_error('No repository information in webhook payload', 'GitHub Webhook') return 'ok' - + if not frappe.db.exists('Repository', {'full_name': repo_full_name}): frappe.log_error(f'Repository {repo_full_name} not found in system', 'GitHub Webhook') return 'ok' - + frappe.enqueue( "erpnext_github_integration.webhooks._process_github_webhook", event=event, @@ -50,9 +49,9 @@ def github_webhook(): repo_full_name=repo_full_name, queue='default' ) - + return 'ok' - + except Exception as e: frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') return {'error': str(e)} From 8ced88dbd41f5fdd70b4dc390950b7d9d77c9299 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:31:55 +0000 Subject: [PATCH 08/10] bug: Unhandled webhook event: None bug fix v3.0 --- erpnext_github_integration/webhooks.py | 45 ++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 249c8c3..c39487f 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -3,45 +3,49 @@ @frappe.whitelist(allow_guest=True) def github_webhook(): + """Handle GitHub webhook events""" try: settings = frappe.get_single('GitHub Settings') - secret = settings.get_password("webhook_secret") or frappe.conf.get('github_webhook_secret') + secret = settings.webhook_secret or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() - - # Normalize all headers to lowercase + + # Normalize headers (convert to dict with lowercase keys) headers = {k.lower(): v for k, v in frappe.request.headers.items()} + + # Debug log all headers frappe.log_error(json.dumps(headers, indent=2), "GitHub Webhook Headers") - - signature = headers.get('x-hub-signature-256') or headers.get('x-hub-signature') or '' + signature = headers.get('x-hub-signature-256') or '' + + # Verify signature if secret is configured if secret: expected_signature = 'sha256=' + hmac.new( - secret.encode(), - msg=payload, + secret.encode(), + msg=payload, digestmod=hashlib.sha256 ).hexdigest() + if not hmac.compare_digest(expected_signature, signature): - frappe.log_error({'expected': expected_signature, 'got': signature}, 'GitHub Webhook Invalid Signature') + frappe.log_error('Invalid webhook signature', 'GitHub Webhook') frappe.throw(_('Invalid webhook signature'), exc=frappe.PermissionError) - - # Check for GitHub event type + + # Safely extract event event = headers.get('x-github-event') - if not event: - frappe.log_error({"headers": headers}, "GitHub Webhook Missing Event") - return 'ok' - data = json.loads(payload.decode('utf-8')) - repo_info = data.get('repository', {}) + + # Get repository info + repo_info = data.get('repository', {}) or {} repo_full_name = repo_info.get('full_name') - + if not repo_full_name: frappe.log_error('No repository information in webhook payload', 'GitHub Webhook') return 'ok' - + if not frappe.db.exists('Repository', {'full_name': repo_full_name}): frappe.log_error(f'Repository {repo_full_name} not found in system', 'GitHub Webhook') return 'ok' - + + # Queue background job to handle the webhook frappe.enqueue( "erpnext_github_integration.webhooks._process_github_webhook", event=event, @@ -49,13 +53,14 @@ def github_webhook(): repo_full_name=repo_full_name, queue='default' ) - + return 'ok' - + except Exception as e: frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') return {'error': str(e)} + def _process_github_webhook(event=None, data=None, repo_full_name=None): """Process GitHub webhook events in background""" try: From e0c9aabce076940b405b2ed03340062dce14501e Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:33:56 +0000 Subject: [PATCH 09/10] bug: Unhandled webhook event: None bug fix v4.0 --- erpnext_github_integration/webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index c39487f..679ae50 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -6,7 +6,7 @@ def github_webhook(): """Handle GitHub webhook events""" try: settings = frappe.get_single('GitHub Settings') - secret = settings.webhook_secret or frappe.conf.get('github_webhook_secret') + secret = settings.get_password('webhook_secret') or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() # Normalize headers (convert to dict with lowercase keys) From 58b4795d161aff0ce78fc132e72be175dece97a6 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Thu, 21 Aug 2025 12:40:21 +0000 Subject: [PATCH 10/10] bug: Unhandled webhook event: None bug fix v5.0 --- erpnext_github_integration/webhooks.py | 67 ++++++++++++++------------ 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index 679ae50..b1ef16e 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -8,53 +8,52 @@ def github_webhook(): settings = frappe.get_single('GitHub Settings') secret = settings.get_password('webhook_secret') or frappe.conf.get('github_webhook_secret') payload = frappe.request.get_data() - - # Normalize headers (convert to dict with lowercase keys) - headers = {k.lower(): v for k, v in frappe.request.headers.items()} - - # Debug log all headers - frappe.log_error(json.dumps(headers, indent=2), "GitHub Webhook Headers") - - signature = headers.get('x-hub-signature-256') or '' - + signature = frappe.request.headers.get('X-Hub-Signature-256') or '' + # Verify signature if secret is configured if secret: expected_signature = 'sha256=' + hmac.new( - secret.encode(), - msg=payload, + secret.encode(), + msg=payload, digestmod=hashlib.sha256 ).hexdigest() - + if not hmac.compare_digest(expected_signature, signature): frappe.log_error('Invalid webhook signature', 'GitHub Webhook') frappe.throw(_('Invalid webhook signature'), exc=frappe.PermissionError) - - # Safely extract event - event = headers.get('x-github-event') + + # Get event name (case-insensitive headers) + event = frappe.get_request_header("X-GitHub-Event") or frappe.get_request_header("x-github-event") data = json.loads(payload.decode('utf-8')) - + # Get repository info repo_info = data.get('repository', {}) or {} repo_full_name = repo_info.get('full_name') - + if not repo_full_name: frappe.log_error('No repository information in webhook payload', 'GitHub Webhook') return 'ok' - + + # Check if repo exists in system if not frappe.db.exists('Repository', {'full_name': repo_full_name}): frappe.log_error(f'Repository {repo_full_name} not found in system', 'GitHub Webhook') return 'ok' - - # Queue background job to handle the webhook + + # Queue background job frappe.enqueue( - "erpnext_github_integration.webhooks._process_github_webhook", + "_process_github_webhook", + queue='default', event=event, data=data, repo_full_name=repo_full_name, - queue='default' + now=False # ensures it's async ) - + return 'ok' + + except Exception as e: + frappe.log_error(f'Webhook processing error: {frappe.get_traceback()}', 'GitHub Webhook Error') + return {'error': str(e)} except Exception as e: frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error') @@ -64,22 +63,28 @@ def github_webhook(): def _process_github_webhook(event=None, data=None, repo_full_name=None): """Process GitHub webhook events in background""" try: - if event == 'issues': + if not event: + frappe.log_error(json.dumps(data, indent=2), "GitHub Webhook Missing Event") + return + + event = event.lower().strip() + + if event == "issues": _handle_issues_event(data, repo_full_name) - elif event == 'pull_request': + elif event == "pull_request": _handle_pull_request_event(data, repo_full_name) - elif event == 'push': + elif event == "push": _handle_push_event(data, repo_full_name) - elif event == 'member': + elif event == "member": _handle_member_event(data, repo_full_name) - elif event == 'repository': + elif event == "repository": _handle_repository_event(data, repo_full_name) else: - frappe.log_error(f'Unhandled webhook event: {event}', 'GitHub Webhook') + frappe.log_error(f"Unhandled webhook event: {event}", "GitHub Webhook") except Exception as e: - frappe.log_error(f'Error processing {event} webhook: {str(e)}', 'GitHub Webhook Handler') - + frappe.log_error(f"Error processing {event} webhook: {str(e)}", "GitHub Webhook Handler") + def _handle_issues_event(data, repo_full_name): """Handle GitHub issues webhook events""" action = data.get('action')