fixing issues of integration with wordpress plugin
This commit is contained in:
@@ -12,6 +12,7 @@ from igny8_core.api.base import SiteSectorModelViewSet
|
||||
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
||||
from igny8_core.api.response import success_response, error_response
|
||||
from igny8_core.api.throttles import DebugScopedRateThrottle
|
||||
from igny8_core.auth.models import Site
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
from igny8_core.business.integration.services.integration_service import IntegrationService
|
||||
from igny8_core.business.integration.services.sync_service import SyncService
|
||||
@@ -131,19 +132,23 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
||||
permission_classes=[AllowAny], throttle_classes=[NoThrottle])
|
||||
def test_connection_collection(self, request):
|
||||
"""
|
||||
Collection-level test connection endpoint for frontend convenience.
|
||||
Test WordPress connection using Site.wp_api_key (single source of truth).
|
||||
|
||||
POST /api/v1/integration/integrations/test-connection/
|
||||
|
||||
Body:
|
||||
{
|
||||
"site_id": 123,
|
||||
"api_key": "...",
|
||||
"site_url": "https://example.com"
|
||||
"site_id": 123
|
||||
}
|
||||
|
||||
Tests:
|
||||
1. WordPress site is reachable
|
||||
2. IGNY8 plugin is installed
|
||||
3. Plugin has API key configured (matching Site.wp_api_key)
|
||||
"""
|
||||
import requests as http_requests
|
||||
|
||||
site_id = request.data.get('site_id')
|
||||
api_key = request.data.get('api_key')
|
||||
site_url = request.data.get('site_url')
|
||||
|
||||
if not site_id:
|
||||
return error_response('site_id is required', None, status.HTTP_400_BAD_REQUEST, request)
|
||||
@@ -155,80 +160,146 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
||||
except (Site.DoesNotExist, ValueError, TypeError):
|
||||
return error_response('Site not found or invalid', None, status.HTTP_404_NOT_FOUND, request)
|
||||
|
||||
# Authentication: accept either authenticated user OR matching API key in body
|
||||
api_key = request.data.get('api_key') or api_key
|
||||
authenticated = False
|
||||
# If request has a valid user and belongs to same account, allow
|
||||
if hasattr(request, 'user') and getattr(request.user, 'is_authenticated', False):
|
||||
try:
|
||||
# If user has account, ensure site belongs to user's account
|
||||
if site.account == request.user.account:
|
||||
authenticated = True
|
||||
except Exception:
|
||||
# Ignore and fallback to api_key check
|
||||
pass
|
||||
# Authentication: user must be authenticated and belong to same account
|
||||
if not hasattr(request, 'user') or not getattr(request.user, 'is_authenticated', False):
|
||||
return error_response('Authentication required', None, status.HTTP_403_FORBIDDEN, request)
|
||||
|
||||
try:
|
||||
if site.account != request.user.account:
|
||||
return error_response('Site does not belong to your account', None, status.HTTP_403_FORBIDDEN, request)
|
||||
except Exception:
|
||||
return error_response('Authentication failed', None, status.HTTP_403_FORBIDDEN, request)
|
||||
|
||||
# If not authenticated via session, allow if provided api_key matches site's stored wp_api_key
|
||||
if not authenticated:
|
||||
stored_key = getattr(site, 'wp_api_key', None)
|
||||
if stored_key and api_key and str(api_key) == str(stored_key):
|
||||
authenticated = True
|
||||
elif not stored_key:
|
||||
# API key not set on site - provide helpful error message
|
||||
return error_response(
|
||||
'API key not configured for this site. Please generate an API key in the IGNY8 app and ensure it is saved to the site.',
|
||||
None,
|
||||
status.HTTP_403_FORBIDDEN,
|
||||
request
|
||||
)
|
||||
elif api_key and stored_key and str(api_key) != str(stored_key):
|
||||
# API key provided but doesn't match
|
||||
return error_response(
|
||||
'Invalid API key. The provided API key does not match the one stored for this site.',
|
||||
None,
|
||||
status.HTTP_403_FORBIDDEN,
|
||||
request
|
||||
)
|
||||
|
||||
if not authenticated:
|
||||
return error_response('Authentication credentials were not provided.', None, status.HTTP_403_FORBIDDEN, request)
|
||||
|
||||
# Try to find an existing integration for this site+platform
|
||||
integration = SiteIntegration.objects.filter(site=site, platform='wordpress').first()
|
||||
|
||||
# If not found, create and save the integration to database (for status tracking, not credentials)
|
||||
integration_created = False
|
||||
if not integration:
|
||||
integration = SiteIntegration.objects.create(
|
||||
account=site.account,
|
||||
site=site,
|
||||
platform='wordpress',
|
||||
platform_type='cms',
|
||||
config_json={'site_url': site_url} if site_url else {},
|
||||
credentials_json={}, # API key is stored in Site.wp_api_key, not here
|
||||
is_active=True,
|
||||
sync_enabled=True
|
||||
# Get stored API key from Site model (single source of truth)
|
||||
stored_api_key = site.wp_api_key
|
||||
if not stored_api_key:
|
||||
return error_response(
|
||||
'API key not configured. Please generate an API key first.',
|
||||
None,
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
request
|
||||
)
|
||||
integration_created = True
|
||||
logger.info(f"[IntegrationViewSet] Created WordPress integration {integration.id} for site {site.id}")
|
||||
|
||||
service = IntegrationService()
|
||||
# Mark this as initial connection test since API key was provided in request body
|
||||
# This allows the test to pass even if WordPress plugin hasn't stored the key yet
|
||||
is_initial_connection = bool(api_key and request.data.get('api_key'))
|
||||
result = service._test_wordpress_connection(integration, is_initial_connection=is_initial_connection)
|
||||
# Get site URL
|
||||
site_url = site.domain or site.url
|
||||
if not site_url:
|
||||
return error_response(
|
||||
'Site URL not configured',
|
||||
None,
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
request
|
||||
)
|
||||
|
||||
if result.get('success'):
|
||||
# Include integration_id in response so plugin can store it
|
||||
result['integration_id'] = integration.id
|
||||
result['integration_created'] = integration_created
|
||||
return success_response(result, request=request)
|
||||
else:
|
||||
# If test failed and we just created integration, delete it
|
||||
if integration_created:
|
||||
integration.delete()
|
||||
logger.info(f"[IntegrationViewSet] Deleted integration {integration.id} due to failed connection test")
|
||||
return error_response(result.get('message', 'Connection test failed'), None, status.HTTP_400_BAD_REQUEST, request)
|
||||
# Health check results
|
||||
health_checks = {
|
||||
'site_url_configured': True,
|
||||
'api_key_configured': True,
|
||||
'wp_rest_api_reachable': False,
|
||||
'plugin_installed': False,
|
||||
'plugin_has_api_key': False,
|
||||
'api_key_verified': False, # NEW: Verifies IGNY8 and WordPress have SAME key
|
||||
}
|
||||
issues = []
|
||||
|
||||
try:
|
||||
# Check 1: WordPress REST API reachable
|
||||
try:
|
||||
rest_response = http_requests.get(
|
||||
f"{site_url.rstrip('/')}/wp-json/",
|
||||
timeout=10
|
||||
)
|
||||
if rest_response.status_code == 200:
|
||||
health_checks['wp_rest_api_reachable'] = True
|
||||
else:
|
||||
issues.append(f"WordPress REST API not reachable: HTTP {rest_response.status_code}")
|
||||
except Exception as e:
|
||||
issues.append(f"WordPress REST API unreachable: {str(e)}")
|
||||
|
||||
# Check 2: IGNY8 Plugin installed (public status endpoint)
|
||||
try:
|
||||
status_response = http_requests.get(
|
||||
f"{site_url.rstrip('/')}/wp-json/igny8/v1/status",
|
||||
timeout=10
|
||||
)
|
||||
if status_response.status_code == 200:
|
||||
health_checks['plugin_installed'] = True
|
||||
|
||||
status_data = status_response.json()
|
||||
plugin_data = status_data.get('data', status_data)
|
||||
|
||||
if plugin_data.get('connected') or plugin_data.get('has_api_key'):
|
||||
health_checks['plugin_has_api_key'] = True
|
||||
else:
|
||||
issues.append("Plugin installed but no API key configured in WordPress")
|
||||
else:
|
||||
issues.append(f"IGNY8 plugin not found: HTTP {status_response.status_code}")
|
||||
except Exception as e:
|
||||
issues.append(f"Cannot detect IGNY8 plugin: {str(e)}")
|
||||
|
||||
# Check 3: Verify API keys MATCH by making authenticated request
|
||||
# This is the CRITICAL check - WordPress must accept our API key
|
||||
if health_checks['plugin_installed'] and health_checks['plugin_has_api_key']:
|
||||
try:
|
||||
# Make authenticated request using Site.wp_api_key to dedicated verify endpoint
|
||||
verify_response = http_requests.get(
|
||||
f"{site_url.rstrip('/')}/wp-json/igny8/v1/verify-key",
|
||||
headers={
|
||||
'X-IGNY8-API-KEY': stored_api_key,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
if verify_response.status_code == 200:
|
||||
health_checks['api_key_verified'] = True
|
||||
elif verify_response.status_code in [401, 403]:
|
||||
issues.append("API key mismatch - WordPress has different key than IGNY8. Please copy the API key from IGNY8 to WordPress plugin settings.")
|
||||
else:
|
||||
issues.append(f"API key verification failed: HTTP {verify_response.status_code}")
|
||||
except Exception as e:
|
||||
issues.append(f"API key verification request failed: {str(e)}")
|
||||
|
||||
# Determine overall status - MUST include api_key_verified for true connection
|
||||
is_healthy = (
|
||||
health_checks['api_key_configured'] and
|
||||
health_checks['wp_rest_api_reachable'] and
|
||||
health_checks['plugin_installed'] and
|
||||
health_checks['plugin_has_api_key'] and
|
||||
health_checks['api_key_verified'] # CRITICAL: keys must match
|
||||
)
|
||||
|
||||
# Build message with clear guidance
|
||||
if is_healthy:
|
||||
message = "✅ WordPress integration is fully connected and verified"
|
||||
elif not health_checks['wp_rest_api_reachable']:
|
||||
message = "❌ Cannot reach WordPress site"
|
||||
elif not health_checks['plugin_installed']:
|
||||
message = "⚠️ WordPress is reachable but IGNY8 plugin not installed"
|
||||
elif not health_checks['plugin_has_api_key']:
|
||||
message = "⚠️ Plugin installed but no API key configured in WordPress"
|
||||
elif not health_checks['api_key_verified']:
|
||||
message = "⚠️ API key mismatch - copy the API key from IGNY8 to WordPress plugin"
|
||||
else:
|
||||
message = "❌ WordPress connection failed"
|
||||
|
||||
return success_response({
|
||||
'success': is_healthy,
|
||||
'message': message,
|
||||
'site_id': site.id,
|
||||
'site_name': site.name,
|
||||
'site_url': site_url,
|
||||
'api_key_configured': bool(stored_api_key),
|
||||
'health_checks': health_checks,
|
||||
'issues': issues if issues else None,
|
||||
}, request=request)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WordPress connection test failed: {e}")
|
||||
return error_response(
|
||||
f'Connection test failed: {str(e)}',
|
||||
None,
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
request
|
||||
)
|
||||
|
||||
@extend_schema(tags=['Integration'])
|
||||
@action(detail=True, methods=['post'])
|
||||
@@ -808,42 +879,71 @@ class IntegrationViewSet(SiteSectorModelViewSet):
|
||||
site.wp_api_key = api_key
|
||||
site.save(update_fields=['wp_api_key'])
|
||||
|
||||
# Get or create SiteIntegration (for integration status/config, NOT credentials)
|
||||
integration, created = SiteIntegration.objects.get_or_create(
|
||||
site=site,
|
||||
platform='wordpress',
|
||||
defaults={
|
||||
'account': site.account,
|
||||
'platform': 'wordpress',
|
||||
'platform_type': 'cms',
|
||||
'is_active': True,
|
||||
'sync_enabled': True,
|
||||
'credentials_json': {}, # Empty - API key is on Site model
|
||||
'config_json': {}
|
||||
}
|
||||
)
|
||||
|
||||
# If integration already exists, just ensure it's active
|
||||
if not created:
|
||||
integration.is_active = True
|
||||
integration.sync_enabled = True
|
||||
# Clear any old credentials_json API key (migrate to Site.wp_api_key)
|
||||
if integration.credentials_json.get('api_key'):
|
||||
integration.credentials_json = {}
|
||||
integration.save()
|
||||
|
||||
logger.info(
|
||||
f"Generated new API key for site {site.name} (ID: {site_id}), "
|
||||
f"stored in Site.wp_api_key (single source of truth)"
|
||||
)
|
||||
|
||||
# Serialize the integration with the new key
|
||||
serializer = self.get_serializer(integration)
|
||||
return success_response({
|
||||
'api_key': api_key,
|
||||
'site_id': site.id,
|
||||
'site_name': site.name,
|
||||
'site_url': site.domain or site.url,
|
||||
'message': 'API key generated successfully. WordPress integration is ready.',
|
||||
}, request=request)
|
||||
|
||||
@action(detail=False, methods=['post'], url_path='revoke-api-key')
|
||||
def revoke_api_key(self, request):
|
||||
"""
|
||||
Revoke (delete) the API key for a site's WordPress integration.
|
||||
|
||||
POST /api/v1/integration/integrations/revoke-api-key/
|
||||
|
||||
Body:
|
||||
{
|
||||
"site_id": 5
|
||||
}
|
||||
"""
|
||||
site_id = request.data.get('site_id')
|
||||
if not site_id:
|
||||
return error_response(
|
||||
'Site ID is required',
|
||||
None,
|
||||
status.HTTP_400_BAD_REQUEST,
|
||||
request
|
||||
)
|
||||
|
||||
try:
|
||||
site = Site.objects.get(id=site_id)
|
||||
except Site.DoesNotExist:
|
||||
return error_response(
|
||||
f'Site with ID {site_id} not found',
|
||||
None,
|
||||
status.HTTP_404_NOT_FOUND,
|
||||
request
|
||||
)
|
||||
|
||||
# Verify user has access to this site
|
||||
if site.account != request.user.account:
|
||||
return error_response(
|
||||
'You do not have permission to modify this site',
|
||||
None,
|
||||
status.HTTP_403_FORBIDDEN,
|
||||
request
|
||||
)
|
||||
|
||||
# SINGLE SOURCE OF TRUTH: Remove API key from Site.wp_api_key
|
||||
site.wp_api_key = None
|
||||
site.save(update_fields=['wp_api_key'])
|
||||
|
||||
logger.info(
|
||||
f"Revoked API key for site {site.name} (ID: {site_id})"
|
||||
)
|
||||
|
||||
return success_response({
|
||||
'integration': serializer.data,
|
||||
'api_key': api_key,
|
||||
'message': f"API key {'generated' if created else 'regenerated'} successfully",
|
||||
'site_id': site.id,
|
||||
'site_name': site.name,
|
||||
'message': 'API key revoked successfully. WordPress integration is now disconnected.',
|
||||
}, request=request)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user