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+ diff --git a/erpnext_github_integration/webhooks.py b/erpnext_github_integration/webhooks.py index b5f5432..b1ef16e 100644 --- a/erpnext_github_integration/webhooks.py +++ b/erpnext_github_integration/webhooks.py @@ -6,72 +6,85 @@ 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 '' - + # 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) - - event = frappe.request.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', {}) + 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 we have this repository in our system + + # 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( - '_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') 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': + 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')