# WordPress Integration Complete Refactor Plan
**Date:** December 1, 2025  
**Status:** 🔴 Critical Issues Identified - Requires Complete Fix  
**Author:** GitHub Copilot

---

## Executive Summary

The WordPress integration is **partially working** but has critical data flow and synchronization issues:

### ✅ What's Working
- ✅ Backend sends HTTP requests successfully to WordPress
- ✅ WordPress creates posts without errors
- ✅ Authentication (API key) works correctly
- ✅ Content title and HTML are being sent

### ❌ What's Broken
1. **Categories/Tags/Images Not Publishing** - WordPress logs show "No categories/tags/images in content_data" despite IGNY8 sending them
2. **Status Sync Broken** - Content stays in "review" after WordPress publish, should change to "published"
3. **Sync Button Republishes Everything** - Should only update counts, not republish all content
4. **Field Name Mismatches** - IGNY8 sends different field names than WordPress plugin expects

---

## Issue Analysis & Root Causes

### Issue 1: Categories/Tags/Images Not Appearing in WordPress

**Current Behavior:**
- IGNY8 Backend logs: `Categories: 1`, `Tags: 4`, `Featured image: Yes`
- WordPress Plugin logs: `⚠️ No categories in content_data`, `⚠️ No tags in content_data`, `⚠️ No featured image in content_data`
- Result: 28 posts created, all "Uncategorized", no tags, no images

**Root Cause:** Field name mismatch between IGNY8 payload and WordPress plugin expectations

**IGNY8 Backend Sends:**
```python
# File: backend/igny8_core/tasks/wordpress_publishing.py
content_data = {
    'categories': categories,          # Array of category names
    'tags': tags,                      # Array of tag names
    'featured_image_url': featured_image_url,  # Image URL
    'gallery_images': gallery_images   # Array of image URLs
}
```

**WordPress Plugin Expects (OLD CODE):**
```php
// File: igny8-wp-plugin/includes/class-igny8-rest-api.php line 509-515
error_log('Categories: ' . (isset($content_data['categories']) ? json_encode($content_data['categories']) : 'MISSING'));
error_log('Tags: ' . (isset($content_data['tags']) ? json_encode($content_data['tags']) : 'MISSING'));

// File: igny8-wp-plugin/sync/igny8-to-wp.php line 200-225
if (!empty($content_data['categories'])) {
    // Process categories
}
if (!empty($content_data['tags'])) {
    // Process tags
}
```

**Analysis:** WordPress plugin DOES expect the correct field names! The issue is that the data is being sent correctly, but the WordPress plugin logs show "MISSING" - this suggests the data is being lost somewhere in the request pipeline.

**Hypothesis:** The issue is likely in how the WordPress REST endpoint receives the JSON data. Let me check the actual endpoint implementation.

### Issue 2: Status Not Syncing from WordPress → IGNY8

**Current Behavior:**
- User publishes content from IGNY8 Writer page
- Content status in IGNY8: `review`
- WordPress creates post with status: `publish`
- Expected: IGNY8 status should change to `published`
- Actual: IGNY8 status stays `review`

**Root Cause:** WordPress plugin sends webhook but IGNY8 only updates status if WordPress status changes from draft → publish

**Current Webhook Flow:**
```
WordPress Plugin (after post creation)
  ↓
Sends webhook: POST /api/v1/integration/webhooks/wordpress/status/
  ↓
IGNY8 Backend: webhooks.py line 156-161
  - Only updates status if: post_status == 'publish' AND old_status != 'published'
  - Problem: Content comes from IGNY8 with status='review', should update to 'published'
```

**Fix Required:** Update webhook logic to properly sync status regardless of previous state

### Issue 3: Sync Button Republishes Everything

**Current Behavior:**
- User clicks "Sync Now" in Site Settings → Content Types tab
- Frontend calls: `integrationApi.syncIntegration(integration_id, 'incremental')`
- Backend endpoint: `POST /v1/integration/integrations/{id}/sync/`
- Actual result: Republishes ALL published content to WordPress (100 items limit)

**Expected Behavior:**
- Should only fetch WordPress site structure (post counts, taxonomy counts)
- Should NOT republish content
- Should update the Content Types table with fresh counts

**Root Cause:** Sync service syncs content instead of just fetching metadata

**Current Sync Implementation:**
```python
# File: backend/igny8_core/business/integration/services/content_sync_service.py line 90-155
def _sync_to_wordpress(self, integration, content_types):
    # Gets all Content.objects with status='publish'
    # Publishes up to 100 items via WordPressAdapter
    # This is WRONG for "Sync Now" button - should only fetch metadata
```

**Fix Required:** Separate "sync metadata" from "publish content" operations

---

## Complete Data Flow Analysis

### Current Flow: IGNY8 → WordPress (Publishing)

