# Publisher Module

**Last Verified:** January 3, 2026  
**Status:** ✅ Active  
**Version:** 1.3.2
**Backend Path:** `backend/igny8_core/modules/publisher/` + `backend/igny8_core/business/publishing/`  
**Frontend Path:** `frontend/src/pages/Publisher/`

---

## Quick Reference

| What | File | Key Items |
|------|------|-----------|
| Views | `modules/publisher/views.py` | `PublishingRecordViewSet`, `DeploymentViewSet`, `PublishContentViewSet` |
| Models | `business/publishing/models.py` | `PublishingRecord`, `DeploymentRecord` |
| Integration Models | `modules/integration/models.py` | `PublishingSettings` |
| Services | `business/publishing/services/*.py` | Publishing orchestration |
| Scheduler Tasks | `igny8_core/tasks/publishing_scheduler.py` | Celery beat tasks |
| URLs | `modules/publisher/urls.py` | Publisher endpoints |
| Frontend | `pages/Publisher/ContentCalendar.tsx` | Content calendar view |

---

## Purpose

The Publisher module manages:
- Content publishing pipeline
- **Publishing scheduler (automated publishing)**
- Publishing record tracking
- Deployment management
- Multi-destination publishing
- **Content calendar visualization**

---

## Data Models

### PublishingRecord

| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Parent site |
| content | FK | Source content |
| destination | CharField | wordpress/ghost/webflow |
| external_id | CharField | ID on destination platform |
| external_url | URLField | Published URL |
| status | CharField | pending/published/failed/retracted |
| published_at | DateTime | Publication time |
| metadata | JSON | Additional data |
| created_at | DateTime | Record creation |

### DeploymentRecord

| Field | Type | Purpose |
|-------|------|---------|
| account | FK | Owner account |
| site | FK | Target site |
| deployment_type | CharField | full/incremental |
| status | CharField | pending/deploying/completed/failed |
| items_deployed | Integer | Number of items |
| started_at | DateTime | Start time |
| completed_at | DateTime | Completion time |
| error_log | TextField | Errors encountered |
| metadata | JSON | Deployment details |

### PublishingSettings (v1.3.2)

Site-level publishing configuration:

| Field | Type | Purpose |
|-------|------|---------|
| site | OneToOne | Parent site (unique) |
| auto_approval_enabled | Boolean | Auto-approve content |
| auto_publish_enabled | Boolean | Auto-publish approved content |
| daily_publish_limit | Integer | Max publications per day |
| weekly_publish_limit | Integer | Max publications per week |
| monthly_publish_limit | Integer | Max publications per month |
| publish_days | JSON | Days of week for publishing ["mon","wed","fri"] |
| publish_time_slots | JSON | Time slots for publishing [{"start":"09:00","end":"17:00"}] |

### Content Site Status Fields (v1.3.2)

Added to Content model for scheduling:

| Field | Type | Values |
|-------|------|--------|
| site_status | CharField | not_published, scheduled, publishing, published, failed |
| scheduled_publish_at | DateTime | Future publication time (nullable) |
| site_status_updated_at | DateTime | Last status change timestamp |

---

## API Endpoints

### Publishing & Records

| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `/api/v1/publisher/records/` | `PublishingRecordViewSet.list` | List publishing records |
| POST | `/api/v1/publisher/records/` | `PublishingRecordViewSet.create` | Create record |
| GET | `/api/v1/publisher/deployments/` | `DeploymentViewSet.list` | List deployments |
| POST | `/api/v1/publisher/publish/` | `PublishContentViewSet.publish` | Publish content immediately |
| GET | `/api/v1/publisher/publish/status/` | `PublishContentViewSet.status` | Get publishing status |
| GET | `/api/v1/publisher/site-definition/` | `SiteDefinitionViewSet.list` | Public site definitions |

### Scheduling Endpoints (v1.3.2+)

| Method | Path | Purpose | Request Body | Response |
|--------|------|---------|--------------|----------|
| **POST** | `/api/v1/writer/content/{id}/schedule/` | Schedule content for future publishing | `{ "scheduled_publish_at": "2025-01-20T09:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-20T09:00:00Z" }` |
| **POST** | `/api/v1/writer/content/{id}/reschedule/` | Reschedule existing scheduled content | `{ "scheduled_at": "2025-01-21T10:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-21T10:00:00Z" }` |
| **POST** | `/api/v1/writer/content/{id}/unschedule/` | Cancel scheduled publishing | `{}` | `{ "success": true, "message": "Content unscheduled" }` |
| **POST** | `/api/v1/writer/content/bulk_schedule/` | Bulk schedule with site defaults | `{ "content_ids": [1,2,3], "use_site_defaults": true, "site_id": 5 }` | `{ "success": true, "scheduled_count": 3, "schedule_preview": [...] }` |
| **POST** | `/api/v1/writer/content/bulk_schedule_preview/` | Preview bulk schedule times | `{ "content_ids": [1,2,3], "site_id": 5 }` | `{ "schedule_preview": [...], "site_settings": {...} }` |

