diff --git a/admin_api/urls.py b/admin_api/urls.py index f36e41a..275f52b 100644 --- a/admin_api/urls.py +++ b/admin_api/urls.py @@ -18,4 +18,8 @@ urlpatterns = [ path('partners//', views.PartnerDetailView.as_view(), name='partner-detail'), path('partners//status/', views.PartnerStatusView.as_view(), name='partner-status'), path('partners//kyc/review/', views.PartnerKYCReviewView.as_view(), name='partner-kyc-review'), -] + path('users/metrics/', views.UserMetricsView.as_view(), name='user-metrics'), + 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 diff --git a/admin_api/views.py b/admin_api/views.py index 0707f61..657adbb 100644 --- a/admin_api/views.py +++ b/admin_api/views.py @@ -471,3 +471,134 @@ class PartnerKYCReviewView(APIView): 'is_kyc_compliant': p.is_kyc_compliant, 'verificationStatus': 'Verified' if decision == 'approved' else 'Rejected', }) + + +# --------------------------------------------------------------------------- +# Phase 4: Users API +# --------------------------------------------------------------------------- + +def _user_status(u): + return 'Active' if u.is_active else 'Suspended' + +_USER_ROLE_MAP = { + 'admin': 'Admin', 'manager': 'Admin', + 'staff': 'Support Agent', + 'customer': 'User', 'is_user': 'User', + 'partner': 'Partner', 'partner_manager': 'Partner', + 'partner_staff': 'Partner', 'partner_customer': 'Partner', +} + +def _serialize_user(u): + full_name = f'{u.first_name} {u.last_name}'.strip() or u.username + role_key = u.role if u.role else ('customer' if getattr(u, 'is_customer', False) else 'staff') + return { + 'id': str(u.id), + 'name': full_name, + 'email': u.email, + 'phone': getattr(u, 'phone_number', '') or '', + 'countryCode': '+91', + 'role': _USER_ROLE_MAP.get(role_key, 'User'), + 'status': _user_status(u), + 'tier': 'Bronze', + 'healthScore': 'warm', + 'isVerified': u.is_active, + 'is2FAEnabled': False, + 'totalSpent': 0, + 'bookingsCount': 0, + 'refundRate': 0, + 'averageOrderValue': 0, + 'language': 'en', + 'timezone': 'Asia/Kolkata', + 'currency': 'INR', + 'emailNotifications': True, + 'pushNotifications': True, + 'smsNotifications': True, + 'tags': [], + 'pinnedNote': '', + 'createdAt': u.date_joined.isoformat() if u.date_joined else '', + 'updatedAt': u.date_joined.isoformat() if u.date_joined else '', + 'lastLoginAt': u.last_login.isoformat() if u.last_login else '', + 'lastActivityAt': u.last_login.isoformat() if u.last_login else '', + } + + +class UserMetricsView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from django.contrib.auth import get_user_model + from django.utils import timezone + import datetime + User = get_user_model() + today = timezone.now().date() + week_ago = today - datetime.timedelta(days=7) + return Response({ + 'total': User.objects.count(), + 'active': User.objects.filter(is_active=True).count(), + 'suspended': User.objects.filter(is_active=False).count(), + 'newThisWeek': User.objects.filter(date_joined__date__gte=week_ago).count(), + }) + + +class UserListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from django.contrib.auth import get_user_model + from django.db.models import Q + User = get_user_model() + qs = User.objects.all() + if s := request.GET.get('status'): + if s == 'Active': + qs = qs.filter(is_active=True) + elif s in ('Suspended', 'Banned'): + qs = qs.filter(is_active=False) + if r := request.GET.get('role'): + role_reverse = {v: k for k, v in _USER_ROLE_MAP.items()} + backend_role = role_reverse.get(r) + if backend_role: + qs = qs.filter(role=backend_role) + if q := request.GET.get('search'): + qs = qs.filter( + Q(username__icontains=q) | Q(email__icontains=q) | + Q(first_name__icontains=q) | Q(last_name__icontains=q) | + Q(phone_number__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() + users = qs.order_by('-date_joined')[(page - 1) * page_size: page * page_size] + return Response({'count': total, 'results': [_serialize_user(u) for u in users]}) + + +class UserDetailView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, pk): + from django.contrib.auth import get_user_model + from django.shortcuts import get_object_or_404 + User = get_user_model() + u = get_object_or_404(User, pk=pk) + return Response(_serialize_user(u)) + + +class UserStatusView(APIView): + permission_classes = [IsAuthenticated] + + def patch(self, request, pk): + from django.contrib.auth import get_user_model + from django.shortcuts import get_object_or_404 + User = get_user_model() + u = get_object_or_404(User, pk=pk) + action = request.data.get('action') + if action in ('suspend', 'ban'): + u.is_active = False + elif action == 'reinstate': + u.is_active = True + else: + 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)})