```
┌──────────────────────────────────────────────────────────────────────────┐
│ 1. User Action: Click "Publish to WordPress" on Writer Review page      │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 2. Frontend: WordPressPublish.tsx                                        │
│    - Calls: POST /v1/publisher/publish/                                  │
│    - Body: { content_id: 123, destinations: ['wordpress'] }              │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 3. Backend: PublisherViewSet.publish() (publisher/views.py)              │
│    - Validates content exists                                            │
│    - Calls: PublisherService.publish_content()                           │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 4. Backend: PublisherService.publish_content()                           │
│    - For each destination (wordpress):                                   │
│      - Get SiteIntegration config                                        │
│      - Call: WordPressAdapter.publish()                                  │
│    - Create PublishingRecord                                             │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 5. Backend: WordPressAdapter.publish() (adapters/wordpress_adapter.py)   │
│    - Checks auth method (API key vs username/password)                   │
│    - Calls: _publish_via_api_key()                                       │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 6. Backend: Celery Task - publish_content_to_wordpress()                 │
│    - Extract categories from taxonomy_terms (taxonomy_type='category')   │
│    - Extract tags from taxonomy_terms (taxonomy_type='tag') + keywords   │
│    - Extract images from Images model                                    │
│    - Convert image paths: /data/app/.../public/images/... →              │
│                            https://app.igny8.com/images/...               │
│    - Build payload with categories[], tags[], featured_image_url, etc.   │
│    - Send: POST {site_url}/wp-json/igny8/v1/publish                      │
│    - Header: X-IGNY8-API-Key: {api_key}                                  │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 7. WordPress: REST Endpoint /igny8/v1/publish (class-igny8-rest-api.php) │
│    - Receives JSON payload                                               │
│    - Validates API key                                                   │
│    ⚠️  ISSUE: Logs show "No categories/tags/images in content_data"     │
│    - Calls: igny8_create_wordpress_post_from_task($content_data)         │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 8. WordPress: igny8_create_wordpress_post_from_task() (igny8-to-wp.php)  │
│    - Creates WordPress post with wp_insert_post()                        │
│    - Processes categories: igny8_process_categories()                    │
│    - Processes tags: igny8_process_tags()                                │
│    - Sets featured image: igny8_set_featured_image()                     │
│    ⚠️  ISSUE: Empty arrays passed because data missing                  │
│    - Returns: WordPress post ID                                          │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 9. WordPress: Should send webhook back to IGNY8                          │
│    - Endpoint: POST /api/v1/integration/webhooks/wordpress/status/       │
│    - Body: { content_id, post_id, post_status: 'publish', ... }          │
│    ⚠️  ISSUE: Webhook logic only updates if old_status != 'published'   │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 10. Backend: Webhook updates Content model                               │
│     - Sets external_id = WordPress post_id                               │
│     - Sets external_url = WordPress post URL                             │
│     ❌ ISSUE: Status stays 'review' instead of changing to 'published'  │
└──────────────────────────────────────────────────────────────────────────┘
```

### Current Flow: "Sync Now" Button

```
┌──────────────────────────────────────────────────────────────────────────┐
│ 1. User Action: Click "Sync Now" in Site Settings → Content Types       │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 2. Frontend: Settings.tsx line 398                                       │
│    - Calls: integrationApi.syncIntegration(integration_id, 'incremental')│
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 3. Frontend: integration.api.ts line 92                                  │
│    - Sends: POST /v1/integration/integrations/{id}/sync/                 │
│    - Body: { sync_type: 'incremental' }                                  │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 4. Backend: IntegrationViewSet.sync() (integration/views.py line 218)    │
│    - Calls: SyncService.sync(integration, direction='both')              │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 5. Backend: SyncService.sync() (services/sync_service.py)                │
│    - Calls: _sync_to_external() + _sync_from_external()                  │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 6. Backend: ContentSyncService._sync_to_wordpress()                      │
│    ❌ ISSUE: Queries ALL Content.objects with status='publish'          │
│    ❌ ISSUE: Publishes up to 100 content items to WordPress             │
│    ❌ This should NOT happen - should only fetch metadata!              │
└─────────────────────────────────┬────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼────────────────────────────────────────┐
│ 7. Expected Behavior:                                                    │
│    ✅ Should only call: /wp-json/igny8/v1/site-metadata/                │
│    ✅ Should fetch: post_types counts, taxonomies counts                │
│    ✅ Should update: ContentTypes table in Settings UI                  │
│    ✅ Should NOT republish any content                                  │
└──────────────────────────────────────────────────────────────────────────┘
```

---

## Detailed Issue Breakdown

### Issue #1: Categories/Tags/Images Missing

**Evidence from Logs:**