### Publishing Settings

| Method | Path | Purpose |
|--------|------|---------|
| **GET** | `/api/v1/sites/{site_id}/settings?tab=publishing` | Get site publishing settings (default schedule, stagger, limits) |
| **PUT** | `/api/v1/sites/{site_id}/publishing-settings/` | Update publishing settings |

---

## API Usage Examples

### Publish Content Immediately

**Request:**
```bash
POST /api/v1/publisher/publish/
Content-Type: application/json

{
  "content_id": 123,
  "destinations": ["wordpress"]  # or ["shopify"], ["custom"]
}
```

**Success Response:**
```json
{
  "success": true,
  "data": {
    "success": true,
    "results": [
      {
        "destination": "wordpress",
        "success": true,
        "external_id": "456",
        "url": "https://mysite.com/article-title/",
        "publishing_record_id": 789,
        "platform_type": "wordpress"
      }
    ]
  }
}
```

**Error Response:**
```json
{
  "success": false,
  "error": "Publishing API error: Invalid credentials"
}
```

### Schedule Content for Future Publishing

**Request:**
```bash
POST /api/v1/writer/content/123/schedule/
Content-Type: application/json

{
  "scheduled_publish_at": "2025-01-20T09:00:00Z"
}
```

**Response:**
```json
{
  "success": true,
  "scheduled_publish_at": "2025-01-20T09:00:00Z",
  "site_status": "scheduled"
}
```

**Notes:**
- Content `site_status` changes from `not_published` → `scheduled`
- Celery task `process_scheduled_publications` will publish at scheduled time
- Runs every 5 minutes, so publishing happens within 5 min of scheduled time

### Reschedule Content

**Request:**
```bash
POST /api/v1/writer/content/123/reschedule/
Content-Type: application/json

{
  "scheduled_at": "2025-01-21T10:00:00Z"
}
```

**Response:**
```json
{
  "success": true,
  "scheduled_publish_at": "2025-01-21T10:00:00Z",
  "site_status": "scheduled"
}
```

**Use Cases:**
- Reschedule from `site_status='scheduled'` (change time)
- Reschedule from `site_status='failed'` (retry at new time)

### Unschedule Content

**Request:**
```bash
POST /api/v1/writer/content/123/unschedule/
Content-Type: application/json

{}
```

**Response:**
```json
{
  "success": true,
  "message": "Content unscheduled successfully",
  "site_status": "not_published"
}
```

**Notes:**
- Removes content from publishing queue
- Content returns to `site_status='not_published'`
- Can be rescheduled or published immediately later

### Bulk Schedule with Site Defaults

**Request:**
```bash
POST /api/v1/writer/content/bulk_schedule/
Content-Type: application/json

{
  "content_ids": [123, 124, 125, 126],
  "use_site_defaults": true,
  "site_id": 45
}
```

**Response:**
```json
{
  "success": true,
  "scheduled_count": 4,
  "schedule_preview": [
    {
      "content_id": 123,
      "scheduled_at": "2025-01-17T09:00:00Z",
      "title": "First Article"
    },
    {
      "content_id": 124,
      "scheduled_at": "2025-01-17T09:15:00Z",
      "title": "Second Article"
    },
    {
      "content_id": 125,
      "scheduled_at": "2025-01-17T09:30:00Z",
      "title": "Third Article"
    },
    {
      "content_id": 126,
      "scheduled_at": "2025-01-17T09:45:00Z",
      "title": "Fourth Article"
    }
  ],
  "site_settings": {
    "base_time": "09:00 AM",
    "stagger_interval": 15,
    "timezone": "America/New_York"
  }
}
```

**Notes:**
- Uses site's default publishing schedule from Site Settings
- Automatically staggers publications (e.g., 15 min intervals)
- No limit on number of items (unlike direct publish which is limited to 5)
- All items set to `site_status='scheduled'`

### Bulk Schedule Preview (Before Confirming)

**Request:**
```bash
POST /api/v1/writer/content/bulk_schedule_preview/
Content-Type: application/json

{
  "content_ids": [123, 124, 125],
  "site_id": 45
}
```

**Response:**
```json
{
  "schedule_preview": [
    {"content_id": 123, "scheduled_at": "2025-01-17T09:00:00Z", "title": "Article 1"},
    {"content_id": 124, "scheduled_at": "2025-01-17T09:15:00Z", "title": "Article 2"},
    {"content_id": 125, "scheduled_at": "2025-01-17T09:30:00Z", "title": "Article 3"}
  ],
  "site_settings": {
    "base_time": "09:00 AM",
    "stagger_interval": 15,
    "timezone": "America/New_York",
    "publish_days": ["mon", "tue", "wed", "thu", "fri"]
  }
}
```

