Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,8 @@ GITHUB_ENCRYPTION_KEY=your_encryption_key_here
# For production: Get real keys at https://www.google.com/recaptcha/admin
# Select "reCAPTCHA v2" > "I'm not a robot" Checkbox
RECAPTCHA_PUBLIC_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
RECAPTCHA_PRIVATE_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
RECAPTCHA_PRIVATE_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe

# Cron Sync Token
# Used for authenticating cron job requests to sync validators
CRON_SYNC_TOKEN=your_cron_sync_token_here
21 changes: 20 additions & 1 deletion backend/contributions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class ContributionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = ContributionSerializer
permission_classes = [permissions.AllowAny] # Allow read-only access without authentication
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['user', 'contribution_type']
filterset_fields = ['user', 'contribution_type', 'mission']
search_fields = ['notes', 'user__email', 'user__name', 'contribution_type__name']
ordering_fields = ['contribution_date', 'created_at', 'points', 'frozen_global_points']
ordering = ['-contribution_date']
Expand Down Expand Up @@ -657,6 +657,25 @@ def create(self, request, *args, **kwargs):
status=status.HTTP_400_BAD_REQUEST
)

# Validate category restrictions based on user role
contribution_type_id = data.get('contribution_type')
if contribution_type_id:
try:
contribution_type = ContributionType.objects.select_related('category').get(id=contribution_type_id)
if contribution_type.category:
if contribution_type.category.slug == 'builder' and not hasattr(request.user, 'builder'):
return Response(
{'error': 'You must complete the Builder Welcome journey before submitting builder contributions.'},
status=status.HTTP_403_FORBIDDEN
)
if contribution_type.category.slug == 'validator' and not hasattr(request.user, 'validator'):
return Response(
{'error': 'You must complete the Validator Waitlist journey before submitting validator contributions.'},
status=status.HTTP_403_FORBIDDEN
)
except ContributionType.DoesNotExist:
pass

serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
Expand Down
10 changes: 4 additions & 6 deletions backend/deploy-apprunner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ if aws apprunner describe-service --service-arn arn:aws:apprunner:$REGION:$ACCOU
"GITHUB_ENCRYPTION_KEY": "$SSM_PREFIX/prod/github_encryption_key",
"GITHUB_REPO_TO_STAR": "$SSM_PREFIX/prod/github_repo_to_star",
"RECAPTCHA_PUBLIC_KEY": "$SSM_PREFIX/prod/recaptcha_public_key",
"RECAPTCHA_PRIVATE_KEY": "$SSM_PREFIX/prod/recaptcha_private_key"
"RECAPTCHA_PRIVATE_KEY": "$SSM_PREFIX/prod/recaptcha_private_key",
"CRON_SYNC_TOKEN": "$SSM_PREFIX/prod/cron_sync_token"
},
"StartCommand": "./startup.sh gunicorn --bind 0.0.0.0:8000 --timeout 180 --workers 2 tally.wsgi:application"
},
Expand All @@ -223,8 +224,6 @@ if aws apprunner describe-service --service-arn arn:aws:apprunner:$REGION:$ACCOU
}
},
"InstanceConfiguration": {
"Cpu": "0.25 vCPU",
"Memory": "0.5 GB",
"InstanceRoleArn": "arn:aws:iam::$ACCOUNT_ID:role/AppRunnerInstanceRole"
},
"HealthCheckConfiguration": {
Expand Down Expand Up @@ -293,7 +292,8 @@ else
"GITHUB_ENCRYPTION_KEY": "$SSM_PREFIX/prod/github_encryption_key",
"GITHUB_REPO_TO_STAR": "$SSM_PREFIX/prod/github_repo_to_star",
"RECAPTCHA_PUBLIC_KEY": "$SSM_PREFIX/prod/recaptcha_public_key",
"RECAPTCHA_PRIVATE_KEY": "$SSM_PREFIX/prod/recaptcha_private_key"
"RECAPTCHA_PRIVATE_KEY": "$SSM_PREFIX/prod/recaptcha_private_key",
"CRON_SYNC_TOKEN": "$SSM_PREFIX/prod/cron_sync_token"
},
"StartCommand": "./startup.sh gunicorn --bind 0.0.0.0:8000 --timeout 180 --workers 2 tally.wsgi:application"
},
Expand All @@ -305,8 +305,6 @@ else
}
},
"InstanceConfiguration": {
"Cpu": "0.25 vCPU",
"Memory": "0.5 GB",
"InstanceRoleArn": "arn:aws:iam::$ACCOUNT_ID:role/AppRunnerInstanceRole"
},
"HealthCheckConfiguration": {
Expand Down
30 changes: 29 additions & 1 deletion backend/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework.parsers import MultiPartParser, FormParser
from django.shortcuts import get_object_or_404
from django.conf import settings
from django.db.models import Sum
from django.db.models import Sum, Q
from .models import User
from .serializers import UserSerializer, UserCreateSerializer, UserProfileUpdateSerializer
from .cloudinary_service import CloudinaryService
Expand Down Expand Up @@ -788,3 +788,31 @@ def referrals(self, request):
'validator_points': validator_pts,
'referrals': referral_list
})

@action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
def search(self, request):
"""Search users by name, address, email, or social handles."""
query = request.query_params.get('q', '').strip()

if len(query) < 2:
return Response([])

users = User.objects.filter(
Q(name__icontains=query) |
Q(address__icontains=query) |
Q(email__icontains=query) |
Q(twitter_handle__icontains=query) |
Q(discord_handle__icontains=query) |
Q(telegram_handle__icontains=query) |
Q(github_username__icontains=query)
).filter(visible=True)[:10]

return Response([
{
'id': user.id,
'name': user.name,
'address': user.address,
'profile_image_url': user.profile_image_url
}
for user in users
])
7 changes: 6 additions & 1 deletion frontend/src/components/Missions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,12 @@
<div class="px-4 py-3 border-b last:border-b-0 hover:bg-gray-50 transition-colors">
<!-- Title row with badges and submit button -->
<div class="flex items-center gap-3 mb-2 flex-wrap">
<h3 class="text-base font-bold font-heading text-gray-900">{mission.name}</h3>
<button
onclick={() => push(`/all-contributions?mission=${mission.id}&category=${$currentCategory !== 'global' ? $currentCategory : 'validator'}`)}
class="text-base font-bold font-heading text-gray-900 {colors.titleTextHover} transition-colors text-left"
>
{mission.name}
</button>

{#if mission.contribution_type_details}
<Badge
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/Navbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { push, location } from 'svelte-spa-router';
import AuthButton from './AuthButton.svelte';
import ReferralSection from './ReferralSection.svelte';
import SearchBar from './SearchBar.svelte';
import Icon from './Icons.svelte';
import { authState } from '../lib/auth.js';
import { categoryTheme, currentCategory } from '../stores/category.js';
Expand Down Expand Up @@ -52,8 +53,11 @@
<!-- Right section with actions -->
<div class="flex-1 flex justify-end items-center h-16 px-4">
<div class="hidden md:flex items-center gap-4">
{#if $authState.isAuthenticated}
<SearchBar />
{/if}
<ReferralSection />
<button
<button
onclick={handleSubmitContribution}
class="px-4 py-2 {$categoryTheme.button} rounded-md font-medium"
>
Expand Down
Loading
Loading