**IGNY8 Backend (wordpress_publishing.py):**
```
[publish_content_to_wordpress] Categories: 1
[publish_content_to_wordpress]   - Outdoor Living Design
[publish_content_to_wordpress] Tags: 4
[publish_content_to_wordpress]   - outdoor lighting ideas
[publish_content_to_wordpress]   - outdoor patio design
[publish_content_to_wordpress] Featured image: Yes
[publish_content_to_wordpress] Gallery images: 2
```

**WordPress Plugin (error_log):**
```
Categories: MISSING
Tags: MISSING
Featured Image: MISSING
⚠️ No categories in content_data
⚠️ No tags in content_data
⚠️ No featured image in content_data
```

**Diagnostic Steps:**

1. **Check IGNY8 payload before sending:**
```python
# File: backend/igny8_core/tasks/wordpress_publishing.py line 220-246
content_data = {
    'categories': categories,  # ✅ Correctly populated
    'tags': tags,              # ✅ Correctly populated
    'featured_image_url': featured_image_url,  # ✅ Correctly populated
}
```

2. **Check WordPress endpoint receives data:**
```php
// File: igny8-wp-plugin/includes/class-igny8-rest-api.php line 497-515
$content_data = $request->get_json_params();
error_log('Categories: ' . (isset($content_data['categories']) ? json_encode($content_data['categories']) : 'MISSING'));
// Shows: MISSING
```

**Hypothesis:** The issue is in how WordPress receives the JSON payload. Possible causes:
- Content-Type header not set correctly
- JSON parsing fails in WordPress
- Field names are being transformed during transmission
- WordPress REST API strips certain fields

**Testing Required:**
1. Add raw request body logging in WordPress plugin
2. Verify Content-Type header is `application/json`
3. Check if WordPress REST API filters are interfering

### Issue #2: Status Sync Logic Incorrect

**Current Webhook Code:**
```python
# File: backend/igny8_core/modules/integration/webhooks.py line 147-161
old_status = content.status  # 'review'
post_status = 'publish'  # From WordPress

# Map WordPress status to IGNY8 status
status_map = {
    'publish': 'published',
    'draft': 'draft',
    'pending': 'review',
}
igny8_status = status_map.get(post_status, 'review')  # 'published'

# ❌ ISSUE: Only updates if WordPress status changed from draft to publish
if post_status == 'publish' and old_status != 'published':
    content.status = 'published'  # ✅ This SHOULD run
else:
    # ❌ Status stays 'review'
```

**Expected Logic:**
```python
# Should always update to mapped status
content.status = igny8_status  # 'published'
```

### Issue #3: Sync Republishes Content

**Current Sync Service:**
```python
# File: backend/igny8_core/business/integration/services/content_sync_service.py line 90-155
def _sync_to_wordpress(self, integration, content_types):
    # ❌ Gets all published content
    content_query = Content.objects.filter(
        account=integration.account,
        site=integration.site,
        source='igny8',
        status='publish'
    )
    
    # ❌ Publishes each one
    for content in content_query[:100]:
        result = adapter.publish(content, destination_config)
```

**Expected Behavior:**
```python
def sync_wordpress_metadata(self, integration):
    """Fetch WordPress site structure (counts only)"""
    # ✅ Should call: /wp-json/igny8/v1/site-metadata/
    # ✅ Should return: post_types, taxonomies, counts
    # ❌ Should NOT publish any content
```

---

## Complete Fix Plan

### Fix #1: Solve Categories/Tags/Images Issue

**Step 1.1: Add Debug Logging to WordPress Plugin**

File: `igny8-wp-plugin/includes/class-igny8-rest-api.php`

```php
public function publish_content_to_wordpress($request) {
    // ADD: Log raw request body
    $raw_body = $request->get_body();
    error_log('========== RAW REQUEST BODY ==========');
    error_log($raw_body);
    error_log('======================================');
    
    // Existing code
    $content_data = $request->get_json_params();
    
    // ADD: Log parsed JSON
    error_log('========== PARSED JSON ==========');
    error_log(print_r($content_data, true));
    error_log('=================================');
    
    // Rest of the function...
}
```

**Step 1.2: Verify IGNY8 Sends Correct Headers**

File: `backend/igny8_core/tasks/wordpress_publishing.py`

```python
headers = {
    'X-IGNY8-API-Key': api_key,
    'Content-Type': 'application/json',  # ✅ Verify this is set
}

# ADD: Log full request details
publish_logger.info(f"Request URL: {wordpress_url}")
publish_logger.info(f"Request Headers: {headers}")
publish_logger.info(f"Request Body: {json.dumps(content_data, indent=2)}")
```

**Step 1.3: Fix WordPress REST Endpoint (if needed)**

If WordPress REST API filters are stripping fields, add this to plugin:

```php
add_filter('rest_request_before_callbacks', function($response, $handler, $request) {
    // Allow all fields for IGNY8 endpoints
    if (strpos($request->get_route(), '/igny8/v1/') === 0) {
        return $response;  // Don't filter IGNY8 requests
    }
    return $response;
}, 10, 3);
```

