fixing issues of integration with wordpress plugin

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-12 23:25:47 +00:00
parent ad828a9fcd
commit 5c3aa90e91
18 changed files with 1414 additions and 427 deletions

View File

@@ -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)