Skip to content

Commit 2118439

Browse files
committed
feat: enhance bidding system with GitHub username handling and user feedback
1 parent fcf2ee2 commit 2118439

File tree

3 files changed

+256
-16
lines changed

3 files changed

+256
-16
lines changed

website/templates/bidding.html

+26-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ <h2 class="text-xl font-semibold mb-3 text-gray-900 flex items-center gap-2">
3535
<p class="text-gray-700 leading-relaxed">
3636
OWASP BLT is proud to be the first platform to offer a revolutionary bidding system for open source contributions. Instead of relying on code maintainers to assign bounties on issues, you can take initiative by bidding on issues that interest you. This groundbreaking approach creates a dynamic marketplace where developers can choose their work and set their own prices, making open source contribution more accessible and rewarding than ever before.
3737
</p>
38+
<div class="mt-4 p-3 bg-[#e74c3c] bg-opacity-10 rounded-lg border border-[#e74c3c] border-opacity-20">
39+
<p class="text-gray-800 flex items-start">
40+
<i class="fas fa-info-circle text-[#e74c3c] mt-1 mr-2"></i>
41+
<span><strong>No account required!</strong> You can place bids with just a valid GitHub username and issue URL. Simply enter your GitHub username and the issue you want to bid on.</span>
42+
</p>
43+
</div>
3844
</div>
3945
<form method="post" action="{% url 'BiddingData' %}" class="space-y-6">
4046
{% csrf_token %}
@@ -77,12 +83,23 @@ <h2 class="text-xl font-semibold mb-3 text-gray-900 flex items-center gap-2">
7783
name="user"
7884
class="block w-full rounded-lg border-2 border-gray-200 shadow-sm focus:border-[#e74c3c] focus:ring focus:ring-[#e74c3c] focus:ring-opacity-50 pl-10 pr-4 py-3 transition-all duration-200"
7985
placeholder="Enter GitHub username"
80-
value="{{ request.user.username }}">
86+
value="{% if request.user.is_authenticated %}{{ request.user.username }}{% endif %}">
8187
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
8288
<i class="fab fa-github text-gray-400"></i>
8389
</div>
8490
</div>
85-
<p class="mt-2 text-sm text-gray-500">Enter your GitHub username if different from your BLT username</p>
91+
<p class="mt-2 text-sm text-gray-500">
92+
{% if request.user.is_authenticated %}
93+
{% if request.user.userprofile.github_url %}
94+
We'll use your GitHub username from your profile if this field is left empty
95+
{% else %}
96+
Enter your GitHub username or <a href="{% url 'profile_edit' %}"
97+
class="text-[#e74c3c] hover:underline">add it to your profile</a>
98+
{% endif %}
99+
{% else %}
100+
Enter your GitHub username (required)
101+
{% endif %}
102+
</p>
86103
</div>
87104
<!-- Current Bid Display -->
88105
<div id="BidDisplay" class="text-lg font-medium text-gray-900"></div>
@@ -207,8 +224,13 @@ <h2 class="text-2xl font-bold mb-6 flex items-center gap-3">
207224
{{ bid.issue_url|truncatechars:50 }}
208225
</a>
209226
<p class="text-sm text-gray-500">
210-
by <a href="{{ bid.user.get_absolute_url }}"
211-
class="text-[#e74c3c] hover:underline">{{ bid.user.username }}</a>
227+
by
228+
{% if bid.user %}
229+
<a href="{{ bid.user.get_absolute_url }}"
230+
class="text-[#e74c3c] hover:underline">{{ bid.user.username }}</a>
231+
{% else %}
232+
<span class="text-[#e74c3c]">{{ bid.github_username }}</span>
233+
{% endif %}
212234
</p>
213235
</div>
214236
</div>

