This commit is contained in:
IGNY8 VPS (Salman)
2026-03-23 17:20:51 +00:00
parent e78a41f11c
commit 0570052fec
21 changed files with 15889 additions and 0 deletions

View File

@@ -0,0 +1,782 @@
# IGNY8 Phase 2: Socializer (02H)
## Native Social Media Posting & Engagement Engine — Stage 8
**Document Version:** 1.0
**Date:** 2026-03-23
**Phase:** IGNY8 Phase 2 — Feature Expansion
**Status:** Build Ready
**Source of Truth:** Codebase at `/data/app/igny8/`
**Audience:** Claude Code, Backend Developers, Architects
---
## 1. CURRENT STATE
### Social Media Today
There is **no** social media functionality in IGNY8. No OAuth connections to any social platform exist. No social post generation, scheduling, or analytics. The automation pipeline ends at Stage 7 (publish to WordPress).
### What Exists
- 7-stage automation pipeline (01E) terminates at publish — no post-publish social distribution
- `SiteIntegration` model (integration app) — handles WordPress OAuth; pattern reusable for social OAuth
- `Content` model — stores the content that feeds social post generation
- `Images` model (writer app) — stores generated images that can be resized for social platforms
- `AutomationConfig` model (automation app) — per-site automation settings; extendable for social toggles
- Celery infrastructure with beat scheduler — ready for social scheduling tasks
### What Does Not Exist
- No social app or models
- No OAuth connections for LinkedIn, Twitter/X, Facebook, Instagram, TikTok
- No AI social post generation or platform adaptation
- No scheduling/calendar system
- No engagement tracking
- No Stage 8 pipeline integration
---
## 2. WHAT TO BUILD
### Overview
Build Stage 8 of the automation pipeline: a social media engine that connects to 5 platforms via OAuth, generates AI-adapted posts from published content, schedules posts with best-time optimization, and tracks engagement metrics.
### 2.1 Platform Support
| Platform | Max Length | Key Format | OAuth Flow | API |
|----------|-----------|------------|------------|-----|
| **LinkedIn** | 1,300 chars | Professional, industry insights | OAuth 2.0 (3-legged) | Marketing API v2 |
| **Twitter/X** | 280 chars (thread option) | Concise, hashtags, hooks | OAuth 2.0 (PKCE) | API v2 |
| **Facebook** | ~63,206 chars (500 optimal) | Conversational, visual | OAuth 2.0 (Facebook Login) | Graph API v18+ |
| **Instagram** | 2,200 chars, 30 hashtags | Visual-first, hashtag-heavy | Via Facebook Business | Graph API (IG) |
| **TikTok** | varies | Gen-Z tone, trending hooks | OAuth 2.0 | Content Posting API |
### 2.2 OAuth Infrastructure
Reusable OAuth service structure:
**Location:** `igny8_core/integration/oauth/`
- `base_oauth.py` — base class with token encrypt/decrypt, refresh logic
- `linkedin_oauth.py` — LinkedIn 3-legged OAuth, scopes: `w_member_social`, `r_organization_social`, `w_organization_social`
- `twitter_oauth.py` — Twitter OAuth 2.0 with PKCE
- `facebook_oauth.py` — Facebook Login, page selection, scopes: `pages_manage_posts`, `pages_read_engagement`
- `instagram_oauth.py` — via Facebook Business, Instagram Basic Display API
- `tiktok_oauth.py` — TikTok OAuth 2.0
**Token Security:**
- Access tokens and refresh tokens encrypted using the same encryption pattern as GSC tokens (02C)
- Tokens stored in `SocialAccount.access_token` and `SocialAccount.refresh_token` (encrypted TextField)
- Auto-refresh via Celery task (`refresh_social_tokens`) runs hourly
**Platform-Specific Auth Notes:**
- **Facebook:** Requires App Review for `pages_manage_posts` permission. Flow: Login → Page Selection → Permission Grant → Store `page_access_token`. Webhook subscription for engagement updates.
- **LinkedIn:** Requires Marketing API Partner Program access. Flow: Authorization → Organization Selection → Store `access_token`.
- **Instagram:** No direct OAuth — connected via Facebook Business account. Requires Facebook Page linked to Instagram Professional account.
- **Multi-account support:** Multiple SocialAccount records per platform per site.
### 2.3 AI Content Adaptation
**Input:** Content record (title, content_html, meta_description, cluster keywords from SAGCluster)
**Per platform, AI generates:**
- Adapted text matching platform tone + length limits
- Hashtag set (topic-aware, platform-appropriate count)
- CTA appropriate to platform conventions
- Image sizing recommendations
**Platform-Specific Generation:**
- **LinkedIn:** Professional tone, industry framing, 3-5 hashtags, link in text
- **Twitter/X:** Hook + key point + CTA in 280 chars. Thread generation option (2-10 tweets) for long content
- **Facebook:** Conversational, 2-3 paragraphs, 3-5 hashtags, link at end
- **Instagram:** Visual-first caption, 20-30 hashtags in first comment or caption, no clickable link (use "link in bio" CTA)
- **TikTok:** Gen-Z/casual tone, trending hook format, 3-5 hashtags
### 2.4 Post Types
| # | Post Type | Description | Platforms |
|---|-----------|-------------|-----------|
| 1 | **Announcement** | Link post with title + excerpt + URL | All |
| 2 | **Highlights** | Key takeaways from article + CTA | All |
| 3 | **Quote Card** | Branded insight/statistic as image post | All |
| 4 | **FAQ Snippet** | Single FAQ from content as post | LinkedIn, Twitter, Facebook |
| 5 | **Carousel** | Multi-image posts (future) | Instagram, LinkedIn |
### 2.5 Image Sizing per Platform
| Platform | Dimension | Aspect Ratio |
|----------|-----------|-------------|
| LinkedIn | 1200×627px | ~1.91:1 |
| Twitter | 1600×900px | ~16:9 |
| Facebook | 1200×630px | ~1.91:1 |
| Instagram (square) | 1080×1080px | 1:1 |
| Instagram (portrait) | 1080×1350px | 4:5 |
| TikTok | 1080×1920px | 9:16 |
Image resizing uses the `Images` model records generated by pipeline Stages 5-6, resized to platform specs using Pillow.
### 2.6 Scheduling & Calendar
**Best-Time Algorithm:**
- Configurable default best-time slots per platform
- Per-account override via `SocialAccount.settings.best_times[]`
- If engagement data available, learn optimal posting times from past performance
**Queue System:**
- Posts with `status='scheduled'` and `scheduled_at` in the past → Celery task publishes
- Celery task `publish_scheduled_posts` runs every minute
- Frequency caps: max posts per day per platform (configurable, default: 2)
- Cooldown: 4-6 hours between posts to same platform (configurable via `SocialAccount.settings.cooldown_hours`)
**Calendar View:**
- API endpoint returns week/month layout of scheduled + published posts
- Frontend renders visual calendar with drag-drop rescheduling (`.tsx` component)
**Bulk Scheduling:**
- Select multiple content → generate social posts for all connected platforms
- Auto-space across optimal times over days/weeks
- Review before confirming schedule
### 2.7 Engagement Tracking
- Celery task `fetch_engagement` runs every 6 hours
- Fetches metrics from each platform API for recent posts (last 30 days)
- Metrics: likes, comments, shares/retweets, impressions, clicks, engagement_rate
- Aggregate dashboard: per-platform performance, best performing posts, optimal posting times
- Attribution: UTM parameters appended to all shared URLs (`utm_source={platform}&utm_medium=social&utm_campaign=igny8`)
### 2.8 Stage 8 Pipeline Integration
After Stage 7 (publish to WordPress), if social accounts are connected for the site:
1. Pipeline triggers Stage 8
2. Load all connected `SocialAccount` records for the site
3. For each connected account, AI generates platform-adapted post
4. **If `AutomationConfig.auto_social_publish` enabled:** posts queue immediately at next best-time slot
5. **If manual review enabled:** posts go to `status='draft'` for user review
6. Published posts logged with `platform_post_id` for engagement tracking
**AutomationConfig Extension:**
```python
# Add to AutomationConfig.settings JSONField:
{
"social_enabled": True, # Master toggle
"auto_social_publish": False, # Auto-queue or manual review
"social_platforms": ["linkedin", "twitter"], # Which platforms to auto-post
"social_post_types": ["announcement", "highlights"], # Which post types
}
```
---
## 3. DATA MODELS & APIS
### 3.1 New Models
All models in a new `social` app.
#### SocialAccount (social app)
```python
class SocialAccount(SiteSectorBaseModel):
"""
Connected social media account for a site.
Stores OAuth tokens (encrypted) and per-account settings.
Multi-account support: multiple accounts per platform per site.
"""
platform = models.CharField(
max_length=15,
choices=[
('linkedin', 'LinkedIn'),
('twitter', 'Twitter/X'),
('facebook', 'Facebook'),
('instagram', 'Instagram'),
('tiktok', 'TikTok'),
]
)
platform_account_id = models.CharField(
max_length=255,
help_text='External platform account/page ID'
)
account_name = models.CharField(max_length=255, help_text='Display name')
account_type = models.CharField(
max_length=20,
choices=[
('personal', 'Personal'),
('page', 'Page'),
('organization', 'Organization'),
],
default='personal'
)
access_token = models.TextField(
help_text='Encrypted OAuth access token'
)
refresh_token = models.TextField(
blank=True,
default='',
help_text='Encrypted OAuth refresh token (nullable for platforms without refresh)'
)
token_expiry = models.DateTimeField(null=True, blank=True)
permissions = models.JSONField(
default=list,
help_text='Granted OAuth scopes'
)
status = models.CharField(
max_length=10,
choices=[
('active', 'Active'),
('expired', 'Expired'),
('revoked', 'Revoked'),
('error', 'Error'),
],
default='active'
)
settings = models.JSONField(
default=dict,
help_text='{auto_publish, max_per_day, best_times[], cooldown_hours}'
)
class Meta:
app_label = 'social'
db_table = 'igny8_social_accounts'
```
**PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel
#### SocialPost (social app)
```python
class SocialPost(SiteSectorBaseModel):
"""
A social media post — generated from content or standalone.
Tracks full lifecycle from draft to published with engagement.
"""
content = models.ForeignKey(
'writer.Content',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='social_posts',
help_text='Source content (null for standalone posts)'
)
social_account = models.ForeignKey(
'social.SocialAccount',
on_delete=models.CASCADE,
related_name='posts'
)
platform = models.CharField(
max_length=15,
help_text='Denormalized from social_account for filtering'
)
post_type = models.CharField(
max_length=15,
choices=[
('announcement', 'Announcement'),
('highlights', 'Highlights'),
('quote_card', 'Quote Card'),
('faq_snippet', 'FAQ Snippet'),
('carousel', 'Carousel'),
]
)
text = models.TextField()
hashtags = models.JSONField(default=list, help_text='List of hashtag strings')
media_urls = models.JSONField(default=list, help_text='List of image/video URLs')
link_url = models.URLField(blank=True, default='')
utm_params = models.JSONField(
default=dict,
blank=True,
help_text='{utm_source, utm_medium, utm_campaign}'
)
thread_posts = models.JSONField(
null=True,
blank=True,
help_text='For Twitter threads: [{text, media_url}]'
)
scheduled_at = models.DateTimeField(null=True, blank=True)
published_at = models.DateTimeField(null=True, blank=True)
platform_post_id = models.CharField(
max_length=255,
blank=True,
default='',
help_text='External post ID after publishing'
)
platform_post_url = models.URLField(blank=True, default='')
status = models.CharField(
max_length=15,
choices=[
('draft', 'Draft'),
('scheduled', 'Scheduled'),
('publishing', 'Publishing'),
('published', 'Published'),
('failed', 'Failed'),
('cancelled', 'Cancelled'),
],
default='draft'
)
error_message = models.TextField(blank=True, default='')
class Meta:
app_label = 'social'
db_table = 'igny8_social_posts'
```
**PK:** BigAutoField (integer) — inherits from SiteSectorBaseModel
#### SocialEngagement (social app)
```python
class SocialEngagement(models.Model):
"""
Engagement metrics for a published social post.
Fetched periodically from platform APIs.
"""
social_post = models.ForeignKey(
'social.SocialPost',
on_delete=models.CASCADE,
related_name='engagement_records'
)
likes = models.IntegerField(default=0)
comments = models.IntegerField(default=0)
shares = models.IntegerField(default=0)
impressions = models.IntegerField(default=0)
clicks = models.IntegerField(default=0)
engagement_rate = models.FloatField(default=0.0)
raw_data = models.JSONField(default=dict, help_text='Full platform API response')
fetched_at = models.DateTimeField(auto_now_add=True)
class Meta:
app_label = 'social'
db_table = 'igny8_social_engagement'
```
**PK:** BigAutoField (integer) — standard Django Model (not AccountBaseModel since engagement is tied to post)
### 3.2 New App Registration
Create social app:
- **App config:** `igny8_core/modules/social/apps.py` with `app_label = 'social'`
- **Add to INSTALLED_APPS** in `igny8_core/settings.py`
### 3.3 Migration
```
igny8_core/migrations/XXXX_add_social_models.py
```
**Operations:**
1. `CreateModel('SocialAccount', ...)` — with index on platform, status
2. `CreateModel('SocialPost', ...)` — with indexes on social_account, platform, status, scheduled_at
3. `CreateModel('SocialEngagement', ...)` — with index on social_post
### 3.4 API Endpoints
All endpoints under `/api/v1/social/`:
#### Account Management
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/social/accounts/connect/{platform}/` | Initiate OAuth flow for platform. Returns redirect URL. |
| GET | `/api/v1/social/accounts/callback/{platform}/` | OAuth callback handler. Exchanges code for tokens, creates SocialAccount. |
| GET | `/api/v1/social/accounts/?site_id=X` | List connected accounts for site. |
| DELETE | `/api/v1/social/accounts/{id}/` | Disconnect account (revoke tokens, soft-delete). |
| PUT | `/api/v1/social/accounts/{id}/settings/` | Update account settings (max_per_day, best_times, cooldown). |
#### Post Management
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/social/posts/generate/` | AI-generate posts for content across all connected platforms. Body: `{content_id, post_types: []}`. |
| POST | `/api/v1/social/posts/` | Create manual post. Body: `{social_account_id, text, ...}`. |
| GET | `/api/v1/social/posts/?site_id=X` | List posts with filters (platform, status, date range). |
| PUT | `/api/v1/social/posts/{id}/` | Edit draft/scheduled post. |
| POST | `/api/v1/social/posts/{id}/publish/` | Publish immediately. |
| POST | `/api/v1/social/posts/{id}/schedule/` | Schedule for later. Body: `{scheduled_at}`. |
| DELETE | `/api/v1/social/posts/{id}/` | Cancel/delete post. |
| POST | `/api/v1/social/posts/bulk-generate/` | Generate for multiple content. Body: `{content_ids: [int]}`. |
| POST | `/api/v1/social/posts/bulk-schedule/` | Schedule multiple posts. Body: `{post_ids: [int], auto_space: true}`. |
#### Calendar & Analytics
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/social/calendar/?site_id=X&month=YYYY-MM` | Calendar view — scheduled + published posts for month. |
| GET | `/api/v1/social/analytics/?site_id=X` | Aggregate analytics: per-platform performance, top posts. |
| GET | `/api/v1/social/analytics/posts/?site_id=X` | Per-post analytics with engagement breakdown. |
**Permissions:** All endpoints use `SiteSectorModelViewSet` permission patterns.
### 3.5 AI Function — GenerateSocialPostsFunction
**Registry key:** `generate_social_posts`
**Location:** `igny8_core/ai/functions/generate_social_posts.py`
```python
class GenerateSocialPostsFunction(BaseAIFunction):
"""
Generates platform-adapted social media posts from content.
One call produces posts for all requested platforms.
"""
function_name = 'generate_social_posts'
def validate(self, content_id, platforms=None, post_types=None, **kwargs):
# Verify content exists, has content_html
# Verify requested platforms have connected accounts
pass
def prepare(self, content_id, platforms=None, post_types=None, **kwargs):
# Load Content record
# Load cluster keywords for hashtag generation
# Load connected SocialAccount records for the site
# Determine applicable post_types per platform
pass
def build_prompt(self):
# Per platform, build adaptation instructions:
# - Platform tone + length limits
# - Content title, meta_description, key points from content_html
# - Hashtag generation instructions
# - CTA format
# - Thread instructions (Twitter if content > 280 chars summary)
pass
def parse_response(self, response):
# Parse per-platform posts:
# {platform: {text, hashtags[], cta, thread_posts?}}
pass
def save_output(self, parsed):
# Create SocialPost records per platform per post_type
# Attach UTM parameters
# Set status='draft' (default) or 'scheduled' if auto-publish
pass
```
### 3.6 OAuth Service
**Location:** `igny8_core/integration/oauth/`
```python
# base_oauth.py
class BaseOAuthService:
"""Base class for OAuth integrations. Handles token encryption/decryption."""
def encrypt_token(self, token):
"""Encrypt token using Django's Fernet key (same pattern as 02C GSC tokens)."""
pass
def decrypt_token(self, encrypted_token):
"""Decrypt stored token."""
pass
def get_authorization_url(self, site_id, account_id=None):
"""Generate OAuth authorization URL with state parameter."""
pass
def handle_callback(self, code, state):
"""Exchange authorization code for tokens, create/update SocialAccount."""
pass
def refresh_tokens(self, social_account):
"""Refresh expired access token using refresh_token."""
pass
# linkedin_oauth.py
class LinkedInOAuthService(BaseOAuthService):
"""LinkedIn Marketing API OAuth 2.0 (3-legged)."""
SCOPES = ['w_member_social', 'r_organization_social', 'w_organization_social']
# ...
# twitter_oauth.py
class TwitterOAuthService(BaseOAuthService):
"""Twitter/X OAuth 2.0 with PKCE."""
SCOPES = ['tweet.read', 'tweet.write', 'users.read']
# ...
# facebook_oauth.py
class FacebookOAuthService(BaseOAuthService):
"""Facebook Login with page selection."""
SCOPES = ['pages_manage_posts', 'pages_read_engagement']
# ...
# tiktok_oauth.py
class TikTokOAuthService(BaseOAuthService):
"""TikTok Content Posting API OAuth."""
# ...
```
### 3.7 Social Publisher Service
**Location:** `igny8_core/business/social_publisher.py`
```python
class SocialPublisherService:
"""
Handles actual posting to platform APIs.
Routes to platform-specific publishers.
"""
PUBLISHERS = {
'linkedin': LinkedInPublisher,
'twitter': TwitterPublisher,
'facebook': FacebookPublisher,
'instagram': InstagramPublisher,
'tiktok': TikTokPublisher,
}
def publish(self, social_post_id):
"""
Publish a SocialPost to its target platform.
1. Load SocialPost + SocialAccount
2. Decrypt access token
3. Route to platform-specific publisher
4. Update SocialPost with platform_post_id, platform_post_url, published_at
5. Set status='published' or 'failed'
"""
pass
```
### 3.8 Celery Tasks
**Location:** `igny8_core/tasks/social_tasks.py`
```python
@shared_task(name='publish_scheduled_posts')
def publish_scheduled_posts():
"""
Runs every minute. Finds SocialPost records with
status='scheduled' and scheduled_at <= now(). Publishes each.
"""
pass
@shared_task(name='refresh_social_tokens')
def refresh_social_tokens():
"""
Runs hourly. Refreshes tokens expiring within next 2 hours.
Updates SocialAccount.access_token, token_expiry.
Sets status='expired' if refresh fails.
"""
pass
@shared_task(name='fetch_engagement')
def fetch_engagement():
"""
Runs every 6 hours. Fetches engagement metrics from platform APIs
for all published posts in last 30 days.
Creates SocialEngagement records.
"""
pass
@shared_task(name='generate_social_posts_stage8')
def generate_social_posts_stage8(content_id):
"""
Triggered by pipeline after Stage 7 (publish).
Generates social posts for all connected platforms for the site.
"""
pass
```
**Beat Schedule Additions:**
| Task | Schedule | Notes |
|------|----------|-------|
| `publish_scheduled_posts` | Every minute | Publishes due scheduled posts |
| `refresh_social_tokens` | Hourly | Refreshes expiring OAuth tokens |
| `fetch_engagement` | Every 6 hours | Fetches engagement metrics for recent posts |
### 3.9 Image Resizing Service
**Location:** `igny8_core/business/social_image_resize.py`
```python
class SocialImageResizeService:
"""
Resizes images from the Images model to platform-specific dimensions.
Uses Pillow for image processing.
"""
PLATFORM_SIZES = {
'linkedin': (1200, 627),
'twitter': (1600, 900),
'facebook': (1200, 630),
'instagram_square': (1080, 1080),
'instagram_portrait': (1080, 1350),
'tiktok': (1080, 1920),
}
def resize(self, image_path, platform, variant='default'):
"""Resize image, return new file path."""
pass
```
---
## 4. IMPLEMENTATION STEPS
### Step 1: Create Social App
1. Create `igny8_core/modules/social/` directory with `__init__.py` and `apps.py`
2. Add `social` to `INSTALLED_APPS` in settings.py
3. Create models: SocialAccount, SocialPost, SocialEngagement
### Step 2: Migration
1. Create migration for 3 new models
2. Run migration
### Step 3: OAuth Infrastructure
1. Create `igny8_core/integration/oauth/` directory
2. Implement `BaseOAuthService` with token encryption/decryption
3. Implement platform-specific OAuth handlers (LinkedIn, Twitter, Facebook, TikTok)
4. Instagram OAuth routes through Facebook Business
### Step 4: AI Function
1. Implement `GenerateSocialPostsFunction` in `igny8_core/ai/functions/generate_social_posts.py`
2. Register `generate_social_posts` in `igny8_core/ai/registry.py`
### Step 5: Services
1. Implement `SocialPublisherService` in `igny8_core/business/social_publisher.py`
2. Implement platform-specific publishers (LinkedIn, Twitter, Facebook, Instagram, TikTok)
3. Implement `SocialImageResizeService` in `igny8_core/business/social_image_resize.py`
### Step 6: Pipeline Integration
Add Stage 8 trigger after Stage 7 (publish):
```python
# In pipeline after publish completes:
def post_publish(content_id):
site = content.site
# Check if social is enabled for this site
config = AutomationConfig.objects.get(site=site)
if config.settings.get('social_enabled'):
generate_social_posts_stage8.delay(content_id)
```
### Step 7: API Endpoints
1. Create `igny8_core/urls/social.py` with account, post, calendar, and analytics endpoints
2. Create views: `AccountConnectView`, `AccountCallbackView`, `AccountListView`
3. Create `PostGenerateView`, `PostViewSet`, `CalendarView`, `AnalyticsView`
4. Register URL patterns under `/api/v1/social/`
### Step 8: Celery Tasks
1. Implement 4 tasks in `igny8_core/tasks/social_tasks.py`
2. Add `publish_scheduled_posts`, `refresh_social_tokens`, `fetch_engagement` to beat schedule
### Step 9: Serializers & Admin
1. Create DRF serializers for SocialAccount (exclude encrypted tokens from response), SocialPost, SocialEngagement
2. Register models in Django admin
### Step 10: Credit Cost Configuration
Add to `CreditCostConfig` (billing app):
| operation_type | default_cost | description |
|---------------|-------------|-------------|
| `social_adaptation` | 1 | AI-generate post for one platform |
| `social_hashtag` | 0.5 | Hashtag generation for one post |
| `social_thread` | 2 | Twitter thread generation (2-10 tweets) |
| `social_carousel` | 5 | Carousel post generation |
| `social_image_resize` | 3-10 | Image resizing/generation per platform |
| `social_suite` | 15-25 | Full suite for all 5 platforms |
---
## 5. ACCEPTANCE CRITERIA
### OAuth Connections
- [ ] LinkedIn OAuth 2.0 connects and stores encrypted tokens
- [ ] Twitter/X OAuth 2.0 with PKCE connects successfully
- [ ] Facebook Login with page selection works, stores page_access_token
- [ ] Instagram connects via Facebook Business account
- [ ] TikTok OAuth connects and stores tokens
- [ ] Token refresh runs hourly, updates expired tokens
- [ ] Multi-account support: multiple accounts per platform per site
### Post Generation
- [ ] AI generates platform-adapted text for each connected platform
- [ ] LinkedIn posts use professional tone, 1,300 char limit
- [ ] Twitter posts respect 280 char limit, thread option for long content
- [ ] Facebook posts use conversational tone, optimal 500 char length
- [ ] Instagram captions include 20-30 hashtags, "link in bio" CTA
- [ ] TikTok posts use casual/Gen-Z tone, trending hook format
- [ ] UTM parameters appended to all shared URLs
### Post Types
- [ ] Announcement, Highlights, Quote Card, FAQ Snippet post types generated
- [ ] Post type selection respects platform compatibility
### Scheduling
- [ ] Posts can be scheduled for future time
- [ ] `publish_scheduled_posts` task runs every minute, publishes due posts
- [ ] Frequency caps enforced (max per day per platform)
- [ ] Cooldown period enforced between posts to same platform
- [ ] Calendar endpoint returns month view of scheduled/published posts
- [ ] Bulk scheduling auto-spaces posts across optimal times
### Engagement
- [ ] Engagement metrics fetched every 6 hours for recent posts
- [ ] SocialEngagement records created with likes, comments, shares, impressions, clicks
- [ ] Aggregate analytics endpoint returns per-platform performance + top posts
- [ ] Per-post analytics available with engagement breakdown
### Pipeline Integration
- [ ] Stage 8 triggers automatically after Stage 7 (publish)
- [ ] Social posts generated for all connected platforms
- [ ] auto_social_publish toggle controls immediate vs manual review
- [ ] Platform selection configurable via AutomationConfig.settings
### Images
- [ ] Images from pipeline resized to platform-specific dimensions
- [ ] media_urls populated on SocialPost records
---
## 6. CLAUDE CODE INSTRUCTIONS
### File Locations
```
igny8_core/
├── modules/
│ └── social/
│ ├── __init__.py
│ ├── apps.py # app_label = 'social'
│ └── models.py # SocialAccount, SocialPost, SocialEngagement
├── ai/
│ └── functions/
│ └── generate_social_posts.py # GenerateSocialPostsFunction
├── integration/
│ └── oauth/
│ ├── __init__.py
│ ├── base_oauth.py # BaseOAuthService
│ ├── linkedin_oauth.py
│ ├── twitter_oauth.py
│ ├── facebook_oauth.py
│ ├── instagram_oauth.py # Routes through Facebook
│ └── tiktok_oauth.py
├── business/
│ ├── social_publisher.py # SocialPublisherService + platform publishers
│ └── social_image_resize.py # SocialImageResizeService
├── tasks/
│ └── social_tasks.py # Celery tasks
├── urls/
│ └── social.py # Social endpoints
└── migrations/
└── XXXX_add_social_models.py
```
### Conventions
- **PKs:** BigAutoField (integer) — do NOT use UUIDs
- **Table prefix:** `igny8_` on all new tables
- **App label:** `social` (new app)
- **Celery app name:** `igny8_core`
- **URL pattern:** `/api/v1/social/...`
- **Permissions:** Use `SiteSectorModelViewSet` permission pattern
- **Token encryption:** Same Fernet pattern as 02C GSC tokens — NEVER expose raw tokens in API responses
- **AI functions:** Extend `BaseAIFunction`; register as `generate_social_posts`
- **Frontend:** `.tsx` files with Zustand stores
### Cross-References
| Doc | Relationship |
|-----|-------------|
| **01E** | Pipeline Stage 8 integration — hooks after Stage 7 publish |
| **02I** | Video creator shares social posting for video content; reuses SocialAccount OAuth for YouTube/TikTok |
| **02C** | GSC token encryption pattern reused for social OAuth tokens |
| **03A** | WP plugin standalone has share buttons — different from this (posting FROM IGNY8) |
| **04A** | Managed services include social media management as a service tier |
### Key Decisions
1. **New `social` app** — Separate from integration because social media is a distinct domain with its own models and business logic
2. **SocialEngagement extends models.Model, not SiteSectorBaseModel** — Engagement records are tied to posts, not directly to sites/sectors
3. **Denormalized `platform` on SocialPost** — Avoids join to SocialAccount for platform-based queries and filtering
4. **OAuth tokens encrypted at rest** — Same Fernet encryption as 02C; tokens never returned in API responses
5. **AutomationConfig.settings extension** — Social pipeline toggles added to existing settings JSONField rather than new model fields