329 lines
12 KiB
Python
329 lines
12 KiB
Python
"""
|
|
WordPress Webhook Handlers
|
|
Receives status updates and sync events from WordPress
|
|
"""
|
|
from rest_framework.decorators import api_view, permission_classes, throttle_classes
|
|
from rest_framework.permissions import AllowAny
|
|
from rest_framework.throttling import BaseThrottle
|
|
from rest_framework.response import Response
|
|
from rest_framework import status as http_status
|
|
from django.utils import timezone
|
|
import logging
|
|
|
|
from igny8_core.api.response import success_response, error_response
|
|
from igny8_core.business.content.models import Content
|
|
from igny8_core.business.integration.models import SiteIntegration, SyncEvent
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class NoThrottle(BaseThrottle):
|
|
"""No throttle for webhooks"""
|
|
def allow_request(self, request, view):
|
|
return True
|
|
|
|
|
|
@api_view(['POST'])
|
|
@permission_classes([AllowAny]) # Webhook authentication handled inside
|
|
@throttle_classes([NoThrottle])
|
|
def wordpress_status_webhook(request):
|
|
"""
|
|
Receive WordPress post status updates via webhook
|
|
|
|
POST /api/v1/integration/webhooks/wordpress/status/
|
|
|
|
Headers:
|
|
- X-IGNY8-API-KEY: <api_key>
|
|
|
|
Body:
|
|
{
|
|
"post_id": 123, # WordPress post ID
|
|
"content_id": 456, # IGNY8 content ID
|
|
"post_status": "publish", # WordPress status
|
|
"post_url": "https://example.com/post/...",
|
|
"post_title": "Post Title",
|
|
"site_url": "https://example.com"
|
|
}
|
|
"""
|
|
try:
|
|
# Validate API key
|
|
api_key = request.headers.get('X-IGNY8-API-KEY') or request.headers.get('Authorization', '').replace('Bearer ', '')
|
|
if not api_key:
|
|
return error_response(
|
|
error='Missing API key',
|
|
status_code=http_status.HTTP_401_UNAUTHORIZED,
|
|
request=request
|
|
)
|
|
|
|
# Get webhook data
|
|
data = request.data
|
|
content_id = data.get('content_id')
|
|
post_id = data.get('post_id')
|
|
post_status = data.get('post_status')
|
|
post_url = data.get('post_url')
|
|
site_url = data.get('site_url')
|
|
|
|
logger.info(f"[wordpress_status_webhook] Received webhook: content_id={content_id}, post_id={post_id}, status={post_status}")
|
|
|
|
# Validate required fields
|
|
if not content_id or not post_id or not post_status:
|
|
return error_response(
|
|
error='Missing required fields: content_id, post_id, post_status',
|
|
status_code=http_status.HTTP_400_BAD_REQUEST,
|
|
request=request
|
|
)
|
|
|
|
# Find content
|
|
try:
|
|
content = Content.objects.get(id=content_id)
|
|
except Content.DoesNotExist:
|
|
logger.error(f"[wordpress_status_webhook] Content {content_id} not found")
|
|
return error_response(
|
|
error=f'Content {content_id} not found',
|
|
status_code=http_status.HTTP_404_NOT_FOUND,
|
|
request=request
|
|
)
|
|
|
|
# Find site integration by site_url and verify API key
|
|
if site_url:
|
|
integration = SiteIntegration.objects.filter(
|
|
site=content.site,
|
|
platform='wordpress',
|
|
config_json__site_url=site_url
|
|
).first()
|
|
else:
|
|
# Fallback: find any active WordPress integration for this site
|
|
integration = SiteIntegration.objects.filter(
|
|
site=content.site,
|
|
platform='wordpress',
|
|
is_active=True
|
|
).first()
|
|
|
|
if not integration:
|
|
logger.error(f"[wordpress_status_webhook] No WordPress integration found for site {content.site.name}")
|
|
return error_response(
|
|
error='WordPress integration not found for this site',
|
|
status_code=http_status.HTTP_404_NOT_FOUND,
|
|
request=request
|
|
)
|
|
|
|
# Verify API key matches integration
|
|
stored_api_key = integration.credentials_json.get('api_key')
|
|
if not stored_api_key or stored_api_key != api_key:
|
|
logger.error(f"[wordpress_status_webhook] Invalid API key for integration {integration.id}")
|
|
return error_response(
|
|
error='Invalid API key',
|
|
status_code=http_status.HTTP_401_UNAUTHORIZED,
|
|
request=request
|
|
)
|
|
|
|
# Map WordPress status to IGNY8 status
|
|
status_map = {
|
|
'publish': 'published',
|
|
'draft': 'draft',
|
|
'pending': 'review',
|
|
'private': 'published',
|
|
'trash': 'draft',
|
|
'future': 'review',
|
|
}
|
|
igny8_status = status_map.get(post_status, 'review')
|
|
|
|
# Update content
|
|
old_status = content.status
|
|
old_wp_status = content.metadata.get('wordpress_status') if content.metadata else None
|
|
|
|
content.external_id = str(post_id)
|
|
if post_url:
|
|
content.external_url = post_url
|
|
|
|
# Only update IGNY8 status if WordPress status changed from draft to publish
|
|
if post_status == 'publish' and old_status != 'published':
|
|
content.status = 'published'
|
|
|
|
# Update WordPress status in metadata
|
|
if not content.metadata:
|
|
content.metadata = {}
|
|
content.metadata['wordpress_status'] = post_status
|
|
content.metadata['last_wp_sync'] = timezone.now().isoformat()
|
|
|
|
content.save(update_fields=['external_id', 'external_url', 'status', 'metadata', 'updated_at'])
|
|
|
|
logger.info(f"[wordpress_status_webhook] Updated content {content_id}:")
|
|
logger.info(f" - Status: {old_status} → {content.status}")
|
|
logger.info(f" - WP Status: {old_wp_status} → {post_status}")
|
|
logger.info(f" - External ID: {content.external_id}")
|
|
|
|
# Log sync event
|
|
SyncEvent.objects.create(
|
|
integration=integration,
|
|
site=content.site,
|
|
account=content.account,
|
|
event_type='webhook',
|
|
action='status_update',
|
|
description=f"WordPress status updated: '{content.title}' is now {post_status}",
|
|
success=True,
|
|
content_id=content.id,
|
|
external_id=str(post_id),
|
|
details={
|
|
'old_status': old_status,
|
|
'new_status': content.status,
|
|
'old_wp_status': old_wp_status,
|
|
'new_wp_status': post_status,
|
|
'post_url': post_url,
|
|
}
|
|
)
|
|
|
|
return success_response(
|
|
data={
|
|
'content_id': content.id,
|
|
'status': content.status,
|
|
'wordpress_status': post_status,
|
|
'external_id': content.external_id,
|
|
'external_url': content.external_url,
|
|
},
|
|
message='Content status updated successfully',
|
|
request=request
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[wordpress_status_webhook] Error processing webhook: {str(e)}", exc_info=True)
|
|
return error_response(
|
|
error=f'Failed to process webhook: {str(e)}',
|
|
status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
request=request
|
|
)
|
|
|
|
|
|
@api_view(['POST'])
|
|
@permission_classes([AllowAny])
|
|
@throttle_classes([NoThrottle])
|
|
def wordpress_metadata_webhook(request):
|
|
"""
|
|
Receive WordPress metadata updates via webhook
|
|
|
|
POST /api/v1/integration/webhooks/wordpress/metadata/
|
|
|
|
Headers:
|
|
- X-IGNY8-API-KEY: <api_key>
|
|
|
|
Body:
|
|
{
|
|
"post_id": 123,
|
|
"content_id": 456,
|
|
"site_url": "https://example.com",
|
|
"metadata": {
|
|
"categories": [...],
|
|
"tags": [...],
|
|
"author": {...},
|
|
"modified_date": "2025-11-30T..."
|
|
}
|
|
}
|
|
"""
|
|
try:
|
|
# Validate API key
|
|
api_key = request.headers.get('X-IGNY8-API-KEY') or request.headers.get('Authorization', '').replace('Bearer ', '')
|
|
if not api_key:
|
|
return error_response(
|
|
error='Missing API key',
|
|
status_code=http_status.HTTP_401_UNAUTHORIZED,
|
|
request=request
|
|
)
|
|
|
|
# Get webhook data
|
|
data = request.data
|
|
content_id = data.get('content_id')
|
|
post_id = data.get('post_id')
|
|
site_url = data.get('site_url')
|
|
metadata = data.get('metadata', {})
|
|
|
|
logger.info(f"[wordpress_metadata_webhook] Received webhook: content_id={content_id}, post_id={post_id}")
|
|
|
|
# Validate required fields
|
|
if not content_id or not post_id:
|
|
return error_response(
|
|
error='Missing required fields: content_id, post_id',
|
|
status_code=http_status.HTTP_400_BAD_REQUEST,
|
|
request=request
|
|
)
|
|
|
|
# Find content
|
|
try:
|
|
content = Content.objects.get(id=content_id)
|
|
except Content.DoesNotExist:
|
|
return error_response(
|
|
error=f'Content {content_id} not found',
|
|
status_code=http_status.HTTP_404_NOT_FOUND,
|
|
request=request
|
|
)
|
|
|
|
# Find integration and verify API key
|
|
if site_url:
|
|
integration = SiteIntegration.objects.filter(
|
|
site=content.site,
|
|
platform='wordpress',
|
|
config_json__site_url=site_url
|
|
).first()
|
|
else:
|
|
integration = SiteIntegration.objects.filter(
|
|
site=content.site,
|
|
platform='wordpress',
|
|
is_active=True
|
|
).first()
|
|
|
|
if not integration:
|
|
return error_response(
|
|
error='WordPress integration not found',
|
|
status_code=http_status.HTTP_404_NOT_FOUND,
|
|
request=request
|
|
)
|
|
|
|
# Verify API key
|
|
stored_api_key = integration.credentials_json.get('api_key')
|
|
if not stored_api_key or stored_api_key != api_key:
|
|
return error_response(
|
|
error='Invalid API key',
|
|
status_code=http_status.HTTP_401_UNAUTHORIZED,
|
|
request=request
|
|
)
|
|
|
|
# Update content metadata
|
|
if not content.metadata:
|
|
content.metadata = {}
|
|
|
|
content.metadata['wp_metadata'] = metadata
|
|
content.metadata['last_metadata_sync'] = timezone.now().isoformat()
|
|
content.save(update_fields=['metadata', 'updated_at'])
|
|
|
|
# Log sync event
|
|
SyncEvent.objects.create(
|
|
integration=integration,
|
|
site=content.site,
|
|
account=content.account,
|
|
event_type='metadata_sync',
|
|
action='metadata_update',
|
|
description=f"WordPress metadata synced for '{content.title}'",
|
|
success=True,
|
|
content_id=content.id,
|
|
external_id=str(post_id),
|
|
details=metadata
|
|
)
|
|
|
|
logger.info(f"[wordpress_metadata_webhook] Updated metadata for content {content_id}")
|
|
|
|
return success_response(
|
|
data={
|
|
'content_id': content.id,
|
|
'metadata_updated': True,
|
|
},
|
|
message='Metadata updated successfully',
|
|
request=request
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[wordpress_metadata_webhook] Error: {str(e)}", exc_info=True)
|
|
return error_response(
|
|
error=f'Failed to process webhook: {str(e)}',
|
|
status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
request=request
|
|
)
|