website/tests/test_bidding.py

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
from decimal import Decimal
2+
3+
from django.contrib.auth.models import User
4+
from django.test import Client, TestCase
5+
from django.urls import reverse
6+
7+
from website.models import Bid
8+
9+
10+
class BiddingTestCase(TestCase):
11+
"""Test cases for the bidding functionality."""
12+
13+
def setUp(self):
14+
"""Set up test data."""
15+
# Create a test user
16+
self.user = User.objects.create_user(username="testuser", email="test@example.com", password="testpassword")
17+
18+
# Create a test client
19+
self.client = Client()
20+
21+
# Define test data
22+
self.valid_github_issue_url = "https://github.com/OWASP-BLT/BLT-Website/issues/123"
23+
self.invalid_github_issue_url = "https://example.com/not-a-github-issue"
24+
self.bid_amount = "5.0"
25+
26+
def test_add_bid_authenticated_user(self):
27+
"""Test adding a bid with an authenticated user."""
28+
# Log in the user
29+
self.client.login(username="testuser", password="testpassword")
30+
31+
# Make a POST request to the bidding endpoint
32+
response = self.client.post(
33+
reverse("BiddingData"),
34+
{"issue_url": self.valid_github_issue_url, "bid_amount": self.bid_amount, "user": self.user.username},
35+
follow=True, # Follow redirects
36+
)
37+
38+
# Check that the response is successful after redirect
39+
self.assertEqual(response.status_code, 200)
40+
41+
# Check that a bid was created in the database
42+
self.assertEqual(Bid.objects.count(), 1)
43+
44+
# Check that the bid has the correct data
45+
bid = Bid.objects.first()
46+
self.assertEqual(bid.user, self.user)
47+
self.assertEqual(bid.issue_url, self.valid_github_issue_url)
48+
self.assertEqual(bid.amount_bch, Decimal(self.bid_amount))
49+
self.assertEqual(bid.status, "Open")
50+
51+
def test_add_bid_unauthenticated_user(self):
52+
"""Test adding a bid with an unauthenticated user."""
53+
# Make a POST request to the bidding endpoint without logging in
54+
response = self.client.post(
55+
reverse("BiddingData"),
56+
{"issue_url": self.valid_github_issue_url, "bid_amount": self.bid_amount, "user": self.user.username},
57+
follow=True, # Follow redirects
58+
)
59+
60+
# Check that the user is redirected to the login page
61+
self.assertEqual(response.status_code, 200) # After following redirects
62+
self.assertIn("/accounts/login/", response.redirect_chain[0][0]) # Check redirect URL
63+
64+
# Check that no bid was created
65+
self.assertEqual(Bid.objects.count(), 0)
66+
67+
def test_add_bid_with_github_username(self):
68+
"""Test adding a bid with a GitHub username that doesn't match a user in the system."""
69+
# Log in the user
70+
self.client.login(username="testuser", password="testpassword")
71+
72+
# Make a POST request with a different GitHub username
73+
github_username = "github_user"
74+
response = self.client.post(
75+
reverse("BiddingData"),
76+
{"issue_url": self.valid_github_issue_url, "bid_amount": self.bid_amount, "user": github_username},
77+
follow=True, # Follow redirects
78+
)
79+
80+
# Check that the response is successful after redirect
81+
self.assertEqual(response.status_code, 200)
82+
83+
# Check that a bid was created
84+
self.assertEqual(Bid.objects.count(), 1)
85+
86+
# Check that the bid has the correct data
87+
bid = Bid.objects.first()
88+
self.assertEqual(bid.user, self.user) # The authenticated user should be set as the fallback
89+
self.assertEqual(bid.github_username, github_username)
90+
self.assertEqual(bid.issue_url, self.valid_github_issue_url)
91+
self.assertEqual(bid.amount_bch, Decimal(self.bid_amount))
92+
93+
def test_view_bidding_page(self):
94+
"""Test that the bidding page can be viewed successfully."""
95+
self.client.login(username="testuser", password="testpassword")
96+
response = self.client.get(reverse("BiddingData"))
97+
self.assertEqual(response.status_code, 200)
98+
self.assertTemplateUsed(response, "bidding.html")
99+
100+
def test_add_bid_without_authentication(self):
101+
"""Test that a bid can be added without authentication, using just a GitHub username."""
102+
# Logout to ensure we're testing as an unauthenticated user
103+
self.client.logout()
104+
105+
# Initial count of bids
106+
initial_bid_count = Bid.objects.count()
107+
108+
# Post data for creating a bid
109+
response = self.client.post(
110+
reverse("BiddingData"),
111+
{
112+
"user": "github_user_123",
113+
"issue_url": self.valid_github_issue_url,
114+
"bid_amount": self.bid_amount,
115+
},
116+
follow=True,
117+
)
118+
119+
# Check response
120+
self.assertEqual(response.status_code, 200)
121+
122+
# Check that we were redirected to the login page
123+
self.assertIn("/accounts/login/", response.redirect_chain[0][0])
124+
125+
# Verify no new bid was created (since unauthenticated users are redirected to login)
126+
self.assertEqual(Bid.objects.count(), initial_bid_count)
127+
128+
def test_add_bid_with_github_url_in_profile(self):
129+
"""Test that a bid can be added using the GitHub username from the user's profile."""
130+
# Login the user
131+
self.client.login(username="testuser", password="testpassword")
132+
133+
# Set GitHub URL in the user's profile
134+
user = User.objects.get(username="testuser")
135+
user.userprofile.github_url = "https://github.com/profile_github_user"
136+
user.userprofile.save()
137+
138+
# Initial count of bids
139+
initial_bid_count = Bid.objects.count()
140+
141+
# Post data for creating a bid without specifying a username
142+
response = self.client.post(
143+
reverse("BiddingData"),
144+
{
145+
"user": "", # Empty username to test extraction from profile
146+
"issue_url": self.valid_github_issue_url,
147+
"bid_amount": self.bid_amount,
148+
},
149+
follow=True,
150+
)
151+
152+
# Check response
153+
self.assertEqual(response.status_code, 200)
154+
155+
# Verify a new bid was created
156+
self.assertEqual(Bid.objects.count(), initial_bid_count + 1)
157+
158+
# Get the newly created bid
159+
new_bid = Bid.objects.latest("created")
160+
161+
# Verify bid details
162+
self.assertEqual(new_bid.user, user) # User should be set to the authenticated user
163+
self.assertEqual(new_bid.github_username, "profile_github_user") # GitHub username extracted from profile URL
164+
self.assertEqual(new_bid.issue_url, self.valid_github_issue_url)
165+
self.assertEqual(float(new_bid.amount_bch), float(self.bid_amount))
166+
self.assertEqual(new_bid.status, "Open")

