temproary docs uplaoded
This commit is contained in:
@@ -0,0 +1,596 @@
|
||||
# Scheduled Content Publishing Workflow
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Module:** Publishing / Automation
|
||||
**Status:** ✅ Site filtering fixed and verified
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8 provides automated content publishing to WordPress, Shopify, and custom sites. Content goes through a scheduling process before being published at the designated time.
|
||||
|
||||
**Current System (v2.0):**
|
||||
- Site credentials stored directly on `Site` model (`api_key`, `domain`, `platform_type`)
|
||||
- Multi-platform support: WordPress, Shopify, Custom APIs
|
||||
- No `SiteIntegration` model required
|
||||
- Publishing via `PublisherService` (not legacy Celery tasks)
|
||||
- API endpoint: `POST /api/v1/publisher/publish`
|
||||
- **Site Filtering:** All content queries filtered by `site_id` (fixed Jan 2026)
|
||||
|
||||
---
|
||||
|
||||
## Content Lifecycle for Publishing
|
||||
|
||||
### Understanding Content.status vs Content.site_status
|
||||
|
||||
Content has **TWO separate status fields**:
|
||||
|
||||
1. **`status`** - Editorial workflow status
|
||||
- `draft` - Being created/edited
|
||||
- `review` - Submitted for review
|
||||
- `approved` - Ready for publishing
|
||||
- `published` - Legacy (not used for external publishing)
|
||||
|
||||
2. **`site_status`** - External site publishing status (platform-agnostic)
|
||||
- `not_published` - Not yet published to any site
|
||||
- `scheduled` - Has a scheduled_publish_at time
|
||||
- `publishing` - Currently being published
|
||||
- `published` - Successfully published to site (WordPress, Shopify, or Custom)
|
||||
- `failed` - Publishing failed
|
||||
|
||||
**Note:** `site_status` works across all platforms (WordPress, Shopify, Custom) and is filtered by `site_id` to show only content for the selected site.
|
||||
|
||||
### Publishing Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ DRAFT │ ← Content is being created/edited
|
||||
│ status: draft │
|
||||
└────────┬────────┘
|
||||
│ User approves content
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ APPROVED │ ← Content is ready for publishing
|
||||
│ status: approved│ status='approved', site_status='not_published'
|
||||
│ site_status: │
|
||||
│ not_published │
|
||||
└────────┬────────┘
|
||||
│ Hourly: schedule_approved_content task
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ SCHEDULED │ ← Content has a scheduled_publish_at time
|
||||
│ status: approved│ site_status='scheduled'
|
||||
│ site_status: │ scheduled_publish_at set to future datetime
|
||||
│ scheduled │
|
||||
└────────┬────────┘
|
||||
│ Every 5 min: process_scheduled_publications task
|
||||
│ (when scheduled_publish_at <= now)
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ PUBLISHING │ ← WordPress API call in progress
|
||||
│ status: approved│ site_status='publishing'
|
||||
│ site_status: │
|
||||
│ publishing │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────┐ ┌────────┐
|
||||
│PUBLISHED│ │ FAILED │
|
||||
│status: │ │status: │
|
||||
│approved │ │approved│
|
||||
│site_ │ │site_ │
|
||||
│status: │ │status: │
|
||||
│published│ │failed │
|
||||
└─────────┘ └────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Site Selector & Content Filtering
|
||||
|
||||
**Content Calendar** (`frontend/src/pages/Publisher/ContentCalendar.tsx`):
|
||||
- Automatically filters all content by selected site
|
||||
- Reloads data when site selector changes
|
||||
- Shows scheduled, publishing, published, and failed content for active site only
|
||||
|
||||
**Critical Implementation Details:**
|
||||
- All API queries include `site_id: activeSite.id` parameter
|
||||
- Backend `ContentFilter` includes `site_id` in filterable fields
|
||||
- useEffect hook reacts to `activeSite?.id` changes to trigger reload
|
||||
- Content cleared when no site selected
|
||||
|
||||
**Site Selector Fix (Jan 2026):**
|
||||
- Fixed circular dependency in useEffect (lines 285-294)
|
||||
- Only depends on `activeSite?.id`, not on callback functions
|
||||
- Added console logging for debugging site changes
|
||||
- Pattern follows Dashboard and Approved pages
|
||||
|
||||
**Backend Filter Configuration:**
|
||||
```python
|
||||
# backend/igny8_core/modules/writer/views.py
|
||||
class ContentFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Content
|
||||
fields = [
|
||||
'cluster_id',
|
||||
'site_id', # Required for site filtering
|
||||
'status',
|
||||
'site_status', # Required for status filtering
|
||||
'content_type',
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Celery Tasks
|
||||
|
||||
### 1. `schedule_approved_content`
|
||||
|
||||
**Schedule:** Every hour at :00
|
||||
**Task Name:** `publishing.schedule_approved_content`
|
||||
**File:** `backend/igny8_core/tasks/publishing_scheduler.py`
|
||||
|
||||
#### What It Does:
|
||||
1. Finds all sites with `PublishingSettings.auto_publish_enabled = True`
|
||||
2. Gets approved content (`status='approved'`, `site_status='not_published'`, `scheduled_publish_at=null`)
|
||||
3. Calculates available publishing slots based on:
|
||||
- `publish_days` - which days are allowed (e.g., Mon-Fri)
|
||||
- `publish_time_slots` - which times are allowed (e.g., 09:00, 14:00, 18:00)
|
||||
- `daily_publish_limit` - max posts per day
|
||||
- `weekly_publish_limit` - max posts per week
|
||||
- `monthly_publish_limit` - max posts per month
|
||||
4. Assigns `scheduled_publish_at` datetime and sets `site_status='scheduled'`
|
||||
|
||||
#### Configuration Location:
|
||||
`PublishingSettings` model linked to each Site. Configurable via:
|
||||
- Admin: `/admin/integration/publishingsettings/`
|
||||
- API: `/api/v1/sites/{site_id}/publishing-settings/`
|
||||
|
||||
---
|
||||
|
||||
### 2. `process_scheduled_publications`
|
||||
|
||||
**Schedule:** Every 5 minutes
|
||||
**Task Name:** `publishing.process_scheduled_publications`
|
||||
**File:** `backend/igny8_core/tasks/publishing_scheduler.py`
|
||||
|
||||
#### What It Does:
|
||||
1. Finds all content where:
|
||||
- `site_status='scheduled'`
|
||||
- `scheduled_publish_at <= now`
|
||||
2. For each content item:
|
||||
- Validates WordPress configuration exists on Site (`wp_api_key`, `domain`)
|
||||
- Updates `site_status='publishing'`
|
||||
- Calls `PublisherService.publish_content()` directly (synchronous)
|
||||
- Updates `site_status='published'` on success or `'failed'` on error
|
||||
3. Logs results and any errors
|
||||
|
||||
**Current Implementation (v2.0):**
|
||||
- Uses `PublisherService` (current system)
|
||||
- Gets config from `Site.wp_api_key` and `Site.domain`
|
||||
- No `SiteIntegration` required
|
||||
- Synchronous publishing (not queued to Celery)
|
||||
|
||||
---
|
||||
|
||||
### 3. `PublisherService.publish_content`
|
||||
|
||||
**Type:** Service method (called by scheduler)
|
||||
**File:** `backend/igny8_core/business/publishing/services/publisher_service.py`
|
||||
|
||||
#### What It Does:
|
||||
1. **Load Content** - Gets content by ID
|
||||
2. **Get WordPress Config** - Reads from `Site.wp_api_key` and `Site.domain`
|
||||
3. **Call Adapter** - Uses `WordPressAdapter` to handle API communication
|
||||
4. **WordPress API Call** - POSTs to `{domain}/wp-json/igny8/v1/publish`
|
||||
5. **Update Content** - Sets `external_id`, `external_url`, `site_status='published'`
|
||||
6. **Create Record** - Logs in `PublishingRecord` model
|
||||
|
||||
#### WordPress Connection:
|
||||
- Uses the IGNY8 WordPress Bridge plugin installed on the site
|
||||
- API endpoint: `{site.domain}/wp-json/igny8/v1/publish`
|
||||
- Authentication: API key from `Site.wp_api_key`
|
||||
- **No SiteIntegration model needed**
|
||||
|
||||
---
|
||||
|
||||
## Database Models
|
||||
|
||||
### Content Fields (Publishing Related)
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | CharField | **Editorial workflow**: `draft`, `review`, `approved` |
|
||||
| `site_status` | CharField | **WordPress publishing status**: `not_published`, `scheduled`, `publishing`, `published`, `failed` |
|
||||
| `site_status_updated_at` | DateTimeField | When site_status was last changed |
|
||||
| `scheduled_publish_at` | DateTimeField | When content should be published (null if not scheduled) |
|
||||
| `external_id` | CharField | WordPress post ID after publishing |
|
||||
| `external_url` | URLField | WordPress post URL after publishing |
|
||||
|
||||
**Important:** These are separate concerns:
|
||||
- `status` tracks editorial approval
|
||||
- `site_status` tracks external publishing
|
||||
- Content typically has `status='approved'` AND `site_status='not_published'` before scheduling
|
||||
|
||||
### PublishingSettings Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `site` | ForeignKey | The site these settings apply to |
|
||||
| `auto_publish_enabled` | BooleanField | Whether automatic scheduling is enabled |
|
||||
| `publish_days` | JSONField | List of allowed days: `['mon', 'tue', 'wed', 'thu', 'fri']` |
|
||||
| `publish_time_slots` | JSONField | List of times: `['09:00', '14:00', '18:00']` |
|
||||
| `daily_publish_limit` | IntegerField | Max posts per day (null = unlimited) |
|
||||
| `weekly_publish_limit` | IntegerField | Max posts per week (null = unlimited) |
|
||||
| `monthly_publish_limit` | IntegerField | Max posts per month (null = unlimited) |
|
||||
|
||||
---
|
||||
|
||||
## Celery Beat Schedule
|
||||
|
||||
From `backend/igny8_core/celery.py`:
|
||||
|
||||
```python
|
||||
app.conf.beat_schedule = {
|
||||
# ...
|
||||
'schedule-approved-content': {
|
||||
'task': 'publishing.schedule_approved_content',
|
||||
'schedule': crontab(minute=0), # Every hour at :00
|
||||
},
|
||||
'process-scheduled-publications': {
|
||||
'task': 'publishing.process_scheduled_publications',
|
||||
'schedule': crontab(minute='*/5'), # Every 5 minutes
|
||||
},
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual Publishing
|
||||
|
||||
Content can be published immediately or rescheduled via API:
|
||||
|
||||
### Publish Now
|
||||
```
|
||||
POST /api/v1/publisher/publish
|
||||
{
|
||||
"content_id": 123,
|
||||
"destinations": ["wordpress"]
|
||||
}
|
||||
```
|
||||
|
||||
### Schedule for Later
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/schedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Reschedule Failed Content
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Reschedule works from any `site_status` (failed, published, scheduled, etc.)
|
||||
|
||||
### Unschedule
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/unschedule/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Debugging
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
**Browser Console Logs:**
|
||||
When changing sites in Content Calendar, you should see:
|
||||
```
|
||||
[ContentCalendar] Site changed to: 45 My Site Name
|
||||
[ContentCalendar] Triggering loadQueue...
|
||||
```
|
||||
|
||||
**Check Site Filtering:**
|
||||
1. Open browser DevTools → Network tab
|
||||
2. Change site in site selector
|
||||
3. Look for API calls to `/api/v1/writer/content/`
|
||||
4. Verify `site_id` parameter is included in query string
|
||||
5. Verify count matches database for that site
|
||||
|
||||
**Common Issues:**
|
||||
- No console logs when changing sites → useEffect not triggering (refresh page)
|
||||
- API calls missing `site_id` parameter → backend filter not working
|
||||
- Wrong count displayed → database query issue or cache problem
|
||||
|
||||
### Backend Log Files
|
||||
- **Publish Logs:** `backend/logs/publish-sync-logs/`
|
||||
- **API Logs:** `backend/logs/wordpress_api.log`
|
||||
|
||||
### Check Celery Status
|
||||
```bash
|
||||
docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_worker
|
||||
docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_beat
|
||||
```
|
||||
|
||||
### Check Scheduled Content
|
||||
```python
|
||||
# Django shell
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
|
||||
# Past due content (should have been published)
|
||||
Content.objects.filter(
|
||||
site_status='scheduled',
|
||||
scheduled_publish_at__lt=timezone.now()
|
||||
).count()
|
||||
|
||||
# Upcoming scheduled content
|
||||
Content.objects.filter(
|
||||
site_status='scheduled',
|
||||
scheduled_publish_at__gt=timezone.now()
|
||||
).order_by('scheduled_publish_at')[:10]
|
||||
|
||||
# Check scheduled content for specific site
|
||||
site_id = 45
|
||||
Content.objects.filter(
|
||||
site_id=site_id,
|
||||
site_status='scheduled'
|
||||
).count()
|
||||
|
||||
# Compare with frontend display
|
||||
# Should match count shown in Content Calendar for that site
|
||||
```
|
||||
|
||||
### Manual Task Execution
|
||||
```python
|
||||
# Django shell
|
||||
from igny8_core.tasks.publishing_scheduler import (
|
||||
schedule_approved_content,
|
||||
process_scheduled_publications
|
||||
)
|
||||
|
||||
# Run scheduling task
|
||||
schedule_approved_content()
|
||||
|
||||
# Process due publications
|
||||
process_scheduled_publications()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Failure Reasons
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| No WordPress API key | Site.wp_api_key is empty | Configure API key in Site settings |
|
||||
| No domain configured | Site.domain is empty | Set domain in Site settings |
|
||||
| API key invalid/expired | WordPress API key issue | Regenerate API key in WordPress plugin |
|
||||
| Connection timeout | WordPress site unreachable | Check site availability |
|
||||
| Plugin not active | IGNY8 Bridge plugin disabled | Enable plugin in WordPress |
|
||||
| Content already published | Duplicate publish attempt | Use reschedule to republish |
|
||||
|
||||
### Retry Policy
|
||||
- Failed content marked with `site_status='failed'`
|
||||
- Use the `reschedule` action to retry publishing
|
||||
- Can reschedule from any status (failed, published, etc.)
|
||||
|
||||
### Rescheduling Failed Content
|
||||
|
||||
```python
|
||||
# Via API
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
|
||||
# Via Django shell
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
failed_content = Content.objects.filter(site_status='failed')
|
||||
for content in failed_content:
|
||||
# Reschedule for 1 hour from now
|
||||
content.site_status = 'scheduled'
|
||||
content.scheduled_publish_at = timezone.now() + timedelta(hours=1)
|
||||
content.site_status_updated_at = timezone.now()
|
||||
content.save(update_fields=['site_status', 'scheduled_publish_at', 'site_status_updated_at'])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Site Selector Not Updating Content Calendar
|
||||
|
||||
**Symptoms:**
|
||||
- Changing sites doesn't reload calendar content
|
||||
- Count shows wrong number of scheduled items
|
||||
- Content from wrong site displayed
|
||||
|
||||
**Solution:**
|
||||
1. **Hard refresh browser:** Ctrl+F5 (Windows/Linux) or Cmd+Shift+R (Mac)
|
||||
2. **Clear browser cache**
|
||||
3. **Check console logs:** Should see `[ContentCalendar] Site changed to: ...`
|
||||
4. **Verify API calls:** Check Network tab for `site_id` parameter
|
||||
|
||||
**What Was Fixed (Jan 2026):**
|
||||
- Frontend: Fixed useEffect circular dependency in ContentCalendar.tsx
|
||||
- Backend: Added `site_id` and `site_status` to ContentFilter fields
|
||||
- All API queries now properly filter by `site_id: activeSite.id`
|
||||
- Content clears when no site selected
|
||||
|
||||
**If Problem Persists:**
|
||||
```bash
|
||||
# Check backend filter configuration
|
||||
grep -n "site_id" backend/igny8_core/modules/writer/views.py
|
||||
|
||||
# Should show site_id in ContentFilter.Meta.fields
|
||||
```
|
||||
|
||||
### Content Not Being Scheduled
|
||||
|
||||
1. Check `PublishingSettings.auto_publish_enabled` is `True`
|
||||
2. Verify content has `status='approved'` and `site_status='not_published'`
|
||||
3. Check `scheduled_publish_at` is null (already scheduled content won't reschedule)
|
||||
4. Verify publish limits haven't been reached
|
||||
5. **Verify correct site selected** in site selector
|
||||
|
||||
### Content Not Publishing
|
||||
|
||||
1. Check Celery Beat is running: `docker compose logs igny8_celery_beat`
|
||||
2. Check Celery Worker is running: `docker compose logs igny8_celery_worker`
|
||||
3. Look for errors in worker logs
|
||||
4. Verify Site has `wp_api_key` configured
|
||||
5. Verify Site has `domain` configured
|
||||
6. Test WordPress API connectivity
|
||||
|
||||
### Resetting Failed Content
|
||||
|
||||
Use the reschedule API endpoint:
|
||||
|
||||
```bash
|
||||
# Reschedule single content
|
||||
curl -X POST https://api.igny8.com/api/v1/writer/content/344/reschedule/ \
|
||||
-H "Authorization: Api-Key YOUR_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"scheduled_at": "2026-01-20T15:00:00Z"}'
|
||||
```
|
||||
|
||||
Or via Django shell:
|
||||
|
||||
```python
|
||||
# Reset all failed content to reschedule in 1 hour
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
failed_content = Content.objects.filter(site_status='failed')
|
||||
for content in failed_content:
|
||||
content.site_status = 'scheduled'
|
||||
content.scheduled_publish_at = timezone.now() + timedelta(hours=1)
|
||||
content.site_status_updated_at = timezone.now()
|
||||
content.save(update_fields=['site_status', 'scheduled_publish_at', 'site_status_updated_at'])
|
||||
print(f"Rescheduled content {content.id}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 Backend │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────Validate Site.wp_api_key & domain │ │
|
||||
│ │ - Call PublisherService.publish_content() │ │
|
||||
│ │ │ │
|
||||
│ │ 3. PublisherService │ │
|
||||
│ │ - Get config from Site model │ │
|
||||
│ │ - Call WordPressAdapter │ │
|
||||
│ │ - Update content status │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────────┼─────────────────────────────┘
|
||||
│
|
||||
▼ HTTPS
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Site │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ IGNY8 Bridge Plugin │ │
|
||||
│ │ │ │
|
||||
│ │ /wp-json/igny8/v1/publish │ │
|
||||
│ │ - Receives content payload │ │
|
||||
│ │ - Creates/updates WordPress post │ │
|
||||
│ │ - Handles images, categories, tags │ │
|
||||
│ │ - Returns post ID and URL │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Schedule Content
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/schedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_at": "2026-01-20T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Reschedule Content (Failed or Any Status)
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Unschedule Content
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/unschedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
```
|
||||
|
||||
### Publish Immediately
|
||||
```http
|
||||
POST /api/v1/publisher/publish
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content_id": 123,
|
||||
"destinations": ["wordpress"]
|
||||
}│
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────────┼─────────────────────────────┘
|
||||
│
|
||||
▼ HTTPS
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Site │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ IGNY8 Bridge Plugin │ │
|
||||
│ │ │ │
|
||||
│ │ /wp-json/igny8-bridge/v1/publish │ │
|
||||
│ │ - Receives content payload │ │
|
||||
│ │ - Creates/updates WordPress post │ │
|
||||
│ │ - Handles images, categories, tags │ │
|
||||
│ │ - Returns post ID and URL │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Publisher Module](../10-MODULES/PUBLISHER.md)
|
||||
- [WordPress Integration](../60-PLUGINS/WORDPRESS-INTEGRATION.md)
|
||||
- [Content Pipeline](CONTENT-PIPELINE.md)
|
||||
Reference in New Issue
Block a user