### Fix #2: Correct Status Sync Logic

File: `backend/igny8_core/modules/integration/webhooks.py`

**Current Code (lines 147-161):**
```python
# 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

# ❌ WRONG: Only updates if WordPress status changed from draft to publish
if post_status == 'publish' and old_status != 'published':
    content.status = 'published'
```

**Fixed Code:**
```python
# 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

# ✅ FIXED: Always update to mapped status when WordPress publishes
# Only skip update if already in the target status
if content.status != igny8_status:
    content.status = igny8_status
    logger.info(f"[wordpress_status_webhook] Status updated: {old_status} → {content.status}")
```

### Fix #3: Separate Sync Metadata from Publish Content

**Step 3.1: Create New Sync Metadata Service**

File: `backend/igny8_core/business/integration/services/sync_metadata_service.py` (NEW)

```python
"""
Sync Metadata Service
Fetches WordPress site structure (post types, taxonomies, counts) without publishing content
"""
import logging
import requests
from typing import Dict, Any

from igny8_core.business.integration.models import SiteIntegration

logger = logging.getLogger(__name__)


class SyncMetadataService:
    """
    Service for syncing WordPress site metadata (counts only, no content publishing)
    """
    
    def sync_wordpress_structure(
        self,
        integration: SiteIntegration
    ) -> Dict[str, Any]:
        """
        Fetch WordPress site structure (post types, taxonomies, counts).
        Does NOT publish or sync any content.
        
        Args:
            integration: SiteIntegration instance
        
        Returns:
            dict: {
                'success': True/False,
                'post_types': {...},
                'taxonomies': {...},
                'message': '...'
            }
        """
        try:
            # Get WordPress site URL and API key
            site_url = integration.config_json.get('site_url', '')
            credentials = integration.get_credentials()
            api_key = credentials.get('api_key', '')
            
            if not site_url or not api_key:
                return {
                    'success': False,
                    'error': 'Missing site_url or api_key in integration config'
                }
            
            # Call WordPress metadata endpoint
            metadata_url = f"{site_url}/wp-json/igny8/v1/site-metadata/"
            headers = {
                'X-IGNY8-API-Key': api_key,
                'Content-Type': 'application/json',
            }
            
            logger.info(f"[SyncMetadataService] Fetching metadata from: {metadata_url}")
            
            response = requests.get(
                metadata_url,
                headers=headers,
                timeout=30
            )
            
            if response.status_code != 200:
                logger.error(f"[SyncMetadataService] WordPress returned {response.status_code}: {response.text}")
                return {
                    'success': False,
                    'error': f'WordPress API error: {response.status_code}',
                    'details': response.text
                }
            
            # Parse response
            data = response.json()
            
            if not data.get('success'):
                return {
                    'success': False,
                    'error': data.get('error', 'Unknown error'),
                    'message': data.get('message', '')
                }
            
            metadata = data.get('data', {})
            
            logger.info(f"[SyncMetadataService] Successfully fetched metadata:")
            logger.info(f"  - Post types: {len(metadata.get('post_types', {}))}")
            logger.info(f"  - Taxonomies: {len(metadata.get('taxonomies', {}))}")
            
            return {
                'success': True,
                'post_types': metadata.get('post_types', {}),
                'taxonomies': metadata.get('taxonomies', {}),
                'message': 'WordPress site structure synced successfully'
            }
            
        except requests.exceptions.Timeout:
            logger.error(f"[SyncMetadataService] Timeout connecting to WordPress")
            return {
                'success': False,
                'error': 'Timeout connecting to WordPress'
            }
        except Exception as e:
            logger.error(f"[SyncMetadataService] Error syncing metadata: {str(e)}", exc_info=True)
            return {
                'success': False,
                'error': str(e)
            }
```

**Step 3.2: Update IntegrationViewSet to Use New Service**

File: `backend/igny8_core/modules/integration/views.py`

```python
@action(detail=True, methods=['post'])
def sync(self, request, pk=None):
    """
    Trigger synchronization with integrated platform.
    
    POST /api/v1/integration/integrations/{id}/sync/
    
    Request body:
    {
        "direction": "metadata",  # 'metadata', 'to_external', 'from_external', 'both'
        "content_types": ["blog_post", "page"]  # Optional
    }
    """
    integration = self.get_object()
    
    direction = request.data.get('direction', 'metadata')
    content_types = request.data.get('content_types')
    
    # NEW: Handle metadata sync separately
    if direction == 'metadata':
        from igny8_core.business.integration.services.sync_metadata_service import SyncMetadataService
        metadata_service = SyncMetadataService()
        result = metadata_service.sync_wordpress_structure(integration)
    else:
        # Existing content sync logic
        sync_service = SyncService()
        result = sync_service.sync(integration, direction=direction, content_types=content_types)
    
    response_status = status.HTTP_200_OK if result.get('success') else status.HTTP_400_BAD_REQUEST
    return success_response(result, request=request, status_code=response_status)
```

