From 8e081c8c243071c99c5ed8b752774689a22dda31 Mon Sep 17 00:00:00 2001 From: Eventify Deploy Date: Tue, 24 Mar 2026 18:42:15 +0000 Subject: [PATCH] =?UTF-8?q?Phase=205:=20Events=20Admin=20=E2=80=94=204=20n?= =?UTF-8?q?ew=20event=20endpoints=20(stats,=20list,=20detail,=20moderate)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin_api/urls.py | 7 ++- admin_api/views.py | 115 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/admin_api/urls.py b/admin_api/urls.py index 275f52b..8347c3f 100644 --- a/admin_api/urls.py +++ b/admin_api/urls.py @@ -22,4 +22,9 @@ urlpatterns = [ path('users/', views.UserListView.as_view(), name='user-list'), path('users//', views.UserDetailView.as_view(), name='user-detail'), path('users//status/', views.UserStatusView.as_view(), name='user-status'), -] \ No newline at end of file + # Phase 5: Events endpoints + path('events/stats/', views.EventStatsView.as_view(), name='event-stats'), + path('events/', views.EventListView.as_view(), name='event-list'), + path('events//', views.EventDetailView.as_view(), name='event-detail'), + path('events//moderate/', views.EventModerationView.as_view(), name='event-moderate'), +] diff --git a/admin_api/views.py b/admin_api/views.py index 657adbb..febed3b 100644 --- a/admin_api/views.py +++ b/admin_api/views.py @@ -602,3 +602,118 @@ class UserStatusView(APIView): return Response({'error': 'action must be suspend, ban, or reinstate'}, status=400) u.save(update_fields=['is_active']) return Response({'id': str(u.id), 'status': _user_status(u)}) + +# --------------------------------------------------------------------------- +# Phase 5: Events API +# --------------------------------------------------------------------------- + +_EVENT_STATUS_MAP = { + 'live': 'live', 'published': 'published', + 'pending': 'published', 'created': 'draft', + 'flagged': 'flagged', 'cancelled': 'cancelled', + 'postponed': 'cancelled', 'completed': 'completed', +} + + +def _serialize_event(e): + partner_name = '' + if e.partner_id: + try: + partner_name = e.partner.name + except Exception: + partner_name = '' + return { + 'id': str(e.id), + 'title': e.title or getattr(e, 'name', '') or '', + 'partnerId': str(e.partner_id) if e.partner_id else '', + 'partnerName': partner_name, + 'date': e.start_date.isoformat() if e.start_date else '', + 'status': _EVENT_STATUS_MAP.get(e.event_status, 'draft'), + 'ticketsSold': 0, + 'revenue': 0, + 'venueName': e.venue_name or '', + 'createdAt': e.created_date.isoformat() if e.created_date else '', + 'isFeatured': bool(e.is_featured), + 'isTopEvent': bool(e.is_top_event), + } + + +class EventStatsView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from events.models import Event + return Response({ + 'total': Event.objects.count(), + 'live': Event.objects.filter(event_status='live').count(), + 'pending': Event.objects.filter(event_status__in=['pending', 'created']).count(), + 'flagged': Event.objects.filter(event_status='flagged').count(), + 'published': Event.objects.filter(event_status='published').count(), + }) + + +class EventListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from events.models import Event + from django.db.models import Q + qs = Event.objects.select_related('partner').all() + if s := request.GET.get('status'): + reverse_map = {v: k for k, v in _EVENT_STATUS_MAP.items()} + backend_status = reverse_map.get(s, s) + qs = qs.filter(event_status=backend_status) + if pid := request.GET.get('partner_id'): + qs = qs.filter(partner_id=pid) + if q := request.GET.get('search'): + qs = qs.filter( + Q(title__icontains=q) | Q(name__icontains=q) | Q(venue_name__icontains=q) + ) + try: + page = max(1, int(request.GET.get('page', 1))) + page_size = min(100, int(request.GET.get('page_size', 20))) + except (ValueError, TypeError): + page, page_size = 1, 20 + total = qs.count() + events = qs.order_by('-id')[(page - 1) * page_size: page * page_size] + return Response({'count': total, 'results': [_serialize_event(e) for e in events]}) + + +class EventDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + from events.models import Event + from django.shortcuts import get_object_or_404 + e = get_object_or_404(Event.objects.select_related('partner'), pk=pk) + return Response(_serialize_event(e)) + + +class EventModerationView(APIView): + permission_classes = [IsAuthenticated] + + def patch(self, request, pk): + from events.models import Event + from django.shortcuts import get_object_or_404 + e = get_object_or_404(Event, pk=pk) + action = request.data.get('action') + if action == 'approve': + e.event_status = 'published' + e.save(update_fields=['event_status']) + elif action == 'reject': + e.event_status = 'cancelled' + e.cancelled_reason = request.data.get('reason', '') + e.save(update_fields=['event_status', 'cancelled_reason']) + elif action == 'flag': + e.event_status = 'flagged' + e.save(update_fields=['event_status']) + elif action == 'feature': + e.is_featured = True + e.save(update_fields=['is_featured']) + elif action == 'unfeature': + e.is_featured = False + e.save(update_fields=['is_featured']) + else: + return Response({'error': 'Invalid action'}, status=400) + e.refresh_from_db() + return Response(_serialize_event(e))