feat: Add user leaderboard API, Cloudflare Turnstile integration, and support ticket categorization.

This commit is contained in:
pacnpal
2025-12-27 15:41:10 -05:00
parent 137b9b8cb9
commit aa56c46c27
11 changed files with 656 additions and 428 deletions

View File

@@ -19,6 +19,7 @@ from .views import (
from .views.discovery import DiscoveryAPIView
from .views.stats import StatsAPIView, StatsRecalculateAPIView
from .views.reviews import LatestReviewsAPIView
from .views.leaderboard import leaderboard
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@@ -61,6 +62,8 @@ urlpatterns = [
),
# Reviews endpoints
path("reviews/latest/", LatestReviewsAPIView.as_view(), name="latest-reviews"),
# Leaderboard endpoint
path("leaderboard/", leaderboard, name="leaderboard"),
# Ranking system endpoints
path(
"rankings/calculate/",

View File

@@ -0,0 +1,133 @@
"""
Leaderboard views for user rankings
"""
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from django.db.models import Count, Sum
from django.db.models.functions import Coalesce
from django.utils import timezone
from datetime import timedelta
from apps.accounts.models import User
from apps.rides.models import RideCredit
from apps.reviews.models import Review
from apps.moderation.models import EditSubmission
@api_view(['GET'])
@permission_classes([AllowAny])
def leaderboard(request):
"""
Get user leaderboard data.
Query params:
- category: 'credits' | 'reviews' | 'contributions' (default: credits)
- period: 'all' | 'monthly' | 'weekly' (default: all)
- limit: int (default: 25, max: 100)
"""
category = request.query_params.get('category', 'credits')
period = request.query_params.get('period', 'all')
limit = min(int(request.query_params.get('limit', 25)), 100)
# Calculate date filter based on period
date_filter = None
if period == 'weekly':
date_filter = timezone.now() - timedelta(days=7)
elif period == 'monthly':
date_filter = timezone.now() - timedelta(days=30)
if category == 'credits':
return _get_credits_leaderboard(date_filter, limit)
elif category == 'reviews':
return _get_reviews_leaderboard(date_filter, limit)
elif category == 'contributions':
return _get_contributions_leaderboard(date_filter, limit)
else:
return Response({'error': 'Invalid category'}, status=400)
def _get_credits_leaderboard(date_filter, limit):
"""Top users by total ride credits."""
queryset = RideCredit.objects.all()
if date_filter:
queryset = queryset.filter(created_at__gte=date_filter)
# Aggregate credits per user
users_data = queryset.values('user_id', 'user__username', 'user__display_name').annotate(
total_credits=Coalesce(Sum('count'), 0),
unique_rides=Count('ride', distinct=True),
).order_by('-total_credits')[:limit]
results = []
for rank, entry in enumerate(users_data, 1):
results.append({
'rank': rank,
'user_id': entry['user_id'],
'username': entry['user__username'],
'display_name': entry['user__display_name'] or entry['user__username'],
'total_credits': entry['total_credits'],
'unique_rides': entry['unique_rides'],
})
return Response({
'category': 'credits',
'results': results,
})
def _get_reviews_leaderboard(date_filter, limit):
"""Top users by review count."""
queryset = Review.objects.all()
if date_filter:
queryset = queryset.filter(created_at__gte=date_filter)
# Count reviews per user
users_data = queryset.values('user_id', 'user__username', 'user__display_name').annotate(
review_count=Count('id'),
).order_by('-review_count')[:limit]
results = []
for rank, entry in enumerate(users_data, 1):
results.append({
'rank': rank,
'user_id': entry['user_id'],
'username': entry['user__username'],
'display_name': entry['user__display_name'] or entry['user__username'],
'review_count': entry['review_count'],
})
return Response({
'category': 'reviews',
'results': results,
})
def _get_contributions_leaderboard(date_filter, limit):
"""Top users by approved contributions."""
queryset = EditSubmission.objects.filter(status='approved')
if date_filter:
queryset = queryset.filter(created_at__gte=date_filter)
# Count contributions per user
users_data = queryset.values('submitted_by_id', 'submitted_by__username', 'submitted_by__display_name').annotate(
contribution_count=Count('id'),
).order_by('-contribution_count')[:limit]
results = []
for rank, entry in enumerate(users_data, 1):
results.append({
'rank': rank,
'user_id': entry['submitted_by_id'],
'username': entry['submitted_by__username'],
'display_name': entry['submitted_by__display_name'] or entry['submitted_by__username'],
'contribution_count': entry['contribution_count'],
})
return Response({
'category': 'contributions',
'results': results,
})