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:
@@ -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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user