783 lines
29 KiB
Markdown
783 lines
29 KiB
Markdown
# 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
|