Add health check endpoint and refactor integration response handling
- Introduced a new public health check endpoint at `api/ping/` to verify API responsiveness. - Refactored integration response handling to utilize a unified success and error response format across various methods in `IntegrationSettingsViewSet`, improving consistency and clarity in API responses. - Updated URL patterns to include the new ping endpoint and adjusted imports accordingly.
This commit is contained in:
@@ -29,7 +29,9 @@ def postprocess_schema_filter_tags(result, generator, request, public):
|
|||||||
|
|
||||||
# If no explicit tags found, infer from path
|
# If no explicit tags found, infer from path
|
||||||
if not filtered_tags:
|
if not filtered_tags:
|
||||||
if '/auth/' in path or '/api/v1/auth/' in path:
|
if '/ping' in path or '/system/ping/' in path:
|
||||||
|
filtered_tags = ['System'] # Health check endpoint
|
||||||
|
elif '/auth/' in path or '/api/v1/auth/' in path:
|
||||||
filtered_tags = ['Authentication']
|
filtered_tags = ['Authentication']
|
||||||
elif '/planner/' in path or '/api/v1/planner/' in path:
|
elif '/planner/' in path or '/api/v1/planner/' in path:
|
||||||
filtered_tags = ['Planner']
|
filtered_tags = ['Planner']
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Unified API Standard v1.0 compliant
|
|||||||
import logging
|
import logging
|
||||||
from rest_framework import viewsets, status
|
from rest_framework import viewsets, status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from igny8_core.api.base import AccountModelViewSet
|
from igny8_core.api.base import AccountModelViewSet
|
||||||
@@ -140,7 +139,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
logger.info(f"[test_connection] Testing {integration_type} connection with API key (length={len(api_key) if api_key else 0})")
|
logger.info(f"[test_connection] Testing {integration_type} connection with API key (length={len(api_key) if api_key else 0})")
|
||||||
try:
|
try:
|
||||||
if integration_type == 'openai':
|
if integration_type == 'openai':
|
||||||
return self._test_openai(api_key, config)
|
return self._test_openai(api_key, config, request)
|
||||||
elif integration_type == 'runware':
|
elif integration_type == 'runware':
|
||||||
return self._test_runware(api_key, request)
|
return self._test_runware(api_key, request)
|
||||||
else:
|
else:
|
||||||
@@ -160,7 +159,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
request=request
|
request=request
|
||||||
)
|
)
|
||||||
|
|
||||||
def _test_openai(self, api_key: str, config: dict = None):
|
def _test_openai(self, api_key: str, config: dict = None, request=None):
|
||||||
"""
|
"""
|
||||||
Test OpenAI API connection.
|
Test OpenAI API connection.
|
||||||
EXACT match to reference plugin's igny8_test_connection() function.
|
EXACT match to reference plugin's igny8_test_connection() function.
|
||||||
@@ -215,33 +214,38 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
rates = MODEL_RATES.get(model, {'input': 2.00, 'output': 8.00})
|
rates = MODEL_RATES.get(model, {'input': 2.00, 'output': 8.00})
|
||||||
cost = (input_tokens * rates['input'] + output_tokens * rates['output']) / 1000000
|
cost = (input_tokens * rates['input'] + output_tokens * rates['output']) / 1000000
|
||||||
|
|
||||||
return Response({
|
return success_response(
|
||||||
'success': True,
|
data={
|
||||||
'message': 'API connection and response test successful!',
|
'message': 'API connection and response test successful!',
|
||||||
'model_used': model,
|
'model_used': model,
|
||||||
'response': response_text,
|
'response': response_text,
|
||||||
'tokens_used': f"{input_tokens} / {output_tokens}",
|
'tokens_used': f"{input_tokens} / {output_tokens}",
|
||||||
'total_tokens': total_tokens,
|
'total_tokens': total_tokens,
|
||||||
'cost': f'${cost:.4f}',
|
'cost': f'${cost:.4f}',
|
||||||
'full_response': response_data,
|
'full_response': response_data,
|
||||||
})
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error='API responded but no content received',
|
||||||
'message': 'API responded but no content received',
|
errors={'response': [response.text[:500]]},
|
||||||
'response': response.text[:500]
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
})
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
body = response.text
|
body = response.text
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'HTTP {response.status_code} – {body[:200]}',
|
||||||
'message': f'HTTP {response.status_code} – {body[:200]}'
|
status_code=response.status_code,
|
||||||
}, status=response.status_code)
|
request=request
|
||||||
|
)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=str(e),
|
||||||
'message': str(e)
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Simple connection test without API call (reference plugin: GET /v1/models)
|
# Simple connection test without API call (reference plugin: GET /v1/models)
|
||||||
try:
|
try:
|
||||||
@@ -254,23 +258,27 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if response.status_code >= 200 and response.status_code < 300:
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
return Response({
|
return success_response(
|
||||||
'success': True,
|
data={
|
||||||
'message': 'API connection successful!',
|
'message': 'API connection successful!',
|
||||||
'model_used': model,
|
'model_used': model,
|
||||||
'response': 'Connection verified without API call'
|
'response': 'Connection verified without API call'
|
||||||
})
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
body = response.text
|
body = response.text
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'HTTP {response.status_code} – {body[:200]}',
|
||||||
'message': f'HTTP {response.status_code} – {body[:200]}'
|
status_code=response.status_code,
|
||||||
}, status=response.status_code)
|
request=request
|
||||||
|
)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=str(e),
|
||||||
'message': str(e)
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
def _test_runware(self, api_key: str, request):
|
def _test_runware(self, api_key: str, request):
|
||||||
"""
|
"""
|
||||||
@@ -338,11 +346,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
error_text = response.text
|
error_text = response.text
|
||||||
logger.error(f"[_test_runware] HTTP error {response.status_code}: {error_text[:200]}")
|
logger.error(f"[_test_runware] HTTP error {response.status_code}: {error_text[:200]}")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'HTTP {response.status_code}: {error_text[:200]}',
|
||||||
'error': f'HTTP {response.status_code}: {error_text[:200]}',
|
status_code=response.status_code,
|
||||||
'message': 'Runware API validation failed'
|
request=request
|
||||||
}, status=response.status_code)
|
)
|
||||||
|
|
||||||
# Parse response - Reference plugin checks: $body['data'][0]['imageURL']
|
# Parse response - Reference plugin checks: $body['data'][0]['imageURL']
|
||||||
body = response.json()
|
body = response.json()
|
||||||
@@ -357,15 +365,17 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
image_url = first_item.get('imageURL') or first_item.get('image_url')
|
image_url = first_item.get('imageURL') or first_item.get('image_url')
|
||||||
if image_url:
|
if image_url:
|
||||||
logger.info(f"[_test_runware] Success! Image URL: {image_url[:50]}...")
|
logger.info(f"[_test_runware] Success! Image URL: {image_url[:50]}...")
|
||||||
return Response({
|
return success_response(
|
||||||
'success': True,
|
data={
|
||||||
'message': '✅ Runware API connected successfully!',
|
'message': '✅ Runware API connected successfully!',
|
||||||
'image_url': image_url,
|
'image_url': image_url,
|
||||||
'cost': '$0.0090',
|
'cost': '$0.0090',
|
||||||
'provider': 'runware',
|
'provider': 'runware',
|
||||||
'model': 'runware:97@1',
|
'model': 'runware:97@1',
|
||||||
'size': '128x128'
|
'size': '128x128'
|
||||||
})
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
# Check for errors - Reference plugin line 4998: elseif (isset($body['errors'][0]['message']))
|
# Check for errors - Reference plugin line 4998: elseif (isset($body['errors'][0]['message']))
|
||||||
if isinstance(body, dict) and 'errors' in body:
|
if isinstance(body, dict) and 'errors' in body:
|
||||||
@@ -373,26 +383,26 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
if isinstance(errors, list) and len(errors) > 0:
|
if isinstance(errors, list) and len(errors) > 0:
|
||||||
error_msg = errors[0].get('message', 'Unknown Runware API error')
|
error_msg = errors[0].get('message', 'Unknown Runware API error')
|
||||||
logger.error(f"[_test_runware] Runware API error: {error_msg}")
|
logger.error(f"[_test_runware] Runware API error: {error_msg}")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'❌ {error_msg}',
|
||||||
'error': f'❌ {error_msg}',
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
'message': 'Runware API validation failed'
|
request=request
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
)
|
||||||
|
|
||||||
# Unknown response format
|
# Unknown response format
|
||||||
logger.error(f"[_test_runware] Unknown response format: {body}")
|
logger.error(f"[_test_runware] Unknown response format: {body}")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error='❌ Unknown response from Runware.',
|
||||||
'error': '❌ Unknown response from Runware.',
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
'message': 'Runware API validation failed'
|
request=request
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[_test_runware] Exception in Runware API test: {str(e)}", exc_info=True)
|
logger.error(f"[_test_runware] Exception in Runware API test: {str(e)}", exc_info=True)
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'Runware API test failed: {str(e)}',
|
||||||
'error': f'Runware API test failed: {str(e)}',
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
'message': 'Runware API validation failed'
|
request=request
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
)
|
||||||
|
|
||||||
def generate_image(self, request, pk=None, **kwargs):
|
def generate_image(self, request, pk=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -419,10 +429,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
if pk != 'image_generation':
|
if pk != 'image_generation':
|
||||||
logger.error(f"[generate_image] Invalid pk: {pk}, expected 'image_generation'")
|
logger.error(f"[generate_image] Invalid pk: {pk}, expected 'image_generation'")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'Image generation endpoint only available for image_generation integration, got: {pk}',
|
||||||
'error': f'Image generation endpoint only available for image_generation integration, got: {pk}'
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
# Get account
|
# Get account
|
||||||
logger.info("[generate_image] Step 1: Getting account")
|
logger.info("[generate_image] Step 1: Getting account")
|
||||||
@@ -445,10 +456,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
if not account:
|
if not account:
|
||||||
logger.error("[generate_image] ERROR: No account found, returning error response")
|
logger.error("[generate_image] ERROR: No account found, returning error response")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error='Account not found',
|
||||||
'error': 'Account not found'
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"[generate_image] Account resolved: {account.id if account else 'None'}")
|
logger.info(f"[generate_image] Account resolved: {account.id if account else 'None'}")
|
||||||
|
|
||||||
@@ -467,10 +479,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
if not prompt:
|
if not prompt:
|
||||||
logger.error("[generate_image] ERROR: Prompt is empty")
|
logger.error("[generate_image] ERROR: Prompt is empty")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error='Prompt is required',
|
||||||
'error': 'Prompt is required'
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
# Get API key from saved settings for the specified provider only
|
# Get API key from saved settings for the specified provider only
|
||||||
logger.info(f"[generate_image] Step 3: Getting API key for provider: {provider}")
|
logger.info(f"[generate_image] Step 3: Getting API key for provider: {provider}")
|
||||||
@@ -502,17 +515,19 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
logger.info(f"[generate_image] Step 4: Validating {provider} provider and API key")
|
logger.info(f"[generate_image] Step 4: Validating {provider} provider and API key")
|
||||||
if provider not in ['openai', 'runware']:
|
if provider not in ['openai', 'runware']:
|
||||||
logger.error(f"[generate_image] ERROR: Invalid provider: {provider}")
|
logger.error(f"[generate_image] ERROR: Invalid provider: {provider}")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'Invalid provider: {provider}. Must be "openai" or "runware"',
|
||||||
'error': f'Invalid provider: {provider}. Must be "openai" or "runware"'
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
if not api_key or not integration_enabled:
|
if not api_key or not integration_enabled:
|
||||||
logger.error(f"[generate_image] ERROR: {provider.upper()} API key not configured or integration not enabled")
|
logger.error(f"[generate_image] ERROR: {provider.upper()} API key not configured or integration not enabled")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'{provider.upper()} API key not configured or integration not enabled',
|
||||||
'error': f'{provider.upper()} API key not configured or integration not enabled'
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"[generate_image] {provider.upper()} API key validated successfully")
|
logger.info(f"[generate_image] {provider.upper()} API key validated successfully")
|
||||||
|
|
||||||
@@ -543,14 +558,14 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
if result.get('error'):
|
if result.get('error'):
|
||||||
logger.error(f"[generate_image] ERROR from AIProcessor: {result.get('error')}")
|
logger.error(f"[generate_image] ERROR from AIProcessor: {result.get('error')}")
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=result['error'],
|
||||||
'error': result['error']
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("[generate_image] Image generation successful, returning response")
|
logger.info("[generate_image] Image generation successful, returning response")
|
||||||
response_data = {
|
response_data = {
|
||||||
'success': True,
|
|
||||||
'image_url': result.get('url'),
|
'image_url': result.get('url'),
|
||||||
'revised_prompt': result.get('revised_prompt'),
|
'revised_prompt': result.get('revised_prompt'),
|
||||||
'model': model,
|
'model': model,
|
||||||
@@ -558,19 +573,27 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
'cost': f"${result.get('cost', 0):.4f}" if result.get('cost') else None,
|
'cost': f"${result.get('cost', 0):.4f}" if result.get('cost') else None,
|
||||||
}
|
}
|
||||||
logger.info(f"[generate_image] Returning success response: {response_data}")
|
logger.info(f"[generate_image] Returning success response: {response_data}")
|
||||||
return Response(response_data)
|
return success_response(
|
||||||
|
data=response_data,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[generate_image] EXCEPTION in image generation: {str(e)}", exc_info=True)
|
logger.error(f"[generate_image] EXCEPTION in image generation: {str(e)}", exc_info=True)
|
||||||
return Response({
|
return error_response(
|
||||||
'success': False,
|
error=f'Failed to generate image: {str(e)}',
|
||||||
'error': f'Failed to generate image: {str(e)}'
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
"""Create integration settings"""
|
"""Create integration settings"""
|
||||||
integration_type = request.data.get('integration_type')
|
integration_type = request.data.get('integration_type')
|
||||||
if not integration_type:
|
if not integration_type:
|
||||||
return Response({'error': 'integration_type is required'}, status=status.HTTP_400_BAD_REQUEST)
|
return error_response(
|
||||||
|
error='integration_type is required',
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
return self.save_settings(request, integration_type)
|
return self.save_settings(request, integration_type)
|
||||||
|
|
||||||
def save_settings(self, request, pk=None):
|
def save_settings(self, request, pk=None):
|
||||||
@@ -693,9 +716,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
import traceback
|
import traceback
|
||||||
error_trace = traceback.format_exc()
|
error_trace = traceback.format_exc()
|
||||||
logger.error(f"Full traceback: {error_trace}")
|
logger.error(f"Full traceback: {error_trace}")
|
||||||
return Response({
|
return error_response(
|
||||||
'error': f'Failed to save settings: {str(e)}'
|
error=f'Failed to save settings: {str(e)}',
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
def get_settings(self, request, pk=None):
|
def get_settings(self, request, pk=None):
|
||||||
"""Get integration settings - defaults to AWS-admin settings if account doesn't have its own"""
|
"""Get integration settings - defaults to AWS-admin settings if account doesn't have its own"""
|
||||||
@@ -772,10 +797,11 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if not account:
|
if not account:
|
||||||
return Response({
|
return error_response(
|
||||||
'error': 'Account not found',
|
error='Account not found',
|
||||||
'type': 'AuthenticationError'
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
}, status=status.HTTP_401_UNAUTHORIZED)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .models import IntegrationSettings
|
from .models import IntegrationSettings
|
||||||
@@ -800,39 +826,44 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
provider = config.get('provider', 'openai')
|
provider = config.get('provider', 'openai')
|
||||||
default_featured_size = '1280x832' if provider == 'runware' else '1024x1024'
|
default_featured_size = '1280x832' if provider == 'runware' else '1024x1024'
|
||||||
|
|
||||||
return Response({
|
return success_response(
|
||||||
'success': True,
|
data={
|
||||||
'config': {
|
'config': {
|
||||||
'provider': config.get('provider', 'openai'),
|
'provider': config.get('provider', 'openai'),
|
||||||
'model': model,
|
'model': model,
|
||||||
'image_type': config.get('image_type', 'realistic'),
|
'image_type': config.get('image_type', 'realistic'),
|
||||||
'max_in_article_images': config.get('max_in_article_images', 2),
|
'max_in_article_images': config.get('max_in_article_images', 2),
|
||||||
'image_format': config.get('image_format', 'webp'),
|
'image_format': config.get('image_format', 'webp'),
|
||||||
'desktop_enabled': config.get('desktop_enabled', True),
|
'desktop_enabled': config.get('desktop_enabled', True),
|
||||||
'mobile_enabled': config.get('mobile_enabled', True),
|
'mobile_enabled': config.get('mobile_enabled', True),
|
||||||
'featured_image_size': config.get('featured_image_size', default_featured_size),
|
'featured_image_size': config.get('featured_image_size', default_featured_size),
|
||||||
'desktop_image_size': config.get('desktop_image_size', '1024x1024'),
|
'desktop_image_size': config.get('desktop_image_size', '1024x1024'),
|
||||||
}
|
}
|
||||||
}, status=status.HTTP_200_OK)
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except IntegrationSettings.DoesNotExist:
|
except IntegrationSettings.DoesNotExist:
|
||||||
return Response({
|
return success_response(
|
||||||
'success': True,
|
data={
|
||||||
'config': {
|
'config': {
|
||||||
'provider': 'openai',
|
'provider': 'openai',
|
||||||
'model': 'dall-e-3',
|
'model': 'dall-e-3',
|
||||||
'image_type': 'realistic',
|
'image_type': 'realistic',
|
||||||
'max_in_article_images': 2,
|
'max_in_article_images': 2,
|
||||||
'image_format': 'webp',
|
'image_format': 'webp',
|
||||||
'desktop_enabled': True,
|
'desktop_enabled': True,
|
||||||
'mobile_enabled': True,
|
'mobile_enabled': True,
|
||||||
}
|
}
|
||||||
}, status=status.HTTP_200_OK)
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[get_image_generation_settings] Error: {str(e)}", exc_info=True)
|
logger.error(f"[get_image_generation_settings] Error: {str(e)}", exc_info=True)
|
||||||
return Response({
|
return error_response(
|
||||||
'error': str(e),
|
error=str(e),
|
||||||
'type': 'ServerError'
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
@action(detail=False, methods=['get'], url_path='task_progress/(?P<task_id>[^/.]+)', url_name='task-progress')
|
@action(detail=False, methods=['get'], url_path='task_progress/(?P<task_id>[^/.]+)', url_name='task-progress')
|
||||||
def task_progress(self, request, task_id=None):
|
def task_progress(self, request, task_id=None):
|
||||||
@@ -841,9 +872,10 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
GET /api/v1/system/settings/task_progress/<task_id>/
|
GET /api/v1/system/settings/task_progress/<task_id>/
|
||||||
"""
|
"""
|
||||||
if not task_id:
|
if not task_id:
|
||||||
return Response(
|
return error_response(
|
||||||
{'error': 'Task ID is required'},
|
error='Task ID is required',
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
request=request
|
||||||
)
|
)
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -862,14 +894,18 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
RedisConnectionError = ConnectionError
|
RedisConnectionError = ConnectionError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning("Celery not available - task progress cannot be retrieved")
|
logger.warning("Celery not available - task progress cannot be retrieved")
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'PENDING',
|
data={
|
||||||
'meta': {
|
'state': 'PENDING',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Celery not available - cannot retrieve task status',
|
'percentage': 0,
|
||||||
'error': 'Celery not configured'
|
'message': 'Celery not available - cannot retrieve task status',
|
||||||
}
|
'error': 'Celery not configured'
|
||||||
}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
}
|
||||||
|
},
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create AsyncResult - this should not raise an exception even if task doesn't exist
|
# Create AsyncResult - this should not raise an exception even if task doesn't exist
|
||||||
@@ -937,51 +973,64 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Error extracting error from task.info: {str(e)}")
|
logger.debug(f"Error extracting error from task.info: {str(e)}")
|
||||||
|
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'FAILURE',
|
data={
|
||||||
'meta': {
|
'state': 'FAILURE',
|
||||||
'error': error_msg,
|
'meta': {
|
||||||
'error_type': error_type,
|
'error': error_msg,
|
||||||
'percentage': 0,
|
'error_type': error_type,
|
||||||
'message': f'Error: {error_msg}',
|
'percentage': 0,
|
||||||
'request_steps': request_steps,
|
'message': f'Error: {error_msg}',
|
||||||
'response_steps': response_steps,
|
'request_steps': request_steps,
|
||||||
}
|
'response_steps': response_steps,
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except (KombuOperationalError, RedisConnectionError, ConnectionError) as conn_exc:
|
except (KombuOperationalError, RedisConnectionError, ConnectionError) as conn_exc:
|
||||||
# Backend connection error - task might not be registered yet or backend is down
|
# Backend connection error - task might not be registered yet or backend is down
|
||||||
logger.warning(f"Backend connection error accessing task.state for {task_id}: {type(conn_exc).__name__}: {str(conn_exc)}")
|
logger.warning(f"Backend connection error accessing task.state for {task_id}: {type(conn_exc).__name__}: {str(conn_exc)}")
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'PENDING',
|
data={
|
||||||
'meta': {
|
'state': 'PENDING',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Task is being queued...',
|
'percentage': 0,
|
||||||
'phase': 'initializing',
|
'message': 'Task is being queued...',
|
||||||
'error': None # Don't show as error, just pending
|
'phase': 'initializing',
|
||||||
}
|
'error': None # Don't show as error, just pending
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except Exception as state_exc:
|
except Exception as state_exc:
|
||||||
logger.error(f"Unexpected error accessing task.state: {type(state_exc).__name__}: {str(state_exc)}")
|
logger.error(f"Unexpected error accessing task.state: {type(state_exc).__name__}: {str(state_exc)}")
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'UNKNOWN',
|
data={
|
||||||
'meta': {
|
'state': 'UNKNOWN',
|
||||||
'error': f'Error accessing task: {str(state_exc)}',
|
'meta': {
|
||||||
'percentage': 0,
|
'error': f'Error accessing task: {str(state_exc)}',
|
||||||
'message': f'Error: {str(state_exc)}',
|
'percentage': 0,
|
||||||
}
|
'message': f'Error: {str(state_exc)}',
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
}
|
||||||
|
},
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
# Check if task exists and is accessible
|
# Check if task exists and is accessible
|
||||||
if task_state is None:
|
if task_state is None:
|
||||||
# Task doesn't exist or hasn't been registered yet
|
# Task doesn't exist or hasn't been registered yet
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'PENDING',
|
data={
|
||||||
'meta': {
|
'state': 'PENDING',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Task not found or not yet registered',
|
'percentage': 0,
|
||||||
'phase': 'initializing',
|
'message': 'Task not found or not yet registered',
|
||||||
}
|
'phase': 'initializing',
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
# Safely get task info/result
|
# Safely get task info/result
|
||||||
# Try to get error from multiple sources
|
# Try to get error from multiple sources
|
||||||
@@ -1114,10 +1163,13 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
# Include image_queue if available (for image generation)
|
# Include image_queue if available (for image generation)
|
||||||
if 'image_queue' in meta:
|
if 'image_queue' in meta:
|
||||||
response_meta['image_queue'] = meta['image_queue']
|
response_meta['image_queue'] = meta['image_queue']
|
||||||
return Response({
|
return success_response(
|
||||||
'state': task_state,
|
data={
|
||||||
'meta': response_meta
|
'state': task_state,
|
||||||
})
|
'meta': response_meta
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
elif task_state == 'SUCCESS':
|
elif task_state == 'SUCCESS':
|
||||||
result = task_result or {}
|
result = task_result or {}
|
||||||
meta = result if isinstance(result, dict) else {}
|
meta = result if isinstance(result, dict) else {}
|
||||||
@@ -1133,10 +1185,13 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
response_meta['request_steps'] = meta['request_steps']
|
response_meta['request_steps'] = meta['request_steps']
|
||||||
if 'response_steps' in meta:
|
if 'response_steps' in meta:
|
||||||
response_meta['response_steps'] = meta['response_steps']
|
response_meta['response_steps'] = meta['response_steps']
|
||||||
return Response({
|
return success_response(
|
||||||
'state': task_state,
|
data={
|
||||||
'meta': response_meta
|
'state': task_state,
|
||||||
})
|
'meta': response_meta
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
elif task_state == 'FAILURE':
|
elif task_state == 'FAILURE':
|
||||||
# Try to get error from task.info meta first (this is where run_ai_task sets it)
|
# Try to get error from task.info meta first (this is where run_ai_task sets it)
|
||||||
if not error_message and isinstance(task_info, dict):
|
if not error_message and isinstance(task_info, dict):
|
||||||
@@ -1211,42 +1266,55 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
error_type = meta['error_type']
|
error_type = meta['error_type']
|
||||||
response_meta['error_type'] = error_type
|
response_meta['error_type'] = error_type
|
||||||
|
|
||||||
return Response({
|
return success_response(
|
||||||
'state': task_state,
|
data={
|
||||||
'meta': response_meta
|
'state': task_state,
|
||||||
})
|
'meta': response_meta
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# PENDING, STARTED, or other states
|
# PENDING, STARTED, or other states
|
||||||
return Response({
|
return success_response(
|
||||||
'state': task_state,
|
data={
|
||||||
'meta': {
|
'state': task_state,
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Task is starting...',
|
'percentage': 0,
|
||||||
'phase': 'initializing',
|
'message': 'Task is starting...',
|
||||||
}
|
'phase': 'initializing',
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except (KombuOperationalError, RedisConnectionError, ConnectionError) as conn_error:
|
except (KombuOperationalError, RedisConnectionError, ConnectionError) as conn_error:
|
||||||
# Backend connection error - task might not be registered yet or backend is down
|
# Backend connection error - task might not be registered yet or backend is down
|
||||||
logger.warning(f"Backend connection error for task {task_id}: {type(conn_error).__name__}: {str(conn_error)}")
|
logger.warning(f"Backend connection error for task {task_id}: {type(conn_error).__name__}: {str(conn_error)}")
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'PENDING',
|
data={
|
||||||
'meta': {
|
'state': 'PENDING',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Task is being queued...',
|
'percentage': 0,
|
||||||
'phase': 'initializing',
|
'message': 'Task is being queued...',
|
||||||
'error': None # Don't show as error, just pending
|
'phase': 'initializing',
|
||||||
}
|
'error': None # Don't show as error, just pending
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
except Exception as task_error:
|
except Exception as task_error:
|
||||||
logger.error(f"Error accessing Celery task {task_id}: {type(task_error).__name__}: {str(task_error)}", exc_info=True)
|
logger.error(f"Error accessing Celery task {task_id}: {type(task_error).__name__}: {str(task_error)}", exc_info=True)
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'UNKNOWN',
|
data={
|
||||||
'meta': {
|
'state': 'UNKNOWN',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': f'Error accessing task: {str(task_error)}',
|
'percentage': 0,
|
||||||
'error': str(task_error)
|
'message': f'Error accessing task: {str(task_error)}',
|
||||||
}
|
'error': str(task_error)
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
}
|
||||||
|
},
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Check if it's a connection-related error - treat as PENDING instead of error
|
# Check if it's a connection-related error - treat as PENDING instead of error
|
||||||
@@ -1263,19 +1331,22 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
if is_connection_error:
|
if is_connection_error:
|
||||||
logger.warning(f"Connection error getting task progress for {task_id}: {error_type}: {str(e)}")
|
logger.warning(f"Connection error getting task progress for {task_id}: {error_type}: {str(e)}")
|
||||||
return Response({
|
return success_response(
|
||||||
'state': 'PENDING',
|
data={
|
||||||
'meta': {
|
'state': 'PENDING',
|
||||||
'percentage': 0,
|
'meta': {
|
||||||
'message': 'Task is being queued...',
|
'percentage': 0,
|
||||||
'phase': 'initializing',
|
'message': 'Task is being queued...',
|
||||||
'error': None
|
'phase': 'initializing',
|
||||||
}
|
'error': None
|
||||||
})
|
}
|
||||||
|
},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Error getting task progress for {task_id}: {error_type}: {str(e)}", exc_info=True)
|
logger.error(f"Error getting task progress for {task_id}: {error_type}: {str(e)}", exc_info=True)
|
||||||
return Response(
|
return success_response(
|
||||||
{
|
data={
|
||||||
'state': 'ERROR',
|
'state': 'ERROR',
|
||||||
'meta': {
|
'meta': {
|
||||||
'error': f'Error getting task status: {str(e)}',
|
'error': f'Error getting task status: {str(e)}',
|
||||||
@@ -1283,6 +1354,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
|
|||||||
'message': f'Error: {str(e)}'
|
'message': f'Error: {str(e)}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
request=request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ URL patterns for system module.
|
|||||||
"""
|
"""
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from .views import AIPromptViewSet, AuthorProfileViewSet, StrategyViewSet, system_status, get_request_metrics, gitea_webhook
|
from .views import AIPromptViewSet, AuthorProfileViewSet, StrategyViewSet, system_status, get_request_metrics, gitea_webhook, ping
|
||||||
from .integration_views import IntegrationSettingsViewSet
|
from .integration_views import IntegrationSettingsViewSet
|
||||||
from .settings_views import (
|
from .settings_views import (
|
||||||
SystemSettingsViewSet, AccountSettingsViewSet, UserSettingsViewSet,
|
SystemSettingsViewSet, AccountSettingsViewSet, UserSettingsViewSet,
|
||||||
@@ -51,6 +51,8 @@ integration_image_gen_settings_viewset = IntegrationSettingsViewSet.as_view({
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
|
# Public health check endpoint (API Standard v1.0 requirement)
|
||||||
|
path('ping/', ping, name='system-ping'),
|
||||||
# System status endpoint
|
# System status endpoint
|
||||||
path('status/', system_status, name='system-status'),
|
path('status/', system_status, name='system-status'),
|
||||||
# Request metrics endpoint
|
# Request metrics endpoint
|
||||||
|
|||||||
@@ -269,6 +269,24 @@ class StrategyViewSet(AccountModelViewSet):
|
|||||||
filterset_fields = ['is_active', 'sector']
|
filterset_fields = ['is_active', 'sector']
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
@permission_classes([AllowAny]) # Public endpoint
|
||||||
|
@extend_schema(
|
||||||
|
tags=['System'],
|
||||||
|
summary='Health Check',
|
||||||
|
description='Simple health check endpoint to verify API is responding'
|
||||||
|
)
|
||||||
|
def ping(request):
|
||||||
|
"""
|
||||||
|
Simple health check endpoint
|
||||||
|
Returns unified format: {success: true, data: {status: 'ok'}}
|
||||||
|
"""
|
||||||
|
return success_response(
|
||||||
|
data={'status': 'ok'},
|
||||||
|
request=request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
@permission_classes([AllowAny]) # Public endpoint for monitoring
|
@permission_classes([AllowAny]) # Public endpoint for monitoring
|
||||||
def system_status(request):
|
def system_status(request):
|
||||||
|
|||||||
@@ -268,8 +268,12 @@ SPECTACULAR_SETTINGS = {
|
|||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
All endpoints require JWT Bearer token authentication except:
|
All endpoints require JWT Bearer token authentication except:
|
||||||
|
- `GET /api/v1/system/ping/` - Health check endpoint
|
||||||
- `POST /api/v1/auth/login/` - User login
|
- `POST /api/v1/auth/login/` - User login
|
||||||
- `POST /api/v1/auth/register/` - User registration
|
- `POST /api/v1/auth/register/` - User registration
|
||||||
|
- `GET /api/v1/auth/plans/` - List subscription plans
|
||||||
|
- `GET /api/v1/auth/industries/` - List industries
|
||||||
|
- `GET /api/v1/system/status/` - System status
|
||||||
|
|
||||||
Include token in Authorization header:
|
Include token in Authorization header:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ export default function ImageGenerationCard({
|
|||||||
console.log('[ImageGenerationCard] Making request to image generation endpoint');
|
console.log('[ImageGenerationCard] Making request to image generation endpoint');
|
||||||
console.log('[ImageGenerationCard] Request body:', requestBody);
|
console.log('[ImageGenerationCard] Request body:', requestBody);
|
||||||
|
|
||||||
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
|
// So data is the extracted response payload
|
||||||
const data = await fetchAPI('/v1/system/settings/integrations/image_generation/generate/', {
|
const data = await fetchAPI('/v1/system/settings/integrations/image_generation/generate/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
@@ -145,8 +147,10 @@ export default function ImageGenerationCard({
|
|||||||
|
|
||||||
console.log('[ImageGenerationCard] Response data:', data);
|
console.log('[ImageGenerationCard] Response data:', data);
|
||||||
|
|
||||||
if (!data.success) {
|
// fetchAPI extracts data from unified format, so data is the response payload
|
||||||
throw new Error(data.error || 'Failed to generate image');
|
// If fetchAPI didn't throw, the request was successful
|
||||||
|
if (!data || typeof data !== 'object') {
|
||||||
|
throw new Error('Invalid response format');
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageData = {
|
const imageData = {
|
||||||
|
|||||||
@@ -81,38 +81,30 @@ export default function ValidationCard({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test endpoint returns Response({success: True, ...}) directly (not unified format)
|
// Test endpoint now returns unified format {success: true, data: {...}}
|
||||||
// So fetchAPI may or may not extract it - handle both cases
|
// fetchAPI extracts the data field, so data is the inner object
|
||||||
const data = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/test/`, {
|
const data = await fetchAPI(`/v1/system/settings/integrations/${integrationId}/test/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if data has success field (direct Response format) or is extracted data
|
// fetchAPI extracts data from unified format, so data is the response payload
|
||||||
if (data && typeof data === 'object' && ('success' in data ? data.success : true)) {
|
if (data && typeof data === 'object') {
|
||||||
// If data has success field, use it; otherwise assume success (extracted data)
|
// Success response - data contains message, model_used, response, etc.
|
||||||
const isSuccess = data.success !== false;
|
setTestResult({
|
||||||
if (isSuccess) {
|
success: true,
|
||||||
setTestResult({
|
message: data.message || 'API connection successful!',
|
||||||
success: true,
|
model_used: data.model_used || data.model,
|
||||||
message: data.message || 'API connection successful!',
|
response: data.response,
|
||||||
model_used: data.model_used || data.model,
|
tokens_used: data.tokens_used,
|
||||||
response: data.response,
|
total_tokens: data.total_tokens,
|
||||||
tokens_used: data.tokens_used,
|
cost: data.cost,
|
||||||
total_tokens: data.total_tokens,
|
full_response: data.full_response || {
|
||||||
cost: data.cost,
|
image_url: data.image_url,
|
||||||
full_response: data.full_response || {
|
provider: data.provider,
|
||||||
image_url: data.image_url,
|
size: data.size,
|
||||||
provider: data.provider,
|
},
|
||||||
size: data.size,
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setTestResult({
|
|
||||||
success: false,
|
|
||||||
message: data.error || data.message || 'API connection failed',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setTestResult({
|
setTestResult({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -334,8 +334,7 @@ export default function Integration() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||||
// But test endpoint may return {success: true, ...} directly (not wrapped)
|
// So data is the extracted response payload
|
||||||
// So data could be either the extracted data object or the full response
|
|
||||||
const data = await fetchAPI(`/v1/system/settings/integrations/${selectedIntegration}/test/`, {
|
const data = await fetchAPI(`/v1/system/settings/integrations/${selectedIntegration}/test/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -344,48 +343,23 @@ export default function Integration() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle both unified format (extracted) and direct format
|
// fetchAPI extracts data from unified format, so data is the response payload
|
||||||
// If data has success field, it's the direct response (not extracted)
|
|
||||||
// If data doesn't have success but has other fields, it's extracted data (successful)
|
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
if (data.success === true || data.success === false) {
|
// Success response - data contains message, response, tokens_used, etc.
|
||||||
// Direct response format (not extracted by fetchAPI)
|
toast.success(data.message || 'API connection test successful!');
|
||||||
if (data.success) {
|
if (data.response) {
|
||||||
toast.success(data.message || 'API connection test successful!');
|
toast.info(`Response: ${data.response}`);
|
||||||
if (data.response) {
|
}
|
||||||
toast.info(`Response: ${data.response}`);
|
if (data.tokens_used) {
|
||||||
}
|
toast.info(`Tokens used: ${data.tokens_used}`);
|
||||||
if (data.tokens_used) {
|
}
|
||||||
toast.info(`Tokens used: ${data.tokens_used}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update validation status to success
|
// Update validation status to success
|
||||||
if (selectedIntegration) {
|
if (selectedIntegration) {
|
||||||
setValidationStatuses(prev => ({
|
setValidationStatuses(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[selectedIntegration]: 'success',
|
[selectedIntegration]: 'success',
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(data.error || data.message || 'Connection test failed');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Extracted data format (successful response)
|
|
||||||
toast.success('API connection test successful!');
|
|
||||||
if (data.response) {
|
|
||||||
toast.info(`Response: ${data.response}`);
|
|
||||||
}
|
|
||||||
if (data.tokens_used) {
|
|
||||||
toast.info(`Tokens used: ${data.tokens_used}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update validation status to success
|
|
||||||
if (selectedIntegration) {
|
|
||||||
setValidationStatuses(prev => ({
|
|
||||||
...prev,
|
|
||||||
[selectedIntegration]: 'success',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid response format');
|
throw new Error('Invalid response format');
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ Authorization: Bearer <access_token>
|
|||||||
- `GET /api/v1/auth/plans/`
|
- `GET /api/v1/auth/plans/`
|
||||||
- `GET /api/v1/auth/industries/`
|
- `GET /api/v1/auth/industries/`
|
||||||
- `GET /api/v1/system/status/`
|
- `GET /api/v1/system/status/`
|
||||||
- `GET /api/ping/` (health check)
|
- `GET /api/v1/system/ping/` (health check)
|
||||||
|
|
||||||
**All other endpoints require JWT authentication.**
|
**All other endpoints require JWT authentication.**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user