Refactor WordPress integration service to use API key for connection testing

- Updated the `IntegrationService` to perform connection tests using only the API key, removing reliance on username and app password.
- Simplified health check logic and improved error messaging for better clarity.
- Added functionality to revoke API keys in the `WordPressIntegrationForm` component.
- Enhanced site settings page with a site selector and improved integration status display.
- Cleaned up unused code and improved overall structure for better maintainability.
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-22 09:31:07 +00:00
parent 1a1214d93f
commit 029c66a0f1
12 changed files with 1337 additions and 456 deletions

View File

@@ -204,7 +204,7 @@ class IntegrationService:
integration: SiteIntegration
) -> Dict[str, Any]:
"""
Test WordPress connection with comprehensive bidirectional health check.
Test WordPress connection using API key only.
Args:
integration: SiteIntegration instance
@@ -212,11 +212,9 @@ class IntegrationService:
Returns:
dict: Connection test result with detailed health status
"""
from igny8_core.utils.wordpress import WordPressClient
import requests
config = integration.config_json
credentials = integration.get_credentials()
# Try to get site URL from multiple sources
site_url = config.get('site_url')
@@ -234,133 +232,77 @@ class IntegrationService:
if not site_url:
return {
'success': False,
'message': 'WordPress site URL not configured. Please set the site URL in integration config, site domain, or legacy wp_url field.',
'message': 'WordPress site URL not configured.',
'details': {
'integration_id': integration.id,
'site_id': integration.site.id,
'site_name': integration.site.name,
'checks': {
'site_url_configured': False,
'wp_rest_api_reachable': False,
'plugin_installed': False,
'plugin_can_reach_igny8': False,
'bidirectional_communication': False
}
}
}
username = credentials.get('username')
app_password = credentials.get('app_password')
# Get API key from site
api_key = integration.site.wp_api_key
if not api_key:
return {
'success': False,
'message': 'API key not configured.',
'details': {}
}
# Initialize health check results
health_checks = {
'site_url_configured': True,
'api_key_configured': True,
'wp_rest_api_reachable': False,
'wp_rest_api_authenticated': False,
'plugin_installed': False,
'plugin_connected': False,
'plugin_can_reach_igny8': False,
'bidirectional_communication': False
'plugin_has_api_key': False,
}
issues = []
try:
# Check 1: WordPress REST API reachable (public)
# Check 1: WordPress REST API reachable (public endpoint)
try:
client = WordPressClient(site_url, username, app_password)
basic_test = client.test_connection()
if basic_test.get('success'):
rest_response = requests.get(
f"{site_url.rstrip('/')}/wp-json/",
timeout=10
)
if rest_response.status_code == 200:
health_checks['wp_rest_api_reachable'] = True
logger.info(f"[IntegrationService] ✓ WordPress REST API reachable: {site_url}")
else:
issues.append(f"WordPress REST API not reachable: {basic_test.get('message')}")
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: WordPress REST API with authentication
if username and app_password:
try:
# Try authenticated endpoint
auth_response = requests.get(
f"{site_url.rstrip('/')}/wp-json/wp/v2/users/me",
auth=(username, app_password),
timeout=10
)
if auth_response.status_code == 200:
health_checks['wp_rest_api_authenticated'] = True
logger.info(f"[IntegrationService] ✓ WordPress authentication valid")
else:
issues.append(f"WordPress authentication failed: HTTP {auth_response.status_code}")
except Exception as e:
issues.append(f"WordPress authentication check failed: {str(e)}")
# Check 3: IGNY8 Plugin installed and reachable
# Check 2: IGNY8 Plugin installed and has API key configured
try:
plugin_response = requests.get(
f"{site_url.rstrip('/')}/wp-json/igny8/v1/",
timeout=10
)
if plugin_response.status_code in [200, 404]: # 404 is ok, means REST API exists
health_checks['plugin_installed'] = True
logger.info(f"[IntegrationService] ✓ IGNY8 plugin REST endpoints detected")
else:
issues.append(f"IGNY8 plugin endpoints not found: HTTP {plugin_response.status_code}")
except Exception as e:
issues.append(f"Cannot detect IGNY8 plugin: {str(e)}")
# Check 4: Plugin connection status (check if plugin has API key)
try:
# Try to get plugin status endpoint
status_response = requests.get(
f"{site_url.rstrip('/')}/wp-json/igny8/v1/status",
timeout=10
)
if status_response.status_code == 200:
health_checks['plugin_installed'] = True
logger.info(f"[IntegrationService] ✓ IGNY8 plugin installed")
status_data = status_response.json()
if status_data.get('connected') or status_data.get('has_api_key'):
health_checks['plugin_connected'] = True
health_checks['plugin_has_api_key'] = True
logger.info(f"[IntegrationService] ✓ Plugin has API key configured")
else:
issues.append("Plugin installed but not configured with API key")
else:
# Endpoint might not exist, that's okay
logger.debug(f"[IntegrationService] Plugin status endpoint returned: {status_response.status_code}")
issues.append(f"IGNY8 plugin not found: HTTP {status_response.status_code}")
except Exception as e:
logger.debug(f"[IntegrationService] Plugin status check: {str(e)}")
# Check 5: Bidirectional communication (can plugin reach us?)
# This is the critical check - can WordPress plugin make API calls to IGNY8 backend?
try:
# Check if plugin can reach our API by looking at last successful sync
last_sync = integration.last_sync_at
last_structure_sync = config.get('content_types', {}).get('last_structure_fetch')
if last_sync or last_structure_sync:
health_checks['plugin_can_reach_igny8'] = True
health_checks['bidirectional_communication'] = True
logger.info(f"[IntegrationService] ✓ Bidirectional communication confirmed (last sync: {last_sync})")
else:
issues.append("No successful syncs detected - plugin may not be able to reach IGNY8 backend")
except Exception as e:
logger.debug(f"[IntegrationService] Bidirectional check: {str(e)}")
# Overall success determination
# Minimum requirements for "success":
# 1. WordPress REST API must be reachable
# 2. Plugin should be installed
# 3. Ideally, bidirectional communication works
issues.append(f"Cannot detect IGNY8 plugin: {str(e)}")
# Success determination - only based on API key and plugin
is_healthy = (
health_checks['api_key_configured'] and
health_checks['wp_rest_api_reachable'] and
health_checks['plugin_installed']
)
is_fully_functional = (
is_healthy and
health_checks['plugin_connected'] and
health_checks['bidirectional_communication']
health_checks['plugin_installed'] and
health_checks['plugin_has_api_key']
)
# Save site_url to config if successful and not already set
@@ -371,27 +313,27 @@ class IntegrationService:
logger.info(f"[IntegrationService] Saved site_url to integration {integration.id} config: {site_url}")
# Build response message
if is_fully_functional:
message = "✅ WordPress integration is healthy and fully functional"
elif is_healthy and health_checks['plugin_installed']:
message = "⚠️ WordPress is reachable and plugin detected, but bidirectional sync not confirmed. Plugin may need API key configuration."
elif health_checks['wp_rest_api_reachable']:
message = "⚠️ WordPress is reachable but IGNY8 plugin not detected or not configured"
if is_healthy:
message = "✅ WordPress integration is connected and authenticated via API key"
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 is installed but API key not configured in WordPress"
else:
message = "❌ WordPress connection failed"
return {
'success': is_healthy,
'fully_functional': is_fully_functional,
'fully_functional': is_healthy,
'message': message,
'site_url': site_url,
'health_checks': health_checks,
'issues': issues if issues else None,
'wp_version': basic_test.get('wp_version') if health_checks['wp_rest_api_reachable'] else None,
'details': {
'last_sync': str(integration.last_sync_at) if integration.last_sync_at else None,
'integration_active': integration.is_active,
'sync_enabled': integration.sync_enabled
}
}

View File

@@ -27,6 +27,27 @@ class IntegrationViewSet(SiteSectorModelViewSet):
throttle_scope = 'integration'
throttle_classes = [DebugScopedRateThrottle]
def get_queryset(self):
"""
Override to filter integrations by site.
SiteIntegration only has 'site' field (no 'sector'), so SiteSectorModelViewSet's
filtering doesn't apply. We manually filter by site here.
"""
queryset = super().get_queryset()
# Get site parameter from query params
site_id = self.request.query_params.get('site_id') or self.request.query_params.get('site')
if site_id:
try:
site_id_int = int(site_id)
queryset = queryset.filter(site_id=site_id_int)
except (ValueError, TypeError):
# Invalid site_id, return empty queryset
queryset = queryset.none()
return queryset
def get_serializer_class(self):
from rest_framework import serializers