@nextsparkjs/plugin-social-media-publisher 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +76 -0
- package/README.md +423 -0
- package/api/social/connect/callback/route.ts +669 -0
- package/api/social/connect/route.ts +327 -0
- package/api/social/disconnect/route.ts +187 -0
- package/api/social/publish/route.ts +402 -0
- package/docs/01-getting-started/01-introduction.md +471 -0
- package/docs/01-getting-started/02-installation.md +471 -0
- package/docs/01-getting-started/03-configuration.md +515 -0
- package/docs/02-core-features/01-oauth-integration.md +501 -0
- package/docs/02-core-features/02-publishing.md +527 -0
- package/docs/02-core-features/03-token-management.md +661 -0
- package/docs/02-core-features/04-audit-logging.md +646 -0
- package/docs/03-advanced-usage/01-provider-apis.md +764 -0
- package/docs/03-advanced-usage/02-custom-integrations.md +695 -0
- package/docs/03-advanced-usage/03-per-client-architecture.md +575 -0
- package/docs/04-use-cases/01-agency-management.md +661 -0
- package/docs/04-use-cases/02-content-publishing.md +668 -0
- package/docs/04-use-cases/03-analytics-reporting.md +748 -0
- package/entities/audit-logs/audit-logs.config.ts +150 -0
- package/lib/oauth-helper.ts +167 -0
- package/lib/providers/facebook.ts +672 -0
- package/lib/providers/index.ts +21 -0
- package/lib/providers/instagram.ts +791 -0
- package/lib/validation.ts +155 -0
- package/migrations/001_social_media_tables.sql +167 -0
- package/package.json +15 -0
- package/plugin.config.ts +81 -0
- package/tsconfig.json +47 -0
- package/types/social.types.ts +171 -0
package/.env.example
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# SOCIAL MEDIA PUBLISHER PLUGIN - ENVIRONMENT VARIABLES
|
|
3
|
+
# ============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Copy this file to .env and fill in your actual values
|
|
6
|
+
#
|
|
7
|
+
# ⚠️ NEVER commit .env to git - it contains secrets!
|
|
8
|
+
#
|
|
9
|
+
# ============================================================================
|
|
10
|
+
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# FACEBOOK/META OAUTH (Required for Instagram Business & Facebook Pages)
|
|
13
|
+
# ============================================================================
|
|
14
|
+
# Get from: https://developers.facebook.com/apps/
|
|
15
|
+
# App Dashboard → Settings → Basic → App ID & App Secret
|
|
16
|
+
#
|
|
17
|
+
# Required Permissions:
|
|
18
|
+
# - pages_show_list
|
|
19
|
+
# - pages_manage_posts
|
|
20
|
+
# - pages_read_engagement
|
|
21
|
+
# - read_insights
|
|
22
|
+
# - business_management
|
|
23
|
+
# - instagram_basic
|
|
24
|
+
# - instagram_content_publish
|
|
25
|
+
# - instagram_manage_comments
|
|
26
|
+
#
|
|
27
|
+
# Redirect URI: [YOUR_APP_URL]/api/v1/plugin/social-media-publisher/social/connect/callback
|
|
28
|
+
# Example: https://yourdomain.com/api/v1/plugin/social-media-publisher/social/connect/callback
|
|
29
|
+
|
|
30
|
+
FACEBOOK_CLIENT_ID="your-facebook-app-id"
|
|
31
|
+
FACEBOOK_CLIENT_SECRET="your-facebook-app-secret"
|
|
32
|
+
|
|
33
|
+
# Alternative names (both are supported):
|
|
34
|
+
# FACEBOOK_APP_ID="your-facebook-app-id"
|
|
35
|
+
# FACEBOOK_APP_SECRET="your-facebook-app-secret"
|
|
36
|
+
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# CRON JOB AUTHENTICATION (Required for scheduled publishing)
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Secret key to authenticate cron job requests
|
|
41
|
+
# Generate with: openssl rand -base64 32
|
|
42
|
+
#
|
|
43
|
+
# This is used by the scheduled publishing endpoint:
|
|
44
|
+
# POST /api/v1/cron/publish-scheduled
|
|
45
|
+
# Header: Authorization: Bearer <CRON_SECRET>
|
|
46
|
+
#
|
|
47
|
+
# Configure in your cron service (Vercel Cron, GitHub Actions, etc.)
|
|
48
|
+
|
|
49
|
+
CRON_SECRET="your-cron-secret-key-here"
|
|
50
|
+
|
|
51
|
+
# ============================================================================
|
|
52
|
+
# USAGE NOTES
|
|
53
|
+
# ============================================================================
|
|
54
|
+
#
|
|
55
|
+
# 1. Copy this file:
|
|
56
|
+
# cp .env.example .env
|
|
57
|
+
#
|
|
58
|
+
# 2. Create Facebook App:
|
|
59
|
+
# - Go to https://developers.facebook.com/apps/
|
|
60
|
+
# - Create new app → Type: Business
|
|
61
|
+
# - Add Facebook Login & Instagram API products
|
|
62
|
+
# - Configure OAuth Redirect URIs
|
|
63
|
+
# - Copy App ID & App Secret to this file
|
|
64
|
+
#
|
|
65
|
+
# 3. Generate CRON_SECRET:
|
|
66
|
+
# openssl rand -base64 32
|
|
67
|
+
#
|
|
68
|
+
# 4. Configure Vercel Cron (see vercel.json):
|
|
69
|
+
# - Add CRON_SECRET to Vercel Environment Variables
|
|
70
|
+
# - Cron runs every 5 minutes by default
|
|
71
|
+
#
|
|
72
|
+
# 5. Test scheduled publishing:
|
|
73
|
+
# curl -X POST http://localhost:5173/api/v1/cron/publish-scheduled \
|
|
74
|
+
# -H "Authorization: Bearer YOUR_CRON_SECRET"
|
|
75
|
+
#
|
|
76
|
+
# ============================================================================
|
package/README.md
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# Social Media Publisher Plugin
|
|
2
|
+
|
|
3
|
+
Multi-account social media publishing plugin for Instagram Business and Facebook Pages with OAuth integration and token encryption. Social accounts are managed per-client, allowing each client to have their own connected social media platforms.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Per-Client Multi-Account Support** - Each client can connect multiple Instagram Business or Facebook Page accounts
|
|
8
|
+
- ✅ **Secure Token Storage** - AES-256-GCM encryption for OAuth tokens
|
|
9
|
+
- ✅ **Auto Token Refresh** - Automatic refresh before expiration
|
|
10
|
+
- ✅ **Audit Logging** - Complete audit trail for all actions
|
|
11
|
+
- ✅ **Platform Support**:
|
|
12
|
+
- Instagram Business API (photos, videos, insights)
|
|
13
|
+
- Facebook Pages API (posts, photos, links, insights)
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
### Per-Client Social Platform Management
|
|
18
|
+
|
|
19
|
+
Social media accounts are managed as a **child entity** of clients (`social-platforms`), not as global user accounts. This means:
|
|
20
|
+
|
|
21
|
+
- Each client can have multiple connected social media platforms
|
|
22
|
+
- Social accounts belong to the client, not directly to the user
|
|
23
|
+
- Users manage social platforms within the client context
|
|
24
|
+
- OAuth connections are stored in `clients_social_platforms` table (child entity)
|
|
25
|
+
|
|
26
|
+
This architecture allows for better organization when managing social media for multiple clients or projects.
|
|
27
|
+
|
|
28
|
+
## Directory Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
contents/plugins/social-media-publisher/
|
|
32
|
+
├── plugin.config.ts # Plugin metadata
|
|
33
|
+
├── types/
|
|
34
|
+
│ └── social.types.ts # TypeScript interfaces
|
|
35
|
+
├── entities/
|
|
36
|
+
│ └── audit-logs/
|
|
37
|
+
│ └── config.ts # Immutable audit trail
|
|
38
|
+
├── lib/
|
|
39
|
+
│ ├── providers/
|
|
40
|
+
│ │ ├── facebook.ts # Facebook Graph API wrapper
|
|
41
|
+
│ │ ├── instagram.ts # Instagram Business API wrapper
|
|
42
|
+
│ │ └── index.ts # Exports
|
|
43
|
+
│ ├── oauth-helper.ts # OAuth flow utilities
|
|
44
|
+
│ └── validation.ts # Zod schemas
|
|
45
|
+
└── api/
|
|
46
|
+
└── social/
|
|
47
|
+
└── connect/
|
|
48
|
+
└── callback/route.ts # OAuth callback handler
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Note: Social platform accounts are managed through the `social-platforms` child entity in the theme, not in this plugin.
|
|
52
|
+
|
|
53
|
+
## Database Schema
|
|
54
|
+
|
|
55
|
+
### `clients_social_platforms` Table (Child Entity)
|
|
56
|
+
|
|
57
|
+
Stores OAuth-connected social media accounts as a child entity of clients.
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
CREATE TABLE "clients_social_platforms" (
|
|
61
|
+
id UUID PRIMARY KEY,
|
|
62
|
+
"parentId" UUID NOT NULL REFERENCES "clients"(id), -- Client owner
|
|
63
|
+
platform TEXT NOT NULL, -- 'instagram_business' | 'facebook_page'
|
|
64
|
+
"platformAccountId" TEXT,
|
|
65
|
+
"platformAccountName" TEXT NOT NULL,
|
|
66
|
+
"accessToken" TEXT NOT NULL, -- Encrypted (format: encrypted:iv:keyId)
|
|
67
|
+
"tokenExpiresAt" TIMESTAMPTZ NOT NULL,
|
|
68
|
+
permissions JSONB DEFAULT '[]',
|
|
69
|
+
"accountMetadata" JSONB DEFAULT '{}',
|
|
70
|
+
"isActive" BOOLEAN DEFAULT true,
|
|
71
|
+
"createdAt" TIMESTAMPTZ DEFAULT now(),
|
|
72
|
+
"updatedAt" TIMESTAMPTZ DEFAULT now(),
|
|
73
|
+
UNIQUE("parentId", "platformAccountId") WHERE "platformAccountId" IS NOT NULL
|
|
74
|
+
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Key Features:**
|
|
78
|
+
- ✅ Multiple accounts per client per platform
|
|
79
|
+
- ✅ Encrypted tokens (AES-256-GCM with format: encrypted:iv:keyId)
|
|
80
|
+
- ✅ Tracks which user connected the account (audit trail)
|
|
81
|
+
- ✅ Soft delete via `isActive` flag
|
|
82
|
+
- ✅ JSONB metadata for flexibility
|
|
83
|
+
- ✅ Unique constraint per client to prevent duplicate connections
|
|
84
|
+
|
|
85
|
+
### `audit_logs` Table
|
|
86
|
+
|
|
87
|
+
Immutable audit trail for compliance.
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
CREATE TABLE "audit_logs" (
|
|
91
|
+
id UUID PRIMARY KEY,
|
|
92
|
+
"userId" TEXT NOT NULL REFERENCES "users"(id),
|
|
93
|
+
"accountId" UUID REFERENCES "clients_social_platforms"(id),
|
|
94
|
+
action TEXT NOT NULL, -- 'account_connected', 'post_published', etc.
|
|
95
|
+
details JSONB DEFAULT '{}',
|
|
96
|
+
"ipAddress" TEXT,
|
|
97
|
+
"userAgent" TEXT,
|
|
98
|
+
"createdAt" TIMESTAMPTZ DEFAULT now()
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## API Endpoints
|
|
103
|
+
|
|
104
|
+
### 1. OAuth Callback (Connect Social Account)
|
|
105
|
+
|
|
106
|
+
```http
|
|
107
|
+
GET /api/v1/plugin/social-media-publisher/social/connect/callback
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This endpoint receives the OAuth redirect from Facebook/Instagram and:
|
|
111
|
+
1. Validates the authorization code
|
|
112
|
+
2. Exchanges code for access token
|
|
113
|
+
3. Fetches connected accounts (Instagram Business or Facebook Pages)
|
|
114
|
+
4. Encrypts tokens and stores them in `clients_social_platforms` table
|
|
115
|
+
5. Returns HTML page that sends postMessage to opener window
|
|
116
|
+
|
|
117
|
+
**Query Parameters:**
|
|
118
|
+
```
|
|
119
|
+
code: Authorization code from Facebook
|
|
120
|
+
state: CSRF protection (format: {randomState}&platform={platform}&clientId={clientId})
|
|
121
|
+
error: (optional) Error if user denied permission
|
|
122
|
+
error_description: (optional) Error description
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Success Response:**
|
|
126
|
+
Returns HTML page with:
|
|
127
|
+
- Success message showing number of connected accounts
|
|
128
|
+
- JavaScript that sends postMessage to parent window
|
|
129
|
+
- Auto-closes popup after 2 seconds
|
|
130
|
+
|
|
131
|
+
**Popup Message:**
|
|
132
|
+
```javascript
|
|
133
|
+
{
|
|
134
|
+
type: 'oauth-success',
|
|
135
|
+
platform: 'instagram_business',
|
|
136
|
+
connectedCount: 2
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2. Initiate OAuth Flow
|
|
141
|
+
|
|
142
|
+
```http
|
|
143
|
+
GET /api/v1/plugin/social-media-publisher/social/connect
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Query Parameters:**
|
|
147
|
+
```
|
|
148
|
+
platform: 'instagram_business' | 'facebook_page'
|
|
149
|
+
clientId: UUID of the client to connect accounts to
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This endpoint generates the OAuth authorization URL and redirects the user to Facebook/Instagram for authorization. The state parameter includes the clientId to maintain context during the OAuth flow.
|
|
153
|
+
|
|
154
|
+
## Usage in Theme
|
|
155
|
+
|
|
156
|
+
### Connecting Social Accounts (Per-Client)
|
|
157
|
+
|
|
158
|
+
Social accounts are managed within the client context. The OAuth flow is initiated from the client's social platforms page:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
'use client'
|
|
162
|
+
|
|
163
|
+
import { useRouter } from 'next/navigation'
|
|
164
|
+
|
|
165
|
+
export function SocialPlatformOAuthForm({ clientId }: { clientId: string }) {
|
|
166
|
+
const router = useRouter()
|
|
167
|
+
|
|
168
|
+
const handleConnect = (platform: 'instagram_business' | 'facebook_page') => {
|
|
169
|
+
// Build OAuth URL with clientId in state
|
|
170
|
+
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || window.location.origin
|
|
171
|
+
const oauthUrl = `${baseUrl}/api/v1/plugin/social-media-publisher/social/connect?platform=${platform}&clientId=${clientId}`
|
|
172
|
+
|
|
173
|
+
// Open OAuth popup
|
|
174
|
+
const popup = window.open(
|
|
175
|
+
oauthUrl,
|
|
176
|
+
'oauth-popup',
|
|
177
|
+
'width=600,height=700'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// Listen for success message from popup
|
|
181
|
+
window.addEventListener('message', (event) => {
|
|
182
|
+
if (event.origin !== window.location.origin) return
|
|
183
|
+
|
|
184
|
+
if (event.data.type === 'oauth-success') {
|
|
185
|
+
console.log(`Connected ${event.data.connectedCount} ${event.data.platform} account(s)`)
|
|
186
|
+
router.refresh() // Refresh to show newly connected accounts
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div>
|
|
193
|
+
<button onClick={() => handleConnect('instagram_business')}>
|
|
194
|
+
Connect Instagram Business
|
|
195
|
+
</button>
|
|
196
|
+
<button onClick={() => handleConnect('facebook_page')}>
|
|
197
|
+
Connect Facebook Page
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Fetching Connected Accounts
|
|
205
|
+
|
|
206
|
+
Connected accounts are fetched through the standard entity API as a child entity:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Server Component
|
|
210
|
+
import { query } from '@/core/lib/db'
|
|
211
|
+
|
|
212
|
+
async function getClientSocialPlatforms(clientId: string) {
|
|
213
|
+
const result = await query(
|
|
214
|
+
`SELECT * FROM "clients_social_platforms"
|
|
215
|
+
WHERE "parentId" = $1 AND "isActive" = true`,
|
|
216
|
+
[clientId]
|
|
217
|
+
)
|
|
218
|
+
return result.rows
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Or use the dynamic entity API:
|
|
222
|
+
// GET /api/v1/entity/clients/{clientId}/social-platforms
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Publishing to Social Media
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { FacebookAPI, InstagramAPI } from '@/contents/plugins/social-media-publisher/lib/providers'
|
|
229
|
+
import { TokenEncryption } from '@/core/lib/oauth/encryption'
|
|
230
|
+
|
|
231
|
+
async function publishToInstagram(account: any, imageUrl: string, caption: string) {
|
|
232
|
+
// Decrypt token
|
|
233
|
+
const [encrypted, iv, keyId] = account.accessToken.split(':')
|
|
234
|
+
const decryptedToken = await TokenEncryption.decrypt(encrypted, iv, keyId)
|
|
235
|
+
|
|
236
|
+
// Publish to Instagram
|
|
237
|
+
const result = await InstagramAPI.publishPhoto({
|
|
238
|
+
igAccountId: account.platformAccountId,
|
|
239
|
+
accessToken: decryptedToken,
|
|
240
|
+
imageUrl,
|
|
241
|
+
caption
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
if (result.success) {
|
|
245
|
+
console.log(`Posted: ${result.postUrl}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Environment Variables
|
|
253
|
+
|
|
254
|
+
```env
|
|
255
|
+
# Facebook App Credentials (same as Better Auth)
|
|
256
|
+
FACEBOOK_CLIENT_ID=your_app_id
|
|
257
|
+
FACEBOOK_CLIENT_SECRET=your_app_secret
|
|
258
|
+
|
|
259
|
+
# OAuth Encryption Key (32 bytes hex)
|
|
260
|
+
OAUTH_ENCRYPTION_KEY=generate_with_crypto_random_bytes_32
|
|
261
|
+
|
|
262
|
+
# App URL
|
|
263
|
+
NEXT_PUBLIC_APP_URL=https://yourdomain.com
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Generate Encryption Key
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## OAuth Scopes
|
|
273
|
+
|
|
274
|
+
### Facebook Pages
|
|
275
|
+
```typescript
|
|
276
|
+
[
|
|
277
|
+
'pages_show_list', // List user's pages
|
|
278
|
+
'pages_manage_posts', // Create/edit posts
|
|
279
|
+
'pages_read_engagement', // Read likes/comments
|
|
280
|
+
'read_insights' // Read analytics
|
|
281
|
+
]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Instagram Business
|
|
285
|
+
```typescript
|
|
286
|
+
[
|
|
287
|
+
'pages_show_list', // Required to get IG account from page
|
|
288
|
+
'instagram_basic', // Read profile info
|
|
289
|
+
'instagram_content_publish', // Publish posts
|
|
290
|
+
'instagram_manage_insights' // Read analytics
|
|
291
|
+
]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Security Features
|
|
295
|
+
|
|
296
|
+
### Token Encryption
|
|
297
|
+
|
|
298
|
+
All OAuth tokens are encrypted using AES-256-GCM before storage:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
const encrypted = await TokenEncryption.encrypt(accessToken)
|
|
302
|
+
// Returns: { encrypted: string, iv: string, keyId: string }
|
|
303
|
+
// Stored as: "encrypted:iv:keyId"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Row-Level Security (RLS)
|
|
307
|
+
|
|
308
|
+
```sql
|
|
309
|
+
-- Users can only access social platforms for clients they own
|
|
310
|
+
CREATE POLICY "clients_social_platforms_select_own"
|
|
311
|
+
ON "clients_social_platforms" FOR SELECT
|
|
312
|
+
USING (
|
|
313
|
+
"parentId" IN (
|
|
314
|
+
SELECT id FROM "clients"
|
|
315
|
+
WHERE "userId" = current_setting('app.current_user_id', true)
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Audit Trail
|
|
321
|
+
|
|
322
|
+
Every action creates an immutable audit log:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
{
|
|
326
|
+
userId: 'user_id',
|
|
327
|
+
accountId: 'account_id',
|
|
328
|
+
action: 'post_published',
|
|
329
|
+
details: {
|
|
330
|
+
platform: 'instagram_business',
|
|
331
|
+
success: true,
|
|
332
|
+
postId: '17899618652010220'
|
|
333
|
+
},
|
|
334
|
+
ipAddress: '192.168.1.1',
|
|
335
|
+
userAgent: 'Mozilla/5.0...',
|
|
336
|
+
createdAt: '2025-10-20T10:30:00Z'
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Database Setup
|
|
341
|
+
|
|
342
|
+
The `clients_social_platforms` table is created as part of the social-platforms child entity migration in the theme. The `audit_logs` table is created as part of the plugin's entity configuration.
|
|
343
|
+
|
|
344
|
+
## Dependencies
|
|
345
|
+
|
|
346
|
+
### Core
|
|
347
|
+
- `core/lib/oauth/encryption.ts` - Token encryption
|
|
348
|
+
- `core/lib/oauth/token-refresh.ts` - Token refresh
|
|
349
|
+
- `core/lib/api/auth/dual-auth.ts` - Authentication
|
|
350
|
+
|
|
351
|
+
### External
|
|
352
|
+
- Facebook Graph API v18.0
|
|
353
|
+
- Instagram Business API (via Graph API)
|
|
354
|
+
|
|
355
|
+
## Testing
|
|
356
|
+
|
|
357
|
+
### Manual Testing Checklist
|
|
358
|
+
|
|
359
|
+
- [ ] Navigate to client detail page
|
|
360
|
+
- [ ] Click "Add Social Platform" button
|
|
361
|
+
- [ ] Select Instagram Business platform
|
|
362
|
+
- [ ] Complete OAuth flow in popup
|
|
363
|
+
- [ ] Verify popup closes and accounts appear in list
|
|
364
|
+
- [ ] Connect Facebook Page for same client
|
|
365
|
+
- [ ] Verify tokens are encrypted in `clients_social_platforms` table (format: encrypted:iv:keyId)
|
|
366
|
+
- [ ] Verify audit logs are created with correct accountId
|
|
367
|
+
- [ ] Verify RLS policies (user can only see their own clients' social platforms)
|
|
368
|
+
- [ ] Test connecting duplicate account (should update existing record)
|
|
369
|
+
|
|
370
|
+
### Unit Tests (TODO)
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
npm test contents/plugins/social-media-publisher
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Troubleshooting
|
|
377
|
+
|
|
378
|
+
### "No Instagram Business Accounts found"
|
|
379
|
+
|
|
380
|
+
**Cause**: Your Facebook Pages don't have linked Instagram Business Accounts.
|
|
381
|
+
|
|
382
|
+
**Solution**:
|
|
383
|
+
1. Go to Facebook Page settings
|
|
384
|
+
2. Navigate to Instagram section
|
|
385
|
+
3. Connect an Instagram Business Account
|
|
386
|
+
4. Try again
|
|
387
|
+
|
|
388
|
+
### "Token exchange failed"
|
|
389
|
+
|
|
390
|
+
**Cause**: Invalid OAuth credentials or redirect URI mismatch.
|
|
391
|
+
|
|
392
|
+
**Solution**:
|
|
393
|
+
1. Verify `FACEBOOK_CLIENT_ID` and `FACEBOOK_CLIENT_SECRET`
|
|
394
|
+
2. Check redirect URI in Facebook App settings matches your callback URL
|
|
395
|
+
3. Ensure app is not in Development Mode (for production)
|
|
396
|
+
|
|
397
|
+
### "Encryption key validation failed"
|
|
398
|
+
|
|
399
|
+
**Cause**: Missing or invalid `OAUTH_ENCRYPTION_KEY`.
|
|
400
|
+
|
|
401
|
+
**Solution**:
|
|
402
|
+
```bash
|
|
403
|
+
# Generate new key
|
|
404
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
405
|
+
|
|
406
|
+
# Add to .env
|
|
407
|
+
OAUTH_ENCRYPTION_KEY=<generated_key>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Roadmap
|
|
411
|
+
|
|
412
|
+
- [ ] Token auto-refresh cron job
|
|
413
|
+
- [ ] Video publishing support
|
|
414
|
+
- [ ] Carousel posts (multiple images)
|
|
415
|
+
- [ ] Story publishing
|
|
416
|
+
- [ ] Scheduled posts
|
|
417
|
+
- [ ] Analytics dashboard
|
|
418
|
+
- [ ] Bulk publishing
|
|
419
|
+
- [ ] Cross-posting (publish to multiple accounts at once)
|
|
420
|
+
|
|
421
|
+
## License
|
|
422
|
+
|
|
423
|
+
Same as parent project.
|