**Step 3.3: Update Frontend to Request Metadata Sync**

File: `frontend/src/pages/Sites/Settings.tsx`

```typescript
const handleManualSync = async () => {
  setSyncLoading(true);
  try {
    if (wordPressIntegration && wordPressIntegration.id) {
      // CHANGED: Request metadata sync only (not content publishing)
      const res = await integrationApi.syncIntegration(
        wordPressIntegration.id,
        'metadata'  // ✅ NEW: Sync metadata only, don't republish content
      );
      
      if (res && res.success) {
        toast.success('WordPress structure synced successfully');
        setTimeout(() => loadContentTypes(), 1500);
      } else {
        toast.error(res?.message || 'Sync failed');
      }
    } else {
      toast.error('No integration configured');
    }
  } catch (err: any) {
    toast.error(`Sync failed: ${err?.message || String(err)}`);
  } finally {
    setSyncLoading(false);
  }
};
```

File: `frontend/src/services/integration.api.ts`

```typescript
async syncIntegration(
  integrationId: number,
  syncType: 'metadata' | 'incremental' | 'full' = 'metadata'  // ✅ Changed default
): Promise<{ success: boolean; message: string; synced_count?: number }> {
  return await fetchAPI(`/v1/integration/integrations/${integrationId}/sync/`, {
    method: 'POST',
    body: JSON.stringify({ 
      direction: syncType === 'metadata' ? 'metadata' : 'both',  // ✅ NEW
      sync_type: syncType  // Keep for backward compatibility
    }),
  });
}
```

---

## Implementation Checklist

### Phase 1: Diagnostic & Discovery (1-2 hours)

- [ ] **Task 1.1:** Add raw request body logging to WordPress plugin
  - File: `igny8-wp-plugin/includes/class-igny8-rest-api.php`
  - Add logging before and after `$request->get_json_params()`
  
- [ ] **Task 1.2:** Verify IGNY8 sends correct headers
  - File: `backend/igny8_core/tasks/wordpress_publishing.py`
  - Log complete request details (URL, headers, body)
  
- [ ] **Task 1.3:** Test publishing and capture full logs
  - IGNY8 logs: `docker-compose -f docker-compose.app.yml logs -f backend`
  - WordPress logs: Check `/wp-content/debug.log` or plugin logs
  
- [ ] **Task 1.4:** Identify exact issue
  - Compare sent payload with received payload
  - Identify which fields are lost/transformed

### Phase 2: Fix Categories/Tags/Images (2-3 hours)

- [ ] **Task 2.1:** Fix WordPress REST endpoint if needed
  - Add REST API filter bypass for IGNY8 endpoints
  - Test with raw cURL request to verify fields are received
  
- [ ] **Task 2.2:** Verify field processing functions
  - Test `igny8_process_categories()` with sample data
  - Test `igny8_process_tags()` with sample data
  - Test `igny8_set_featured_image()` with sample URL
  
- [ ] **Task 2.3:** End-to-end test
  - Publish content from IGNY8
  - Verify categories appear in WordPress admin
  - Verify tags appear in WordPress admin
  - Verify featured image appears

### Phase 3: Fix Status Sync (1 hour)

- [ ] **Task 3.1:** Update webhook status logic
  - File: `backend/igny8_core/modules/integration/webhooks.py`
  - Change condition from `if post_status == 'publish' and old_status != 'published'`
  - To: `if content.status != igny8_status`
  
- [ ] **Task 3.2:** Test status sync
  - Content with status='review' in IGNY8
  - Publish to WordPress (status='publish')
  - Verify IGNY8 status changes to 'published'
  
- [ ] **Task 3.3:** Verify webhook is being sent
  - Check WordPress plugin sends webhook after post creation
  - File: `igny8-wp-plugin/sync/igny8-to-wp.php` line 300-350
  - Ensure `igny8_send_status_webhook()` is called

### Phase 4: Fix Sync Button (2-3 hours)

- [ ] **Task 4.1:** Create SyncMetadataService
  - File: `backend/igny8_core/business/integration/services/sync_metadata_service.py`
  - Implement `sync_wordpress_structure()` method
  
- [ ] **Task 4.2:** Update IntegrationViewSet
  - File: `backend/igny8_core/modules/integration/views.py`
  - Add `direction='metadata'` handling
  
- [ ] **Task 4.3:** Update frontend
  - File: `frontend/src/pages/Sites/Settings.tsx`
  - Change sync call to use 'metadata' direction
  - File: `frontend/src/services/integration.api.ts`
  - Update `syncIntegration()` signature
  
- [ ] **Task 4.4:** Test sync button
  - Click "Sync Now" in Settings
  - Verify it only fetches counts
  - Verify it does NOT republish content
  - Verify Content Types table updates

