Enhance API response handling and implement unified API standard across multiple modules. Added feature flags for unified exception handling and debug throttling in settings. Updated pagination and response formats in various viewsets to align with the new standard. Improved error handling and response validation in frontend components for better user feedback.

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-15 20:18:42 +00:00
parent 94f243f4a2
commit a75ebf2584
18 changed files with 1974 additions and 642 deletions

View File

@@ -13,6 +13,10 @@ from django.core.cache import cache
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from igny8_core.api.base import AccountModelViewSet
from igny8_core.api.response import success_response, error_response
from igny8_core.api.permissions import IsEditorOrAbove
from igny8_core.api.throttles import DebugScopedRateThrottle
from igny8_core.api.pagination import CustomPageNumberPagination
from .models import AIPrompt, AuthorProfile, Strategy
from .serializers import AIPromptSerializer, AuthorProfileSerializer, StrategySerializer
@@ -22,10 +26,14 @@ logger = logging.getLogger(__name__)
class AIPromptViewSet(AccountModelViewSet):
"""
ViewSet for managing AI prompts
Unified API Standard v1.0 compliant
"""
queryset = AIPrompt.objects.all()
serializer_class = AIPromptSerializer
permission_classes = [] # Allow any for now
permission_classes = [] # Allow any for now (backward compatibility)
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
pagination_class = CustomPageNumberPagination # Explicitly use custom pagination
def get_queryset(self):
"""Get prompts for the current account"""
@@ -37,28 +45,39 @@ class AIPromptViewSet(AccountModelViewSet):
try:
prompt = self.get_queryset().get(prompt_type=prompt_type)
serializer = self.get_serializer(prompt)
return Response(serializer.data)
return success_response(data=serializer.data, request=request)
except AIPrompt.DoesNotExist:
# Return default if not found
from .utils import get_default_prompt
default_value = get_default_prompt(prompt_type)
return Response({
'prompt_type': prompt_type,
'prompt_value': default_value,
'default_prompt': default_value,
'is_active': True,
})
return success_response(
data={
'prompt_type': prompt_type,
'prompt_value': default_value,
'default_prompt': default_value,
'is_active': True,
},
request=request
)
@action(detail=False, methods=['post'], url_path='save', url_name='save')
def save_prompt(self, request):
"""Save or update a prompt"""
"""Save or update a prompt - requires editor or above"""
prompt_type = request.data.get('prompt_type')
prompt_value = request.data.get('prompt_value')
if not prompt_type:
return Response({'error': 'prompt_type is required'}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='prompt_type is required',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
if prompt_value is None:
return Response({'error': 'prompt_value is required'}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='prompt_value is required',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
# Get account - try multiple methods
account = getattr(request, 'account', None)
@@ -78,7 +97,11 @@ class AIPromptViewSet(AccountModelViewSet):
pass
if not account:
return Response({'error': 'Account not found. Please ensure you are logged in.'}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='Account not found. Please ensure you are logged in.',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
# Get default prompt value if creating new
from .utils import get_default_prompt
@@ -100,11 +123,11 @@ class AIPromptViewSet(AccountModelViewSet):
prompt.save()
serializer = self.get_serializer(prompt)
return Response({
'success': True,
'data': serializer.data,
'message': f'{prompt.get_prompt_type_display()} saved successfully'
})
return success_response(
data=serializer.data,
message=f'{prompt.get_prompt_type_display()} saved successfully',
request=request
)
@action(detail=False, methods=['post'], url_path='reset', url_name='reset')
def reset_prompt(self, request):
@@ -112,7 +135,11 @@ class AIPromptViewSet(AccountModelViewSet):
prompt_type = request.data.get('prompt_type')
if not prompt_type:
return Response({'error': 'prompt_type is required'}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='prompt_type is required',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
# Get account - try multiple methods (same as integration_views)
account = getattr(request, 'account', None)
@@ -132,7 +159,11 @@ class AIPromptViewSet(AccountModelViewSet):
pass
if not account:
return Response({'error': 'Account not found. Please ensure you are logged in.'}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='Account not found. Please ensure you are logged in.',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
# Get default prompt
from .utils import get_default_prompt
@@ -154,19 +185,22 @@ class AIPromptViewSet(AccountModelViewSet):
prompt.save()
serializer = self.get_serializer(prompt)
return Response({
'success': True,
'data': serializer.data,
'message': f'{prompt.get_prompt_type_display()} reset to default'
})
return success_response(
data=serializer.data,
message=f'{prompt.get_prompt_type_display()} reset to default',
request=request
)
class AuthorProfileViewSet(AccountModelViewSet):
"""
ViewSet for managing Author Profiles
Unified API Standard v1.0 compliant
"""
queryset = AuthorProfile.objects.all()
serializer_class = AuthorProfileSerializer
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
search_fields = ['name', 'description', 'tone']
@@ -178,9 +212,12 @@ class AuthorProfileViewSet(AccountModelViewSet):
class StrategyViewSet(AccountModelViewSet):
"""
ViewSet for managing Strategies
Unified API Standard v1.0 compliant
"""
queryset = Strategy.objects.all()
serializer_class = StrategySerializer
throttle_scope = 'system'
throttle_classes = [DebugScopedRateThrottle]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
search_fields = ['name', 'description']
@@ -190,7 +227,7 @@ class StrategyViewSet(AccountModelViewSet):
@api_view(['GET'])
@permission_classes([AllowAny]) # Adjust permissions as needed
@permission_classes([AllowAny]) # Public endpoint for monitoring
def system_status(request):
"""
Comprehensive system status endpoint for monitoring
@@ -457,7 +494,7 @@ def system_status(request):
logger.error(f"Error getting module statistics: {str(e)}")
status_data['modules'] = {'error': str(e)}
return Response(status_data)
return success_response(data=status_data, request=request)
@api_view(['GET'])
@@ -469,19 +506,31 @@ def get_request_metrics(request, request_id):
"""
# Check if user is admin/developer
if not request.user.is_authenticated:
return Response({'error': 'Authentication required'}, status=http_status.HTTP_401_UNAUTHORIZED)
return error_response(
error='Authentication required',
status_code=http_status.HTTP_401_UNAUTHORIZED,
request=request
)
if not (hasattr(request.user, 'is_admin_or_developer') and request.user.is_admin_or_developer()):
return Response({'error': 'Admin access required'}, status=http_status.HTTP_403_FORBIDDEN)
return error_response(
error='Admin access required',
status_code=http_status.HTTP_403_FORBIDDEN,
request=request
)
# Get metrics from cache
from django.core.cache import cache
metrics = cache.get(f"resource_tracking_{request_id}")
if not metrics:
return Response({'error': 'Metrics not found or expired'}, status=http_status.HTTP_404_NOT_FOUND)
return error_response(
error='Metrics not found or expired',
status_code=http_status.HTTP_404_NOT_FOUND,
request=request
)
return Response(metrics)
return success_response(data=metrics, request=request)
@api_view(['POST'])
@@ -504,10 +553,11 @@ def gitea_webhook(request):
# Only process push events
if event_type != 'push':
return Response({
'status': 'ignored',
'message': f'Event type {event_type} is not processed'
}, status=http_status.HTTP_200_OK)
return success_response(
data={'status': 'ignored'},
message=f'Event type {event_type} is not processed',
request=request
)
# Extract repository information
repository = payload.get('repository', {})
@@ -518,10 +568,11 @@ def gitea_webhook(request):
# Only process pushes to main branch
if ref != 'refs/heads/main':
logger.info(f"[Webhook] Ignoring push to {ref}, only processing main branch")
return Response({
'status': 'ignored',
'message': f'Push to {ref} ignored, only main branch is processed'
}, status=http_status.HTTP_200_OK)
return success_response(
data={'status': 'ignored'},
message=f'Push to {ref} ignored, only main branch is processed',
request=request
)
# Get commit information
commits = payload.get('commits', [])
@@ -636,30 +687,35 @@ def gitea_webhook(request):
deployment_error = str(deploy_error)
logger.error(f"[Webhook] Deployment error: {deploy_error}", exc_info=True)
return Response({
'status': 'success' if deployment_success else 'partial',
'message': 'Webhook received and processed',
'repository': repo_full_name,
'branch': ref,
'commits': commit_count,
'pusher': pusher,
'event': event_type,
'deployment': {
'success': deployment_success,
'error': deployment_error
}
}, status=http_status.HTTP_200_OK)
return success_response(
data={
'status': 'success' if deployment_success else 'partial',
'repository': repo_full_name,
'branch': ref,
'commits': commit_count,
'pusher': pusher,
'event': event_type,
'deployment': {
'success': deployment_success,
'error': deployment_error
}
},
message='Webhook received and processed',
request=request
)
except json.JSONDecodeError as e:
logger.error(f"[Webhook] Invalid JSON payload: {e}")
return Response({
'status': 'error',
'message': 'Invalid JSON payload'
}, status=http_status.HTTP_400_BAD_REQUEST)
return error_response(
error='Invalid JSON payload',
status_code=http_status.HTTP_400_BAD_REQUEST,
request=request
)
except Exception as e:
logger.error(f"[Webhook] Error processing webhook: {e}", exc_info=True)
return Response({
'status': 'error',
'message': str(e)
}, status=http_status.HTTP_500_INTERNAL_SERVER_ERROR)
return error_response(
error=str(e),
status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)