@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 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.