### Phase 5: Comprehensive Testing (2 hours)

- [ ] **Task 5.1:** Test complete publishing flow
  - Create new content in Writer
  - Add categories, tags, images
  - Publish to WordPress
  - Verify all data appears correctly
  
- [ ] **Task 5.2:** Test status sync
  - Content starts in 'review'
  - Publishes to WordPress
  - Verify status changes to 'published'
  
- [ ] **Task 5.3:** Test sync button
  - Configure new WordPress integration
  - Click "Sync Now"
  - Verify counts update
  - Verify no content is republished
  
- [ ] **Task 5.4:** Test error handling
  - Invalid API key
  - WordPress site offline
  - Missing required fields

---

## Expected Results After Fix

### 1. Publishing Content to WordPress

**Before Fix:**
```
✅ Title appears
✅ Content HTML appears
❌ Categories: "Uncategorized"
❌ Tags: None
❌ Featured Image: None
❌ Status in IGNY8: stays 'review'
```

**After Fix:**
```
✅ Title appears
✅ Content HTML appears
✅ Categories: "Outdoor Living Design" (and any others)
✅ Tags: "outdoor lighting ideas", "outdoor patio design", etc.
✅ Featured Image: Displays correctly
✅ Gallery Images: Added to post
✅ SEO Title: Set in Yoast/SEOPress/AIOSEO
✅ SEO Description: Set in Yoast/SEOPress/AIOSEO
✅ Status in IGNY8: changes to 'published'
```

### 2. Sync Now Button

**Before Fix:**
```
❌ Republishes all published content (up to 100 items)
❌ Creates duplicate posts in WordPress
❌ Takes 30+ seconds to complete
✅ Updates Content Types table
```

**After Fix:**
```
✅ Only fetches WordPress site structure
✅ Updates post type counts
✅ Updates taxonomy counts
✅ Does NOT republish any content
✅ Completes in < 5 seconds
✅ Updates Content Types table
```

### 3. Status Sync

**Before Fix:**
```
IGNY8 Content Status: 'review'
  ↓
Publish to WordPress
  ↓
WordPress Post Status: 'publish'
  ↓
Webhook sent to IGNY8
  ↓
❌ IGNY8 Content Status: 'review' (unchanged)
```

**After Fix:**
```
IGNY8 Content Status: 'review'
  ↓
Publish to WordPress
  ↓
WordPress Post Status: 'publish'
  ↓
Webhook sent to IGNY8
  ↓
✅ IGNY8 Content Status: 'published' (updated correctly)
```

---

## Complete Flow Diagrams

### After Fix: Publishing Flow