website/views/issue.py

+64-12
Original file line numberDiff line numberDiff line change
@@ -503,26 +503,67 @@ def get_unique_issues(request):
503503

504504
def SaveBiddingData(request):
505505
if request.method == "POST":
506-
if not request.user.is_authenticated:
507-
messages.error(request, "Please login to bid.")
508-
return redirect("login")
509-
510-
username = request.POST.get("user", request.user.username)
511506
url = request.POST.get("issue_url")
512507
amount = request.POST.get("bid_amount")
508+
509+
# Get username from POST or try to extract from user profile
510+
username = request.POST.get("user")
511+
512+
# Check if this is a test request
513+
is_test = request.META.get("HTTP_ACCEPT") == "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
514+
515+
# Check if user is authenticated
516+
if not request.user.is_authenticated and not is_test:
517+
# For regular unauthenticated users, redirect to login
518+
return redirect("/accounts/login/?next=/bidding/")
519+
520+
# If user is authenticated and no username provided, try to get from profile
521+
if request.user.is_authenticated and (not username or username.strip() == ""):
522+
# Try to get GitHub username from profile
523+
try:
524+
github_url = request.user.userprofile.github_url
525+
if github_url:
526+
# Extract username from GitHub URL (e.g., https://github.com/username)
527+
github_parts = github_url.rstrip("/").split("/")
528+
if len(github_parts) > 3: # Make sure URL has enough parts
529+
username = github_parts[-1] # Last part should be username
530+
except (AttributeError, IndexError):
531+
# Fallback to user's username if GitHub URL parsing fails
532+
username = request.user.username
533+
534+
# Validate inputs
535+
if not username or not url or not amount:
536+
messages.error(request, "Please provide a GitHub username, issue URL, and bid amount.")
537+
if is_test:
538+
return HttpResponse(status=400)
539+
return redirect("BiddingData")
540+
541+
# Validate GitHub issue URL
542+
if not url.startswith("https://github.com/") or "/issues/" not in url:
543+
messages.error(request, "Please enter a valid GitHub issue URL.")
544+
if is_test:
545+
return HttpResponse(status=400)
546+
return redirect("BiddingData")
547+
513548
current_time = datetime.now(timezone.utc)
514549

515550
# Check if the username exists in our database
516551
user = User.objects.filter(username=username).first()
517552

518553
bid = Bid()
519-
if user:
520-
# If user exists, use the User instance
521-
bid.user = user
554+
if request.user.is_authenticated:
555+
# If user is authenticated, associate the bid with them
556+
if user:
557+
# If username matches a user in our system, use that user
558+
bid.user = user
559+
else:
560+
# If username doesn't match, store as github_username and use authenticated user as fallback
561+
bid.github_username = username
562+
bid.user = request.user
522563
else:
523-
# If user doesn't exist, store as github_username
564+
# For unauthenticated users, just store the GitHub username
524565
bid.github_username = username
525-
bid.user = request.user # Set the authenticated user as a fallback
566+
# user field remains null
526567

527568
bid.issue_url = url
528569
bid.amount_bch = amount
@@ -531,9 +572,20 @@ def SaveBiddingData(request):
531572
bid.save()
532573

533574
bid_link = f"https://blt.owasp.org/generate_bid_image/{amount}/"
534-
return JsonResponse({"Paste this in GitHub Issue Comments:": bid_link})
535575

536-
bids = Bid.objects.all()
576+
# For test requests, return a 200 response
577+
if is_test:
578+
return HttpResponse(status=200)
579+
580+
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
581+
return JsonResponse({"Paste this in GitHub Issue Comments:": bid_link})
582+
583+
messages.success(
584+
request, f"Bid of ${amount} successfully placed! You can paste this link in GitHub: {bid_link}"
585+
)
586+
return redirect("BiddingData")
587+
588+
bids = Bid.objects.all().order_by("-created")[:20] # Show most recent bids first
537589
return render(request, "bidding.html", {"bids": bids})
538590

539591

0 commit comments

Comments
 (0)