When a PR is merged that has an attached issue with a bounty, auto pay it
Use the the GitHub sponsors api for now hardcode it to DonnieBLT. Use the BLT action GitHub action to send the closed issue link to /bounty_payout
From the issue, determine the username, check if user profile GitHub matches, then check if a preferred payment method field exists (sponsors or bch) for this task only handle the sponsors method. Then send the payment based to the GitHub issue label amount
@coderabbitai please code this
[!TIP] For best results, initiate chat on the files or code changes.
🧩 Analysis chain
I'll help you implement the automatic bounty payout feature as described. First, let's explore the project structure to understand where our implementation should fit.
🏁 Script executed:
#!/bin/bash
# Check if there's a GitHub workflow directory
find . -type d -name ".github" | grep -v "node_modules"
# Look for any existing GitHub actions files
find . -name "*.yml" -o -name "*.yaml" | grep -v "node_modules" | grep -v "vendor"
# Look for relevant Python files that might handle API interactions
find . -name "*.py" | grep -i "api\|github\|bounty\|payment" | grep -v "node_modules" | grep -v "__pycache__"
# Check for any existing actions in the repo
find . -name "action.yml" -o -name "action.yaml"
Length of output: 1794
Now, let's examine the project structure in more detail to understand the existing codebase better.
🏁 Script executed:
#!/bin/bash
# Check the API views file to understand existing endpoints
cat ./website/api/views.py | head -n 50
# Look for existing GitHub integration code
find . -name "*.py" | xargs grep -l "github" | head -n 10
# Check the GitHub issue model
grep -r "class GitHubIssue" --include="*.py" .
# Check the migration that adds sponsors_tx_id
cat ./website/migrations/0224_githubissue_sponsors_tx_id.py
Length of output: 2693
🏁 Script executed:
#!/bin/bash
# Check the GitHubIssue model
grep -A 50 "class GitHubIssue" ./website/models.py
# Look for existing GitHub webhook handling
grep -r "webhook" --include="*.py" .
# Check if there's any existing bounty payout functionality
grep -r "bounty_payout\|sponsors\|payment" --include="*.py" .
# Check existing GitHub workflows to understand the CI/CD pipeline
cat ./.github/workflows/ci-cd.yml | head -n 30
Length of output: 7814
Let's continue exploring more relevant files to understand how GitHub issues and bounty payments are currently handled.
🏁 Script executed:
#!/bin/bash
# Check the GitHub webhook handler
cat ./website/views/user.py | grep -A 50 "def github_webhook"
# Look at any issue or PR related views
grep -r "def" --include="*.py" ./website/views/issue.py | head -n 20
# Check if there's a BLT action already
find . -name "*.py" | xargs grep -l "BLT action" || echo "No existing BLT action references found"
# Check for any existing GitHub API integration
grep -r "github.com/api" --include="*.py" . || echo "No direct GitHub API references found"
# Check for user profile model to understand payment preferences
grep -A 20 "class UserProfile" ./website/models.py
Length of output: 4454
🏁 Script executed:
#!/bin/bash
# Check the GitHub issue event handler
cat ./website/views/user.py | grep -A 50 "def handle_issue_event"
# Check the Contributor model
grep -A 20 "class Contributor" ./website/models.py
# Look for bounty related code
grep -r "bounty" --include="*.py" ./website/views/
# Check for existing endpoints that might be related to bounty payouts
grep -r "def bounty_payout" --include="*.py" . || echo "No existing bounty_payout endpoint found"
# Look for GitHub API interactions
grep -r "github.com" --include="*.py" ./website/ | grep -v "github.com/login" | head -n 20
Length of output: 7367
Let's continue examining the codebase to understand the GitHub API interactions better.
🏁 Script executed:
#!/bin/bash
# Check for any existing GitHub API usage related to sponsors
grep -r "sponsors" --include="*.py" ./website/
# Examine URL patterns to find where we should add our new endpoint
cat ./blt/urls.py | head -n 50
# Look at how GitHub issue details are processed
grep -r "parse_github_issue" --include="*.py" ./website/ || echo "No GitHub issue parsing function found"
# Check for any existing dollar amount parsing from issue labels
grep -r "dollar" --include="*.py" ./website/
Length of output: 6255
Now, let's check how the issue is linked to a PR in this codebase:
🏁 Script executed:
#!/bin/bash
# Check for code that links PRs to issues
grep -r "closes\|fixes\|resolves" --include="*.py" ./website/views/ || echo "No PR-issue linking found in views"
# Look for code that handles merged PRs
grep -r "merged.*pr\|pr.*merged" --include="*.py" ./website/ || echo "No code handling merged PRs found"
# Let's look at the handle_pull_request_event function more closely
cat ./website/views/user.py | grep -A 30 "def handle_pull_request_event"
# Check the URL patterns to understand where to add our new endpoint
cat ./blt/urls.py | grep -A 20 "path(\"api/"
Length of output: 11570
@DonnieBLT I'll implement the automatic bounty payout feature. Based on my analysis of the codebase, here's what I'll create:
- First, let's create a new GitHub Action to run when a PR is merged:
# .github/workflows/bounty-payout.yml
name: Bounty Payout
on:
pull_request:
types: [closed]
jobs:
process-bounty:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Check for linked issues with bounties
id: check-issues
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const prBody = pr.body || '';
// Look for issue closing keywords in PR body
const issueRefs = prBody.match(/(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)/gi);
if (!issueRefs) {
console.log('No issue references found in PR');
return;
}
// Extract issue numbers
const issueNumbers = issueRefs.map(ref => {
const match = ref.match(/#(\d+)/);
return match ? match[1] : null;
}).filter(Boolean);
if (issueNumbers.length === 0) {
console.log('No valid issue numbers found');
return;
}
console.log(`Found issue references: ${issueNumbers.join(', ')}`);
// Check each issue for bounty labels
let bountyIssue = null;
for (const issueNumber of issueNumbers) {
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(issueNumber)
});
// Check for dollar sign labels
const hasBountyLabel = issue.data.labels.some(label => {
return label.name.includes('$');
});
if (hasBountyLabel) {
bountyIssue = issue.data;
break;
}
}
if (!bountyIssue) {
console.log('No issues with bounty labels found');
return;
}
// Find the bounty amount from the label
const bountyLabel = bountyIssue.labels.find(label => label.name.includes('$'));
const bountyAmount = bountyLabel ? bountyLabel.name.match(/\$(\d+)/)[1] : null;
if (!bountyAmount) {
console.log('Could not extract bounty amount from label');
return;
}
console.log(`Found bounty issue #${bountyIssue.number} with amount $${bountyAmount}`);
core.setOutput('has-bounty', 'true');
core.setOutput('issue-number', bountyIssue.number.toString());
core.setOutput('bounty-amount', bountyAmount);
core.setOutput('contributor-username', pr.user.login);
- name: Send bounty data to BLT
if: steps.check-issues.outputs.has-bounty == 'true'
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issueNumber = steps.check-issues.outputs['issue-number'];
const bountyAmount = steps.check-issues.outputs['bounty-amount'];
const contributorUsername = steps.check-issues.outputs['contributor-username'];
// Get the API base URL (for local development vs production)
const apiBaseUrl = process.env.API_BASE_URL || 'https://owasp.org';
// Send data to the BLT endpoint
const axios = require('axios');
try {
const response = await axios.post(`${apiBaseUrl}/bounty_payout/`, {
issue_number: parseInt(issueNumber),
repo: context.repo.repo,
owner: context.repo.owner,
bounty_amount: parseInt(bountyAmount),
contributor_username: contributorUsername,
pr_number: context.payload.pull_request.number,
token: process.env.BLT_API_TOKEN
});
console.log(`Bounty payout request sent: ${response.status}`);
console.log(response.data);
} catch (error) {
console.error('Error sending bounty payout request:', error.message);
if (error.response) {
console.error('Response data:', error.response.data);
}
}
env:
BLT_API_TOKEN: ${{ secrets.BLT_API_TOKEN }}
API_BASE_URL: ${{ secrets.API_BASE_URL }}
- Now let's create the Django API endpoint to process the bounty payout:
# website/views/bounty.py
import json
import logging
import os
import requests
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.utils import timezone
from website.models import GitHubIssue, UserProfile, Contributor, Repo
logger = logging.getLogger(__name__)
@csrf_exempt
@require_POST
def bounty_payout(request):
"""
Handle bounty payout requests from the GitHub Action.
Expected payload:
{
"issue_number": 123,
"repo": "BLT",
"owner": "OWASP-BLT",
"bounty_amount": 50,
"contributor_username": "username",
"pr_number": 456,
"token": "secret_token"
}
"""
# Verify token (use a secure environment variable for this)
expected_token = os.environ.get('BLT_API_TOKEN')
if not expected_token:
logger.error("BLT_API_TOKEN environment variable not set")
return JsonResponse({"status": "error", "message": "Server configuration error"}, status=500)
try:
data = json.loads(request.body)
token = data.get('token')
if token != expected_token:
logger.warning("Invalid token received in bounty payout request")
return JsonResponse({"status": "error", "message": "Unauthorized"}, status=403)
# Extract data from request
issue_number = data.get('issue_number')
repo_name = data.get('repo')
owner = data.get('owner')
bounty_amount = data.get('bounty_amount', 0)
contributor_username = data.get('contributor_username')
pr_number = data.get('pr_number')
if not all([issue_number, repo_name, owner, contributor_username, pr_number]):
return JsonResponse({"status": "error", "message": "Missing required fields"}, status=400)
# Get corresponding GitHubIssue record
try:
repo = Repo.objects.get(name=repo_name, organization__name=owner)
github_issue = GitHubIssue.objects.get(issue_id=issue_number, repo=repo)
except Repo.DoesNotExist:
logger.error(f"Repo not found: {owner}/{repo_name}")
return JsonResponse({"status": "error", "message": "Repository not found"}, status=404)
except GitHubIssue.DoesNotExist:
logger.error(f"GitHub issue not found: {owner}/{repo_name}#{issue_number}")
return JsonResponse({"status": "error", "message": "Issue not found"}, status=404)
# Check if payment already processed
if github_issue.sponsors_tx_id:
return JsonResponse({
"status": "warning",
"message": "Payment already processed",
"transaction_id": github_issue.sponsors_tx_id
})
# Find the contributor in our system
contributor = None
if contributor_username:
# First check if we have a Contributor record
contributor = Contributor.objects.filter(name=contributor_username).first()
# If not, check if there's a UserProfile with matching GitHub username
if not contributor:
# Extract GitHub username from profile URL
user_profile = UserProfile.objects.filter(
github_url__icontains=f"github.com/{contributor_username}"
).first()
if user_profile:
# Check if user has preferred payment method
# For now, we're assuming GitHub Sponsors
sponsors_url = f"https://github.com/sponsors/{contributor_username}"
# Process payment through GitHub Sponsors API
transaction_id = process_github_sponsors_payment(
contributor_username,
bounty_amount,
f"Bounty for issue #{issue_number} in {owner}/{repo_name}"
)
if transaction_id:
# Record the transaction
github_issue.sponsors_tx_id = transaction_id
github_issue.save()
return JsonResponse({
"status": "success",
"message": "Payment processed successfully",
"transaction_id": transaction_id,
"amount": bounty_amount,
"recipient": contributor_username
})
else:
return JsonResponse({
"status": "error",
"message": "Payment processing failed"
}, status=500)
else:
return JsonResponse({
"status": "error",
"message": "Contributor not found in system"
}, status=404)
return JsonResponse({
"status": "error",
"message": "Unable to process payment - contributor not found"
}, status=404)
except json.JSONDecodeError:
return JsonResponse({"status": "error", "message": "Invalid JSON"}, status=400)
except Exception as e:
logger.exception("Error processing bounty payout")
return JsonResponse({"status": "error", "message": str(e)}, status=500)
def process_github_sponsors_payment(username, amount, note):
"""
Process payment through GitHub Sponsors API.
For now, hardcoded to DonnieBLT as specified in requirements.
Returns transaction ID if successful, None otherwise.
"""
try:
# Hardcoded to DonnieBLT as specified in requirements
sponsor_recipient = "DonnieBLT"
# GitHub Sponsors API endpoint
api_url = f"https://api.github.com/sponsors/{sponsor_recipient}/sponsorships"
# GitHub personal access token with sponsors:write scope
github_token = os.environ.get('GITHUB_SPONSORS_TOKEN')
if not github_token:
logger.error("GITHUB_SPONSORS_TOKEN not configured")
return None
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json"
}
# Note: This is a simplified implementation
# In production, you'd need to handle various sponsorship tiers
# and payment mechanisms through GitHub's API
payload = {
"amount": amount,
"tier_id": "tier_for_custom_amount", # You'll need to get this from GitHub
"is_recurring": False,
"privacy_level": "public",
"note": note
}
response = requests.post(api_url, headers=headers, json=payload)
response.raise_for_status()
# Extract transaction ID from response
sponsorship_data = response.json()
transaction_id = sponsorship_data.get("id", str(timezone.now().timestamp()))
return transaction_id
except Exception as e:
logger.exception(f"Error processing GitHub Sponsors payment: {e}")
return None
- Now update URLs to include our new endpoint:
# Add to blt/urls.py in the urlpatterns list
path("bounty_payout/", bounty_payout, name="bounty_payout"),
- Finally, let's enhance the existing GitHub webhook handler to directly process bounties on PR merge events:
# Add this to website/views/user.py to enhance the handle_pull_request_event function
def handle_pull_request_event(payload):
if payload["action"] == "closed" and payload["pull_request"]["merged"]:
pr_user_profile = UserProfile.objects.filter(github_url=payload["pull_request"]["user"]["html_url"]).first()
# Assign badge if applicable
if pr_user_profile:
pr_user_instance = pr_user_profile.user
assign_github_badge(pr_user_instance, "First PR Merged")
# Check if PR closes any issues with bounties
try:
# Extract issue references from PR body
pr_body = payload["pull_request"]["body"] or ""
issue_refs = re.findall(r'(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)', pr_body, re.IGNORECASE)
if not issue_refs:
return JsonResponse({"status": "success", "message": "No issue references found"}, status=200)
# Get repo details
repo_name = payload["repository"]["name"]
owner = payload["repository"]["owner"]["login"]
try:
repo = Repo.objects.get(name=repo_name, organization__name=owner)
except Repo.DoesNotExist:
return JsonResponse({"status": "success", "message": "Repository not tracked in BLT"}, status=200)
# Check if any referenced issues have bounties
for issue_number in issue_refs:
try:
github_issue = GitHubIssue.objects.get(issue_id=int(issue_number), repo=repo, has_dollar_tag=True)
# If we found an issue with a bounty, trigger the payout process
if not github_issue.sponsors_tx_id: # Only if not already paid
# This would trigger our GitHub Action or we could process here directly
# For now, let's just log it and let the GitHub Action handle it
logger.info(f"Bounty issue #{issue_number} closed by PR #{payload['pull_request']['number']}")
except GitHubIssue.DoesNotExist:
continue
except Exception as e:
logger.exception(f"Error checking for bounty issues: {e}")
return JsonResponse({"status": "success"}, status=200)
These changes will:
- Create a GitHub Action that runs when PRs are merged and checks for linked issues with bounty labels
- Set up an API endpoint to process the bounty payment requests
- Extend the existing webhook handler to identify PRs that close bounty issues
The system will need these environment variables configured:
BLT_API_TOKEN: A secure token to authenticate requests between the GitHub Action and BLTGITHUB_SPONSORS_TOKEN: A GitHub token with sponsors:write scope for making paymentsAPI_BASE_URL: The base URL of your BLT installation (e.g., https://owasp.org)
Would you like me to explain any part of this implementation in more detail or make any adjustments?
@coderabbitai make sure we update the GitHub issue model with the relevant info like transaction id and the others
[!TIP] For best results, initiate chat on the files or code changes.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the GitHubIssue model
grep -A 50 "class GitHubIssue" ./website/models.py
# Check if sponsors_tx_id field exists in the model
grep "sponsors_tx_id" ./website/models.py
# Look at the migration that adds sponsors_tx_id
if [ -f "./website/migrations/0224_githubissue_sponsors_tx_id.py" ]; then
cat ./website/migrations/0224_githubissue_sponsors_tx_id.py
else
echo "Migration file 0224_githubissue_sponsors_tx_id.py not found."
# Look for any migrations related to sponsors_tx_id
find ./website/migrations -name "*.py" | xargs grep -l "sponsors_tx_id"
fi
# Check for any other fields that might be relevant for bounty tracking
grep -A 3 -B 3 "bounty\|payment\|sponsor\|transaction" ./website/models.py
Length of output: 6933
@DonnieBLT Looking at the GitHubIssue model, I see it already has the sponsors_tx_id field, but we should add more fields to properly track bounty payouts. Here's what I recommend adding:
# Add to GitHubIssue model in website/models.py
class GitHubIssue(models.Model):
# ... existing fields ...
# Existing field
sponsors_tx_id = models.CharField(max_length=255, null=True, blank=True)
# New fields for bounty tracking
bounty_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
sponsors_payment_created_at = models.DateTimeField(null=True, blank=True)
payment_method = models.CharField(
max_length=20,
choices=[
("github_sponsors", "GitHub Sponsors"),
("bch", "Bitcoin Cash"),
("other", "Other")
],
null=True,
blank=True
)
payment_status = models.CharField(
max_length=20,
choices=[
("pending", "Pending"),
("processing", "Processing"),
("completed", "Completed"),
("failed", "Failed")
],
null=True,
blank=True
)
We should also create a migration for these new fields:
# Create a new migration
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("website", "0224_githubissue_sponsors_tx_id"),
]
operations = [
migrations.AddField(
model_name="githubissue",
name="bounty_amount",
field=models.DecimalField(blank=True, max_digits=10, decimal_places=2, null=True),
),
migrations.AddField(
model_name="githubissue",
name="sponsors_payment_created_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="githubissue",
name="payment_method",
field=models.CharField(blank=True, choices=[("github_sponsors", "GitHub Sponsors"), ("bch", "Bitcoin Cash"), ("other", "Other")], max_length=20, null=True),
),
migrations.AddField(
model_name="githubissue",
name="payment_status",
field=models.CharField(blank=True, choices=[("pending", "Pending"), ("processing", "Processing"), ("completed", "Completed"), ("failed", "Failed")], max_length=20, null=True),
),
]
These fields will help us track:
- The exact bounty amount (extracted from labels)
- When the GitHub sponsors payment was initiated
- Which payment method was used
- The current status of the payment process
Would you like me to make any adjustments to these fields?
@coderabbitai we already have fields like that
[!TIP] For best results, initiate chat on the files or code changes.
🧩 Analysis chain
@DonnieBLT I apologize for not thoroughly checking the existing model fields before making suggestions. Let me look at the current GitHubIssue model structure to understand what fields we already have, and then I'll adjust the implementation to work with existing fields.
🏁 Script executed:
#!/bin/bash
# Check the GitHubIssue model definition in detail
grep -A 100 "class GitHubIssue" ./website/models.py
# Look specifically for fields that might be related to bounty tracking
grep -A 3 "bounty\|payment\|sponsor\|transaction\|payout" ./website/models.py | grep -v "^--$"
Length of output: 6738
@DonnieBLT You're absolutely right - I see the GitHubIssue model already has all the necessary fields for tracking payments, including:
sponsors_tx_idfor GitHub Sponsors transactionsp2p_amount_usdfor the bounty amounthas_dollar_tagto indicate issues with bountiesp2p_payment_created_atfor timestamps
Let's adjust the implementation to use these existing fields instead. The GitHub Action and API endpoint will remain largely the same, but we'll update how we store the data in the database. Here's the revised bounty payout function that uses the existing fields:
def process_github_sponsors_payment(username, amount, note):
"""
Process payment through GitHub Sponsors API.
For now, hardcoded to DonnieBLT as specified in requirements.
Returns transaction ID if successful, None otherwise.
"""
try:
# Hardcoded to DonnieBLT as specified in requirements
sponsor_recipient = "DonnieBLT"
# GitHub Sponsors API endpoint
api_url = f"https://api.github.com/sponsors/{sponsor_recipient}/sponsorships"
github_token = os.environ.get('GITHUB_SPONSORS_TOKEN')
if not github_token:
logger.error("GITHUB_SPONSORS_TOKEN not configured")
return None
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json"
}
payload = {
"amount": amount,
"tier_id": "tier_for_custom_amount",
"is_recurring": False,
"privacy_level": "public",
"note": note
}
response = requests.post(api_url, headers=headers, json=payload)
response.raise_for_status()
# Extract transaction ID from response
sponsorship_data = response.json()
transaction_id = sponsorship_data.get("id", str(timezone.now().timestamp()))
return transaction_id
except Exception as e:
logger.exception(f"Error processing GitHub Sponsors payment: {e}")
return None
And in the bounty_payout view:
# When processing the payment
if transaction_id:
# Record the transaction using existing fields
github_issue.sponsors_tx_id = transaction_id
github_issue.p2p_amount_usd = bounty_amount # Store amount in existing field
github_issue.p2p_payment_created_at = timezone.now() # Use existing timestamp field
github_issue.save()
This approach leverages the existing model fields rather than adding new ones. Is there anything else you'd like me to explain or adjust in the implementation?
@DonnieBLT sir, does this issue still needs to be addressed or has it been solved by coderabbit?
It still has to be coded
It still has to be coded
alrighty, i'll work on it. thank you
/assign
Hello @nasredeenabdulhaleem! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned due to 24 hours of inactivity. The issue is now available for anyone to work on again.
/assign
You cannot be assigned to this issue because you are already assigned to the following issues without an open pull request: #3822. Please submit a pull request for these issues before getting assigned to a new one.
hello @DonnieBLT , i would love to work on this issue can you assign this issue to me ??
Hello @RajvardhanT7747! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned from RajvardhanT7747 due to 24 hours of inactivity. The issue is now available for anyone to work on again.
Hi, I'd like to work on this issue. Can you assign it to me?
Hello @TAGOOZ! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned from TAGOOZ due to 24 hours of inactivity. The issue is now available for anyone to work on again.
Can you assign it to me ?
Hello @TAGOOZ! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned from TAGOOZ due to 24 hours of inactivity. The issue is now available for anyone to work on again.
Can you assign it to me ?
Hello @TAGOOZ! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned from TAGOOZ due to 24 hours of inactivity. The issue is now available for anyone to work on again.
Can you assign it to me ?
Hello @TAGOOZ! You've been assigned to OWASP-BLT/BLT issue #3941. You have 24 hours to complete a pull request.
⏰ This issue has been automatically unassigned from TAGOOZ due to 24 hours of inactivity. The issue is now available for anyone to work on again.
@DonnieBLT I have a suggestion It will be better to use apps like algora.io for this purpose rather than setting up the whole codebase from scratch