diff --git a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py index 5d2ed18d..affc40be 100644 --- a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py +++ b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py @@ -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, diff --git a/backend/igny8_core/business/publishing/services/publisher_service.py b/backend/igny8_core/business/publishing/services/publisher_service.py index 2bfbf698..88deec5a 100644 --- a/backend/igny8_core/business/publishing/services/publisher_service.py +++ b/backend/igny8_core/business/publishing/services/publisher_service.py @@ -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' diff --git a/backend/igny8_core/modules/publisher/views.py b/backend/igny8_core/modules/publisher/views.py index d2e57946..48438d38 100644 --- a/backend/igny8_core/modules/publisher/views.py +++ b/backend/igny8_core/modules/publisher/views.py @@ -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: diff --git a/backend/igny8_core/tasks/wordpress_publishing.py b/backend/igny8_core/tasks/wordpress_publishing.py index 2df3d2ae..1074a0bb 100644 --- a/backend/igny8_core/tasks/wordpress_publishing.py +++ b/backend/igny8_core/tasks/wordpress_publishing.py @@ -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)} diff --git a/frontend/src/pages/Writer/Review.tsx b/frontend/src/pages/Writer/Review.tsx index 6ea70ec7..bbe5f8e2 100644 --- a/frontend/src/pages/Writer/Review.tsx +++ b/frontend/src/pages/Writer/Review.tsx @@ -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]);