1234
This commit is contained in:
@@ -56,28 +56,36 @@ class WordPressAdapter(BaseAdapter):
|
||||
}
|
||||
"""
|
||||
try:
|
||||
logger.info(f"[WordPressAdapter.publish] 🎬 Starting WordPress publish")
|
||||
|
||||
# Extract content data
|
||||
if hasattr(content, 'title'):
|
||||
# Content model instance
|
||||
title = content.title
|
||||
content_html = getattr(content, 'content_html', '') or ''
|
||||
logger.info(f"[WordPressAdapter.publish] 📄 Content: id={content.id}, title='{title}', html_length={len(content_html)}")
|
||||
elif isinstance(content, dict):
|
||||
# Dict with content data
|
||||
title = content.get('title', '')
|
||||
content_html = content.get('content_html', '')
|
||||
logger.info(f"[WordPressAdapter.publish] 📄 Content dict: title='{title}', html_length={len(content_html)}")
|
||||
else:
|
||||
raise ValueError(f"Unsupported content type: {type(content)}")
|
||||
|
||||
# Get site URL
|
||||
site_url = destination_config.get('site_url')
|
||||
if not site_url:
|
||||
logger.error(f"[WordPressAdapter.publish] ❌ site_url is missing in destination_config")
|
||||
raise ValueError("site_url is required in destination_config")
|
||||
logger.info(f"[WordPressAdapter.publish] 🌐 Target site: {site_url}")
|
||||
|
||||
# Check if using API key authentication
|
||||
api_key = destination_config.get('api_key')
|
||||
logger.info(f"[WordPressAdapter.publish] 🔐 Auth method: {'API Key' if api_key else 'Username/Password'}")
|
||||
|
||||
if api_key:
|
||||
# Use IGNY8 custom endpoint with API key
|
||||
logger.info(f"[WordPressAdapter.publish] 🔑 Using API key authentication method")
|
||||
return self._publish_via_api_key(
|
||||
site_url=site_url,
|
||||
api_key=api_key,
|
||||
@@ -88,6 +96,7 @@ class WordPressAdapter(BaseAdapter):
|
||||
)
|
||||
else:
|
||||
# Use standard WordPress REST API with username/password
|
||||
logger.info(f"[WordPressAdapter.publish] 👤 Using username/password authentication method")
|
||||
return self._publish_via_username_password(
|
||||
site_url=site_url,
|
||||
username=destination_config.get('username'),
|
||||
@@ -128,12 +137,15 @@ class WordPressAdapter(BaseAdapter):
|
||||
import requests
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] 📦 Preparing payload for {site_url}")
|
||||
|
||||
# Generate excerpt
|
||||
excerpt = ''
|
||||
if content_html:
|
||||
excerpt = strip_tags(content_html)[:150].strip()
|
||||
if len(content_html) > 150:
|
||||
excerpt += '...'
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] 📝 Generated excerpt: {excerpt[:50]}...")
|
||||
|
||||
# Prepare payload
|
||||
content_data = {
|
||||
@@ -143,20 +155,29 @@ class WordPressAdapter(BaseAdapter):
|
||||
'excerpt': excerpt,
|
||||
'status': destination_config.get('status', 'publish'),
|
||||
}
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] ✅ Base payload prepared: content_id={content_data['content_id']}, status={content_data['status']}")
|
||||
|
||||
# Add optional fields from content model
|
||||
optional_fields = []
|
||||
if hasattr(content, 'meta_title'):
|
||||
content_data['seo_title'] = content.meta_title or ''
|
||||
optional_fields.append('meta_title')
|
||||
if hasattr(content, 'meta_description'):
|
||||
content_data['seo_description'] = content.meta_description or ''
|
||||
optional_fields.append('meta_description')
|
||||
if hasattr(content, 'primary_keyword'):
|
||||
content_data['primary_keyword'] = content.primary_keyword or ''
|
||||
optional_fields.append('primary_keyword')
|
||||
if hasattr(content, 'secondary_keywords'):
|
||||
content_data['secondary_keywords'] = content.secondary_keywords or []
|
||||
optional_fields.append('secondary_keywords')
|
||||
if hasattr(content, 'cluster') and content.cluster:
|
||||
content_data['cluster_id'] = content.cluster.id
|
||||
optional_fields.append('cluster_id')
|
||||
if hasattr(content, 'sector') and content.sector:
|
||||
content_data['sector_id'] = content.sector.id
|
||||
optional_fields.append('sector_id')
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] ➕ Added optional fields: {', '.join(optional_fields)}")
|
||||
|
||||
# Call WordPress endpoint
|
||||
url = f"{site_url.rstrip('/')}/wp-json/igny8/v1/publish-content/"
|
||||
@@ -165,10 +186,14 @@ class WordPressAdapter(BaseAdapter):
|
||||
'X-IGNY8-API-KEY': api_key,
|
||||
}
|
||||
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] 🚀 POST to WordPress: {url}")
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] 📡 Payload size: {len(str(content_data))} chars")
|
||||
response = requests.post(url, json=content_data, headers=headers, timeout=30)
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] 📬 WordPress response: status={response.status_code}")
|
||||
|
||||
if response.status_code == 201:
|
||||
wp_data = response.json().get('data', {})
|
||||
logger.info(f"[WordPressAdapter._publish_via_api_key] ✅ Success! WordPress post created: post_id={wp_data.get('post_id')}, url={wp_data.get('post_url')}")
|
||||
return {
|
||||
'success': True,
|
||||
'external_id': str(wp_data.get('post_id')),
|
||||
@@ -181,6 +206,7 @@ class WordPressAdapter(BaseAdapter):
|
||||
}
|
||||
else:
|
||||
error_msg = f"HTTP {response.status_code}: {response.text}"
|
||||
logger.error(f"[WordPressAdapter._publish_via_api_key] ❌ WordPress API error: {error_msg}")
|
||||
return {
|
||||
'success': False,
|
||||
'external_id': None,
|
||||
|
||||
@@ -76,9 +76,13 @@ class PublisherService:
|
||||
"""
|
||||
from igny8_core.business.content.models import Content
|
||||
|
||||
logger.info(f"[PublisherService.publish_content] 🎯 Starting publish: content_id={content_id}, destinations={destinations}")
|
||||
|
||||
try:
|
||||
content = Content.objects.get(id=content_id, account=account)
|
||||
logger.info(f"[PublisherService.publish_content] 📄 Content found: title='{content.title}', site={content.site.name if content.site else 'None'}")
|
||||
except Content.DoesNotExist:
|
||||
logger.error(f"[PublisherService.publish_content] ❌ Content {content_id} not found")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Content {content_id} not found'
|
||||
@@ -86,12 +90,14 @@ class PublisherService:
|
||||
|
||||
results = []
|
||||
for destination in destinations:
|
||||
logger.info(f"[PublisherService.publish_content] 🔄 Publishing to destination: {destination}")
|
||||
try:
|
||||
result = self._publish_to_destination(content, destination, account)
|
||||
logger.info(f"[PublisherService.publish_content] {'✅' if result.get('success') else '❌'} Destination {destination} result: {result}")
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error publishing content {content_id} to {destination}: {str(e)}",
|
||||
f"[PublisherService.publish_content] ❌ Error publishing content {content_id} to {destination}: {str(e)}",
|
||||
exc_info=True
|
||||
)
|
||||
results.append({
|
||||
@@ -100,8 +106,11 @@ class PublisherService:
|
||||
'error': str(e)
|
||||
})
|
||||
|
||||
overall_success = all(r.get('success', False) for r in results)
|
||||
logger.info(f"[PublisherService.publish_content] {'✅' if overall_success else '❌'} Overall publish result: success={overall_success}, {len(results)} destinations")
|
||||
|
||||
return {
|
||||
'success': all(r.get('success', False) for r in results),
|
||||
'success': overall_success,
|
||||
'results': results
|
||||
}
|
||||
|
||||
@@ -123,6 +132,7 @@ class PublisherService:
|
||||
dict: Publishing result
|
||||
"""
|
||||
# Create publishing record
|
||||
logger.info(f"[PublisherService._publish_to_destination] 📝 Creating publishing record for {destination}")
|
||||
record = PublishingRecord.objects.create(
|
||||
account=account,
|
||||
site=content.site,
|
||||
@@ -131,12 +141,16 @@ class PublisherService:
|
||||
destination=destination,
|
||||
status='pending'
|
||||
)
|
||||
logger.info(f"[PublisherService._publish_to_destination] ✅ Publishing record created: id={record.id}")
|
||||
|
||||
try:
|
||||
# Get adapter for destination
|
||||
logger.info(f"[PublisherService._publish_to_destination] 🔌 Getting adapter for {destination}")
|
||||
adapter = self._get_adapter(destination)
|
||||
if not adapter:
|
||||
logger.error(f"[PublisherService._publish_to_destination] ❌ No adapter found for destination: {destination}")
|
||||
raise ValueError(f"No adapter found for destination: {destination}")
|
||||
logger.info(f"[PublisherService._publish_to_destination] ✅ Adapter found: {adapter.__class__.__name__}")
|
||||
|
||||
# Get destination config
|
||||
destination_config = {}
|
||||
@@ -144,6 +158,7 @@ class PublisherService:
|
||||
# If content has site, try to get integration config
|
||||
if hasattr(content, 'site') and content.site:
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
logger.info(f"[PublisherService._publish_to_destination] 🔍 Looking for integration: site={content.site.name}, platform={destination}")
|
||||
integration = SiteIntegration.objects.filter(
|
||||
site=content.site,
|
||||
platform=destination,
|
||||
@@ -151,16 +166,23 @@ class PublisherService:
|
||||
).first()
|
||||
|
||||
if integration:
|
||||
logger.info(f"[PublisherService._publish_to_destination] ✅ Integration found: id={integration.id}")
|
||||
# Merge config_json and credentials_json
|
||||
destination_config.update(integration.config_json or {})
|
||||
destination_config.update(integration.get_credentials() or {})
|
||||
logger.info(f"[PublisherService._publish_to_destination] 🔑 Config merged: has_api_key={bool(destination_config.get('api_key'))}, has_site_url={bool(destination_config.get('site_url'))}")
|
||||
|
||||
# Ensure site_url is set (from config or from site model)
|
||||
if not destination_config.get('site_url'):
|
||||
destination_config['site_url'] = content.site.url
|
||||
logger.info(f"[PublisherService._publish_to_destination] 🌐 Using site.url: {content.site.url}")
|
||||
else:
|
||||
logger.warning(f"[PublisherService._publish_to_destination] ⚠️ No integration found for site={content.site.name}, platform={destination}")
|
||||
|
||||
# Publish via adapter
|
||||
logger.info(f"[PublisherService._publish_to_destination] 🚀 Calling adapter.publish() with config keys: {list(destination_config.keys())}")
|
||||
result = adapter.publish(content, destination_config)
|
||||
logger.info(f"[PublisherService._publish_to_destination] 📬 Adapter returned: success={result.get('success')}, external_id={result.get('external_id')}")
|
||||
|
||||
# Update record
|
||||
record.status = 'published' if result.get('success') else 'failed'
|
||||
|
||||
@@ -89,10 +89,15 @@ class PublisherViewSet(viewsets.ViewSet):
|
||||
"destinations": ["wordpress", "sites"] # Required: list of destinations
|
||||
}
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
content_id = request.data.get('content_id')
|
||||
site_blueprint_id = request.data.get('site_blueprint_id')
|
||||
destinations = request.data.get('destinations', [])
|
||||
|
||||
logger.info(f"[PublisherViewSet.publish] 🚀 Publish request received: content_id={content_id}, destinations={destinations}")
|
||||
|
||||
if not destinations:
|
||||
return error_response(
|
||||
'destinations is required',
|
||||
@@ -125,11 +130,13 @@ class PublisherViewSet(viewsets.ViewSet):
|
||||
|
||||
elif content_id:
|
||||
# Publish content
|
||||
logger.info(f"[PublisherViewSet.publish] 📝 Publishing content {content_id} to {destinations}")
|
||||
result = self.publisher_service.publish_content(
|
||||
content_id,
|
||||
destinations,
|
||||
account
|
||||
)
|
||||
logger.info(f"[PublisherViewSet.publish] {'✅' if result.get('success') else '❌'} Publish result: {result}")
|
||||
return success_response(result, request=request)
|
||||
|
||||
else:
|
||||
|
||||
@@ -31,19 +31,24 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int
|
||||
from igny8_core.business.content.models import Content
|
||||
from igny8_core.business.integration.models import SiteIntegration
|
||||
|
||||
logger.info(f"[publish_content_to_wordpress] 🎯 Celery task started: content_id={content_id}, site_integration_id={site_integration_id}")
|
||||
|
||||
# Get content and site integration
|
||||
try:
|
||||
content = Content.objects.get(id=content_id)
|
||||
logger.info(f"[publish_content_to_wordpress] 📄 Content loaded: title='{content.title}'")
|
||||
site_integration = SiteIntegration.objects.get(id=site_integration_id)
|
||||
logger.info(f"[publish_content_to_wordpress] 🔌 Integration loaded: platform={site_integration.platform}, site={site_integration.site.name}")
|
||||
except (Content.DoesNotExist, SiteIntegration.DoesNotExist) as e:
|
||||
logger.error(f"Content or site integration not found: {e}")
|
||||
logger.error(f"[publish_content_to_wordpress] ❌ Content or site integration not found: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
# Check if content is already published
|
||||
if content.external_id:
|
||||
logger.info(f"Content {content_id} already published to WordPress")
|
||||
logger.info(f"[publish_content_to_wordpress] ⚠️ Content {content_id} already published: external_id={content.external_id}")
|
||||
return {"success": True, "message": "Already published", "external_id": content.external_id}
|
||||
|
||||
logger.info(f"[publish_content_to_wordpress] 📦 Preparing content payload...")
|
||||
# Prepare content data for WordPress
|
||||
# Generate excerpt from content_html (Content model has no 'brief' field)
|
||||
excerpt = ''
|
||||
@@ -88,16 +93,20 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int
|
||||
'X-IGNY8-API-KEY': site_integration.api_key,
|
||||
}
|
||||
|
||||
logger.info(f"[publish_content_to_wordpress] 🚀 POSTing to WordPress: {wordpress_url}")
|
||||
response = requests.post(
|
||||
wordpress_url,
|
||||
json=content_data,
|
||||
headers=headers,
|
||||
timeout=30
|
||||
)
|
||||
logger.info(f"[publish_content_to_wordpress] 📬 WordPress response: status={response.status_code}")
|
||||
|
||||
if response.status_code == 201:
|
||||
# Success
|
||||
wp_data = response.json().get('data', {})
|
||||
logger.info(f"[publish_content_to_wordpress] ✅ WordPress post created successfully: post_id={wp_data.get('post_id')}")
|
||||
|
||||
# Update external_id and external_url for unified Content model
|
||||
content.external_id = wp_data.get('post_id')
|
||||
content.external_url = wp_data.get('post_url')
|
||||
@@ -105,8 +114,9 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int
|
||||
content.save(update_fields=[
|
||||
'external_id', 'external_url', 'status', 'updated_at'
|
||||
])
|
||||
logger.info(f"[publish_content_to_wordpress] 💾 Content model updated: external_id={content.external_id}, status=published")
|
||||
|
||||
logger.info(f"Successfully published content {content_id} to WordPress post {content.external_id}")
|
||||
logger.info(f"[publish_content_to_wordpress] 🎉 Successfully published content {content_id} to WordPress post {content.external_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"external_id": content.external_id,
|
||||
@@ -129,19 +139,21 @@ def publish_content_to_wordpress(self, content_id: int, site_integration_id: int
|
||||
else:
|
||||
# Error
|
||||
error_msg = f"WordPress API error: {response.status_code} - {response.text}"
|
||||
logger.error(error_msg)
|
||||
logger.error(f"[publish_content_to_wordpress] ❌ {error_msg}")
|
||||
|
||||
# Retry logic
|
||||
if self.request.retries < self.max_retries:
|
||||
# Exponential backoff: 1min, 5min, 15min
|
||||
countdown = 60 * (5 ** self.request.retries)
|
||||
logger.warning(f"[publish_content_to_wordpress] 🔄 Retrying (attempt {self.request.retries + 1}/{self.max_retries}) in {countdown}s")
|
||||
raise self.retry(countdown=countdown, exc=Exception(error_msg))
|
||||
else:
|
||||
# Max retries reached - mark as failed
|
||||
logger.error(f"[publish_content_to_wordpress] ❌ Max retries reached, giving up")
|
||||
return {"success": False, "error": error_msg}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error publishing content {content_id}: {str(e)}", exc_info=True)
|
||||
logger.error(f"[publish_content_to_wordpress] ❌ Exception during publish: {str(e)}", exc_info=True)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
|
||||
@@ -145,7 +145,14 @@ export default function Review() {
|
||||
|
||||
// Publish to WordPress - single item
|
||||
const handlePublishSingle = useCallback(async (row: Content) => {
|
||||
console.log('[Review.handlePublishSingle] Starting publish for content:', {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('[Review.handlePublishSingle] Calling API endpoint /v1/publisher/publish/');
|
||||
const response = await fetchAPI('/v1/publisher/publish/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
@@ -154,14 +161,31 @@ export default function Review() {
|
||||
})
|
||||
});
|
||||
|
||||
console.log('[Review.handlePublishSingle] API response received:', response);
|
||||
|
||||
if (response.success) {
|
||||
console.log('[Review.handlePublishSingle] ✅ Publish successful:', {
|
||||
content_id: row.id,
|
||||
response: response
|
||||
});
|
||||
toast.success(`Successfully published "${row.title}" to WordPress`);
|
||||
loadContent(); // Reload to reflect changes
|
||||
} else {
|
||||
console.error('[Review.handlePublishSingle] ❌ Publish failed:', {
|
||||
content_id: row.id,
|
||||
error: response.error,
|
||||
message: response.message,
|
||||
response: response
|
||||
});
|
||||
toast.error(`Failed to publish: ${response.error || response.message}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('WordPress publish error:', error);
|
||||
console.error('[Review.handlePublishSingle] ❌ Exception during publish:', {
|
||||
content_id: row.id,
|
||||
error: error,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
toast.error(`Failed to publish to WordPress: ${error.message || 'Network error'}`);
|
||||
}
|
||||
}, [loadContent, toast]);
|
||||
|
||||
Reference in New Issue
Block a user