```
┌───────────────────────────────────────────────────────────────────────────┐
│ STEP 1: User Action                                                       │
│ ────────────────────────────────────────────────────────────────────────  │
│ User clicks "Publish to WordPress" on Writer Review page                  │
│ Content ID: 123                                                           │
│ Content Status: 'review'                                                  │
│ Categories: ['Outdoor Living Design']                                     │
│ Tags: ['outdoor lighting ideas', 'outdoor patio design', ...]            │
│ Images: 1 featured, 2 gallery                                             │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 2: Frontend Request                                                  │
│ ────────────────────────────────────────────────────────────────────────  │
│ POST /v1/publisher/publish/                                               │
│ Body: {                                                                   │
│   content_id: 123,                                                        │
│   destinations: ['wordpress']                                             │
│ }                                                                         │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 3: Backend Processing                                                │
│ ────────────────────────────────────────────────────────────────────────  │
│ PublisherViewSet → PublisherService → WordPressAdapter                    │
│ ✅ Queues Celery task: publish_content_to_wordpress.delay(123, 456)      │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 4: Celery Task Execution                                             │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ Extract categories from taxonomy_terms (taxonomy_type='category')     │
│    Result: ['Outdoor Living Design']                                     │
│                                                                           │
│ ✅ Extract tags from taxonomy_terms + keywords                           │
│    Result: ['outdoor lighting ideas', 'outdoor patio design', ...]       │
│                                                                           │
│ ✅ Extract images from Images model                                      │
│    Featured: /data/app/.../featured.jpg                                  │
│    Gallery: [/data/app/.../gallery1.jpg, /data/app/.../gallery2.jpg]     │
│                                                                           │
│ ✅ Convert image URLs                                                     │
│    Featured: https://app.igny8.com/images/featured.jpg                   │
│    Gallery: [https://app.igny8.com/images/gallery1.jpg, ...]             │
│                                                                           │
│ ✅ Build payload:                                                         │
│    {                                                                      │
│      content_id: 123,                                                     │
│      title: "10 Outdoor Living Design Ideas",                            │
│      content_html: "<p>Full content...</p>",                              │
│      categories: ["Outdoor Living Design"],                               │
│      tags: ["outdoor lighting ideas", ...],                               │
│      featured_image_url: "https://app.igny8.com/images/featured.jpg",    │
│      gallery_images: ["https://app.igny8.com/images/gallery1.jpg", ...], │
│      seo_title: "10 Best Outdoor Living Design Ideas 2025",              │
│      seo_description: "Discover the top outdoor living...",              │
│      primary_keyword: "outdoor living design",                            │
│      secondary_keywords: ["outdoor patio design", ...]                    │
│    }                                                                      │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 5: Send to WordPress                                                 │
│ ────────────────────────────────────────────────────────────────────────  │
│ POST https://homeg8.com/wp-json/igny8/v1/publish                          │
│ Headers:                                                                  │
│   X-IGNY8-API-Key: ***abc123                                              │
│   Content-Type: application/json                                          │
│ Body: {payload from above}                                                │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 6: WordPress Receives Request                                        │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ Validates API key                                                      │
│ ✅ Parses JSON payload                                                    │
│ ✅ Logs received data (FIXED - all fields present)                       │
│    Categories: ["Outdoor Living Design"]                                  │
│    Tags: ["outdoor lighting ideas", ...]                                  │
│    Featured Image: "https://app.igny8.com/images/featured.jpg"           │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 7: WordPress Creates Post                                            │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ wp_insert_post() creates post                                          │
│    Post ID: 789                                                           │
│    Post Status: 'publish'                                                 │
│                                                                           │
│ ✅ igny8_process_categories() processes categories                        │
│    - Finds "Outdoor Living Design" (or creates if missing)               │
│    - Returns category ID: [12]                                            │
│    - Assigns to post with wp_set_post_terms()                             │
│                                                                           │
│ ✅ igny8_process_tags() processes tags                                    │
│    - Finds/creates each tag                                               │
│    - Returns tag IDs: [45, 67, 89, 91]                                    │
│    - Assigns to post with wp_set_post_terms()                             │
│                                                                           │
│ ✅ igny8_set_featured_image() downloads and sets image                    │
│    - Downloads from https://app.igny8.com/images/featured.jpg            │
│    - Creates attachment in WordPress media library                        │
│    - Sets as featured image with set_post_thumbnail()                     │
│                                                                           │
│ ✅ igny8_set_gallery_images() processes gallery                           │
│    - Downloads each gallery image                                         │
│    - Creates attachments                                                  │
│    - Adds to post gallery meta                                            │
│                                                                           │
│ ✅ SEO metadata set in Yoast/SEOPress/AIOSEO                             │
│    - _yoast_wpseo_title: "10 Best Outdoor Living Design Ideas 2025"      │
│    - _yoast_wpseo_metadesc: "Discover the top outdoor living..."         │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 8: WordPress Sends Webhook                                           │
│ ────────────────────────────────────────────────────────────────────────  │
│ POST https://api.igny8.com/api/v1/integration/webhooks/wordpress/status/  │
│ Headers:                                                                  │
│   X-IGNY8-API-Key: ***abc123                                              │
│ Body: {                                                                   │
│   content_id: 123,                                                        │
│   post_id: 789,                                                           │
│   post_status: "publish",                                                 │
│   post_url: "https://homeg8.com/outdoor-living-design-ideas/",           │
│   post_title: "10 Outdoor Living Design Ideas",                          │
│   site_url: "https://homeg8.com"                                          │
│ }                                                                         │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 9: IGNY8 Webhook Handler (FIXED)                                     │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ Validates API key                                                      │
│ ✅ Finds Content by content_id: 123                                       │
│ ✅ Maps WordPress status to IGNY8 status                                  │
│    'publish' → 'published'                                                │
│                                                                           │
│ ✅ FIXED: Updates status regardless of previous value                     │
│    OLD CODE: if post_status == 'publish' and old_status != 'published'   │
│    NEW CODE: if content.status != igny8_status                            │
│                                                                           │
│ ✅ Updates Content model:                                                 │
│    - status: 'review' → 'published' ✅                                    │
│    - external_id: 789                                                     │
│    - external_url: "https://homeg8.com/outdoor-living-design-ideas/"     │
│    - metadata['wordpress_status']: 'publish'                              │
│    - metadata['last_wp_sync']: "2025-12-01T15:30:00Z"                     │
│                                                                           │
│ ✅ Creates SyncEvent record for audit trail                               │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 10: Results                                                          │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ WordPress Post Created:                                                │
│    - Title: "10 Outdoor Living Design Ideas"                             │
│    - Category: "Outdoor Living Design" ✅                                 │
│    - Tags: "outdoor lighting ideas", "outdoor patio design", ... ✅       │
│    - Featured Image: Displayed ✅                                         │
│    - Gallery: 2 images attached ✅                                        │
│    - SEO: Title and description set ✅                                    │
│    - Status: Published ✅                                                 │
│                                                                           │
│ ✅ IGNY8 Content Updated:                                                 │
│    - Status: 'published' ✅                                               │
│    - External ID: 789 ✅                                                  │
│    - External URL: "https://homeg8.com/..." ✅                            │
│    - WP Status Badge: Shows "Published" ✅                                │
└───────────────────────────────────────────────────────────────────────────┘
```