**Use Case:**
- Show user what times items will be scheduled before confirming
- Allow user to adjust site settings if needed
- User clicks "Confirm" to execute actual bulk_schedule

---

## Publishing Scheduler (v1.3.2)

### Celery Beat Tasks

Located in `igny8_core/tasks/publishing_scheduler.py`:

| Task | Schedule | Purpose |
|------|----------|---------|
| `schedule_approved_content` | Every 15 minutes | Assigns publish times to approved content |
| `process_scheduled_publications` | Every 5 minutes | Publishes content when scheduled time arrives |
| `cleanup_failed_publications` | Daily at midnight | Retries or cleans up failed publications |

### Scheduling Flow

```
Approved Content → schedule_approved_content (every 15 min)
                        ↓
            Check PublishingSettings
                        ↓
            Assign scheduled_publish_at based on:
            - publish_days configuration
            - publish_time_slots configuration  
            - daily/weekly/monthly limits
                        ↓
            Set site_status = "scheduled"
                        ↓
process_scheduled_publications (every 5 min)
                        ↓
            If scheduled_publish_at <= now:
            - Set site_status = "publishing"
            - Execute publication
            - Set site_status = "published" or "failed"
```

### Site Status State Machine

```
not_published → scheduled → publishing → published
                    ↓              ↓
                    ↓         → failed
                    ↓
              not_published (if unscheduled)
```

---

## Frontend Pages (v1.3.2)

### Content Calendar (`/publisher/content-calendar`)

**Purpose:** Visualize and manage scheduled content

**Features:**
- Calendar view of scheduled publications
- List view alternative
- Filter by site
- Schedule/unschedule actions
- Drag-and-drop rescheduling (planned)

**Components:**
- `ContentCalendar.tsx` - Main page component
- `CalendarItemTooltip` - Hover details for calendar items
- `SiteInfoBar` - Site context header

### Publishing Queue (`/publisher/publishing-queue`)

**Purpose:** Review upcoming publications

**Features:**
- List of content pending publication
- Status indicators (scheduled, publishing, failed)
- Publish now / unschedule actions

---

## Publishing Flow

### Single Content Publish

**Trigger:** User clicks "Publish" or API call  
**Flow:**
1. Validate content is in publishable state
2. Get site integration credentials
3. Transform content for destination:
   - Format HTML for platform
   - Process images
   - Map taxonomies
4. Push to destination API
5. Create `PublishingRecord` with external ID
6. Update Content status to `published`

### Batch Deployment

**Trigger:** Deployment action  
**Flow:**
1. Create `DeploymentRecord`
2. Gather all pending content
3. Process each item:
   - Publish to destination
   - Create publishing record
   - Update content status
4. Update deployment status
5. Log any errors

---

## Destination Adapters

### WordPress Adapter

- Uses WordPress REST API
- Supports posts, pages, custom post types
- Handles media upload
- Maps categories/tags

### Ghost Adapter (Planned)

- Ghost Admin API integration
- Post publishing
- Image handling

### Webflow Adapter (Planned)

- Webflow CMS API
- Collection item publishing
- Asset management

---

## Publishing States

| Status | Description |
|--------|-------------|
| pending | Ready to publish |
| published | Successfully published |
| failed | Publishing failed |
| retracted | Unpublished/removed |

---

## Site Definition Endpoint

**Purpose:** Public endpoint for headless CMS use cases

Returns site structure for external consumption:
- Site metadata
- Available taxonomies
- Content types
- URL structure

---

## Integration Points

| From | To | Trigger |
|------|----|---------| 
| Writer | Publisher | Publish action |
| Automation Stage 7 | Publisher | Auto-publish (future) |
| Publisher | Integrations | Destination APIs |
| Publisher | Content | Status updates |

---

## Common Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| Publish failed | Invalid credentials | Check integration settings |
| Duplicate posts | Published twice | Check existing publishing record |
| Images missing | Upload failed | Check media library access |
| Wrong category | Mapping issue | Verify taxonomy sync |

---

## Planned Changes

| Feature | Status | Description |
|---------|--------|-------------|
| Ghost integration | 🔜 Planned | Ghost CMS publishing |
| Webflow integration | 🔜 Planned | Webflow publishing |
| ~~Scheduled publishing~~ | ✅ Implemented (v1.3.2) | Future-date publishing |
| Republish detection | 🔜 Planned | Detect and handle updates |
| ~~Publishing queue~~ | ✅ Implemented (v1.3.2) | Batch publishing with queue |
| Drag-and-drop calendar | 🔜 Planned | Reschedule via drag-and-drop |
