From d14d6093e042b497da44e2dadeddeca1937573ba Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Fri, 14 Nov 2025 16:15:18 +0000 Subject: [PATCH] section 2 --- backend/igny8_core/api/exception_handlers.py | 64 +++++--- backend/igny8_core/auth/views.py | 158 ++++++++++--------- 2 files changed, 129 insertions(+), 93 deletions(-) diff --git a/backend/igny8_core/api/exception_handlers.py b/backend/igny8_core/api/exception_handlers.py index 9181e451..365f60ca 100644 --- a/backend/igny8_core/api/exception_handlers.py +++ b/backend/igny8_core/api/exception_handlers.py @@ -56,16 +56,9 @@ def extract_error_message(exc, response): if isinstance(exc.detail, dict): # Validation errors - use as errors dict errors = exc.detail - # Extract first error message as top-level error - if errors: - first_key = list(errors.keys())[0] - first_error = errors[first_key] - if isinstance(first_error, list) and first_error: - error_message = f"{first_key}: {first_error[0]}" - else: - error_message = f"{first_key}: {first_error}" - else: - error_message = "Validation failed" + # Set a general validation error message + # The specific field errors are in the errors dict + error_message = "Validation failed" elif isinstance(exc.detail, list): # List of errors error_message = exc.detail[0] if exc.detail else "An error occurred" @@ -76,14 +69,35 @@ def extract_error_message(exc, response): elif response and hasattr(response, 'data'): # Try to extract from response data if isinstance(response.data, dict): - # Check for common error message fields - error_message = ( - response.data.get('error') or - response.data.get('message') or - response.data.get('detail') or - str(response.data) - ) - errors = response.data if 'error' not in response.data else None + # If response already has unified format, preserve it + if 'success' in response.data: + error_message = response.data.get('error', 'An error occurred') + errors = response.data.get('errors') + return error_message, errors + + # If response.data looks like validation errors (dict with field names as keys) + # and no 'error', 'message', or 'detail' fields, treat it as validation errors + if 'error' not in response.data and 'message' not in response.data and 'detail' not in response.data: + # This is a validation error dict - extract errors and set error message + errors = response.data + error_message = 'Validation failed' + else: + # Check for common error message fields + error_message = ( + response.data.get('error') or + response.data.get('message') or + response.data.get('detail') or + 'Validation failed' if response.data else 'An error occurred' + ) + # Extract errors from response.data if it's a dict with field errors + # Check if response.data contains field names (likely validation errors) + if isinstance(response.data, dict) and any( + isinstance(v, (list, str)) for v in response.data.values() + ) and 'error' not in response.data and 'message' not in response.data: + errors = response.data + error_message = 'Validation failed' + else: + errors = response.data if 'error' not in response.data else None elif isinstance(response.data, list): error_message = response.data[0] if response.data else "An error occurred" errors = {"non_field_errors": response.data} @@ -148,6 +162,15 @@ def custom_exception_handler(exc, context): # Determine status code if response is not None: status_code = response.status_code + + # If response already has unified format (success field), return it as-is + # This handles cases where error_response() was manually returned + if hasattr(response, 'data') and isinstance(response.data, dict): + if 'success' in response.data: + # Response already in unified format - just add request_id if needed + if request_id and 'request_id' not in response.data: + response.data['request_id'] = request_id + return response else: # Unhandled exception - default to 500 status_code = status.HTTP_500_INTERNAL_SERVER_ERROR @@ -188,8 +211,13 @@ def custom_exception_handler(exc, context): } # Add errors dict if present + # Always include errors if we have them, even if error_message was set if errors: error_response_data["errors"] = errors + # If we have errors but no error message was set, ensure we have one + elif not error_message or error_message == "An error occurred": + error_message = get_status_code_message(status_code) + error_response_data["error"] = error_message # Add request ID for error tracking if request_id: diff --git a/backend/igny8_core/auth/views.py b/backend/igny8_core/auth/views.py index 85183dd2..70fc00d6 100644 --- a/backend/igny8_core/auth/views.py +++ b/backend/igny8_core/auth/views.py @@ -11,6 +11,7 @@ from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend from igny8_core.api.base import AccountModelViewSet from igny8_core.api.authentication import JWTAuthentication, CSRFExemptSessionAuthentication +from igny8_core.api.response import success_response, error_response from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword from .serializers import ( UserSerializer, AccountSerializer, PlanSerializer, SubscriptionSerializer, @@ -680,21 +681,24 @@ class AuthViewSet(viewsets.GenericViewSet): refresh_expires_at = get_token_expiry('refresh') user_serializer = UserSerializer(user) - return Response({ - 'success': True, - 'message': 'Registration successful', - 'user': user_serializer.data, - 'tokens': { - 'access': access_token, - 'refresh': refresh_token, - 'access_expires_at': access_expires_at.isoformat(), - 'refresh_expires_at': refresh_expires_at.isoformat(), - } - }, status=status.HTTP_201_CREATED) - return Response({ - 'success': False, - 'errors': serializer.errors - }, status=status.HTTP_400_BAD_REQUEST) + return success_response( + data={ + 'user': user_serializer.data, + 'tokens': { + 'access': access_token, + 'refresh': refresh_token, + 'access_expires_at': access_expires_at.isoformat(), + 'refresh_expires_at': refresh_expires_at.isoformat(), + } + }, + message='Registration successful', + status_code=status.HTTP_201_CREATED + ) + return error_response( + error='Validation failed', + errors=serializer.errors, + status_code=status.HTTP_400_BAD_REQUEST + ) @action(detail=False, methods=['post']) def login(self, request): @@ -707,10 +711,10 @@ class AuthViewSet(viewsets.GenericViewSet): try: user = User.objects.select_related('account', 'account__plan').get(email=email) except User.DoesNotExist: - return Response({ - 'success': False, - 'message': 'Invalid credentials' - }, status=status.HTTP_401_UNAUTHORIZED) + return error_response( + error='Invalid credentials', + status_code=status.HTTP_401_UNAUTHORIZED + ) if user.check_password(password): # Log the user in (create session for session authentication) @@ -727,27 +731,29 @@ class AuthViewSet(viewsets.GenericViewSet): refresh_expires_at = get_token_expiry('refresh') user_serializer = UserSerializer(user) - return Response({ - 'success': True, - 'message': 'Login successful', - 'user': user_serializer.data, - 'tokens': { - 'access': access_token, - 'refresh': refresh_token, - 'access_expires_at': access_expires_at.isoformat(), - 'refresh_expires_at': refresh_expires_at.isoformat(), - } - }) + return success_response( + data={ + 'user': user_serializer.data, + 'tokens': { + 'access': access_token, + 'refresh': refresh_token, + 'access_expires_at': access_expires_at.isoformat(), + 'refresh_expires_at': refresh_expires_at.isoformat(), + } + }, + message='Login successful' + ) - return Response({ - 'success': False, - 'message': 'Invalid credentials' - }, status=status.HTTP_401_UNAUTHORIZED) + return error_response( + error='Invalid credentials', + status_code=status.HTTP_401_UNAUTHORIZED + ) - return Response({ - 'success': False, - 'errors': serializer.errors - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Validation failed', + errors=serializer.errors, + status_code=status.HTTP_400_BAD_REQUEST + ) @action(detail=False, methods=['post'], permission_classes=[permissions.IsAuthenticated]) def change_password(self, request): @@ -756,23 +762,23 @@ class AuthViewSet(viewsets.GenericViewSet): if serializer.is_valid(): user = request.user if not user.check_password(serializer.validated_data['old_password']): - return Response({ - 'success': False, - 'message': 'Current password is incorrect' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Current password is incorrect', + status_code=status.HTTP_400_BAD_REQUEST + ) user.set_password(serializer.validated_data['new_password']) user.save() - return Response({ - 'success': True, - 'message': 'Password changed successfully' - }) + return success_response( + message='Password changed successfully' + ) - return Response({ - 'success': False, - 'errors': serializer.errors - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Validation failed', + errors=serializer.errors, + status_code=status.HTTP_400_BAD_REQUEST + ) @action(detail=False, methods=['get'], permission_classes=[permissions.IsAuthenticated]) def me(self, request): @@ -781,20 +787,20 @@ class AuthViewSet(viewsets.GenericViewSet): # This ensures account/plan changes are reflected immediately user = User.objects.select_related('account', 'account__plan').get(id=request.user.id) serializer = UserSerializer(user) - return Response({ - 'success': True, - 'user': serializer.data - }) + return success_response( + data={'user': serializer.data} + ) @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) def refresh(self, request): """Refresh access token using refresh token.""" serializer = RefreshTokenSerializer(data=request.data) if not serializer.is_valid(): - return Response({ - 'success': False, - 'errors': serializer.errors - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Validation failed', + errors=serializer.errors, + status_code=status.HTTP_400_BAD_REQUEST + ) refresh_token = serializer.validated_data['refresh'] @@ -804,10 +810,10 @@ class AuthViewSet(viewsets.GenericViewSet): # Verify it's a refresh token if payload.get('type') != 'refresh': - return Response({ - 'success': False, - 'message': 'Invalid token type' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Invalid token type', + status_code=status.HTTP_400_BAD_REQUEST + ) # Get user user_id = payload.get('user_id') @@ -816,10 +822,10 @@ class AuthViewSet(viewsets.GenericViewSet): try: user = User.objects.get(id=user_id) except User.DoesNotExist: - return Response({ - 'success': False, - 'message': 'User not found' - }, status=status.HTTP_404_NOT_FOUND) + return error_response( + error='User not found', + status_code=status.HTTP_404_NOT_FOUND + ) # Get account account_id = payload.get('account_id') @@ -837,17 +843,19 @@ class AuthViewSet(viewsets.GenericViewSet): access_token = generate_access_token(user, account) access_expires_at = get_token_expiry('access') - return Response({ - 'success': True, - 'access': access_token, - 'access_expires_at': access_expires_at.isoformat() - }) + return success_response( + data={ + 'access': access_token, + 'access_expires_at': access_expires_at.isoformat() + }, + message='Token refreshed successfully' + ) except jwt.InvalidTokenError as e: - return Response({ - 'success': False, - 'message': 'Invalid or expired refresh token' - }, status=status.HTTP_401_UNAUTHORIZED) + return error_response( + error='Invalid or expired refresh token', + status_code=status.HTTP_401_UNAUTHORIZED + ) @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) def request_reset(self, request):