### After Fix: Sync Now Button Flow

```
┌───────────────────────────────────────────────────────────────────────────┐
│ STEP 1: User Action                                                       │
│ ────────────────────────────────────────────────────────────────────────  │
│ User navigates to: Settings → Integrations → WordPress → Content Types    │
│ Clicks: "Sync Now" button                                                │
│ Purpose: Update post counts and taxonomy counts from WordPress            │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 2: Frontend Request (FIXED)                                          │
│ ────────────────────────────────────────────────────────────────────────  │
│ POST /v1/integration/integrations/{id}/sync/                              │
│ Body: {                                                                   │
│   direction: 'metadata'  ✅ CHANGED from 'both'                          │
│ }                                                                         │
│                                                                           │
│ OLD CODE: direction: 'both' → republishes all content ❌                  │
│ NEW CODE: direction: 'metadata' → only fetches structure ✅               │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 3: Backend IntegrationViewSet (FIXED)                                │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ NEW: Checks direction parameter                                        │
│    if direction == 'metadata':                                            │
│        # Use new SyncMetadataService                                      │
│        metadata_service = SyncMetadataService()                           │
│        result = metadata_service.sync_wordpress_structure(integration)    │
│    else:                                                                  │
│        # Use existing SyncService for content sync                        │
│        sync_service = SyncService()                                       │
│        result = sync_service.sync(integration, direction)                 │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 4: SyncMetadataService (NEW)                                         │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ Gets WordPress site URL from integration.config_json['site_url']       │
│ ✅ Gets API key from integration.get_credentials()['api_key']             │
│                                                                           │
│ ✅ Calls WordPress metadata endpoint:                                     │
│    GET https://homeg8.com/wp-json/igny8/v1/site-metadata/                 │
│    Headers:                                                               │
│      X-IGNY8-API-Key: ***abc123                                           │
│                                                                           │
│ ✅ Does NOT query Content.objects                                         │
│ ✅ Does NOT publish any content                                           │
│ ✅ Does NOT call WordPressAdapter                                         │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 5: WordPress Returns Metadata                                        │
│ ────────────────────────────────────────────────────────────────────────  │
│ Response: {                                                               │
│   success: true,                                                          │
│   data: {                                                                 │
│     post_types: {                                                         │
│       "post": { label: "Posts", count: 28 },                              │
│       "page": { label: "Pages", count: 12 },                              │
│       "product": { label: "Products", count: 156 }                        │
│     },                                                                    │
│     taxonomies: {                                                         │
│       "category": { label: "Categories", count: 8 },                      │
│       "post_tag": { label: "Tags", count: 45 }                            │
│     },                                                                    │
│     plugin_connection_enabled: true,                                      │
│     two_way_sync_enabled: true                                            │
│   }                                                                       │
│ }                                                                         │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 6: Backend Returns to Frontend                                       │
│ ────────────────────────────────────────────────────────────────────────  │
│ Response: {                                                               │
│   success: true,                                                          │
│   post_types: { ... },                                                    │
│   taxonomies: { ... },                                                    │
│   message: "WordPress site structure synced successfully"                 │
│ }                                                                         │
│                                                                           │
│ ⏱️  Total time: < 5 seconds (was 30+ seconds before)                     │
└─────────────────────────────────┬─────────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼─────────────────────────────────────────┐
│ STEP 7: Frontend Updates UI                                               │
│ ────────────────────────────────────────────────────────────────────────  │
│ ✅ Shows success toast: "WordPress structure synced successfully"         │
│ ✅ Calls loadContentTypes() to refresh table                              │
│ ✅ Content Types table updates with fresh counts:                         │
│    - Posts: 28                                                            │
│    - Pages: 12                                                            │
│    - Products: 156                                                        │
│    - Categories: 8                                                        │
│    - Tags: 45                                                             │
│                                                                           │
│ ✅ No content was republished                                             │
│ ✅ No duplicate posts created                                             │
│ ✅ Fast response time                                                     │
└───────────────────────────────────────────────────────────────────────────┘
```

---

## Summary

This refactor plan addresses all three critical issues:

1. **Categories/Tags/Images Missing** - Diagnostic approach to identify exact issue, then fix field parsing
2. **Status Sync Broken** - Simple logic fix in webhook handler
3. **Sync Republishes Everything** - New SyncMetadataService separates metadata fetch from content publishing

All fixes are backwards-compatible and follow existing code patterns. Total implementation time: 8-11 hours.
