@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social Media Publisher - Validation Schemas
|
|
3
|
+
*
|
|
4
|
+
* Zod schemas for request validation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// PUBLISH REQUEST SCHEMAS
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
export const PublishPhotoSchema = z.object({
|
|
14
|
+
accountId: z.string().uuid('Invalid account ID'),
|
|
15
|
+
imageUrl: z.string().url('Invalid image URL').nullish(), // ✅ Accepts null/undefined - Facebook allows text-only
|
|
16
|
+
imageUrls: z.array(z.string().url('Invalid image URL')).optional(), // For carousels
|
|
17
|
+
caption: z.string().max(2200).optional(),
|
|
18
|
+
platform: z.enum(['instagram_business', 'facebook_page']),
|
|
19
|
+
}).refine(data => {
|
|
20
|
+
// Instagram requires at least one image
|
|
21
|
+
if (data.platform === 'instagram_business') {
|
|
22
|
+
return (data.imageUrl || (data.imageUrls && data.imageUrls.length > 0))
|
|
23
|
+
}
|
|
24
|
+
return true
|
|
25
|
+
}, { message: 'Instagram requires at least one image' }).refine(data => {
|
|
26
|
+
// Instagram allows maximum 10 images per carousel
|
|
27
|
+
if (data.platform === 'instagram_business' && data.imageUrls && data.imageUrls.length > 10) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
return true
|
|
31
|
+
}, { message: 'Instagram allows maximum 10 images per carousel' })
|
|
32
|
+
|
|
33
|
+
export const PublishTextSchema = z.object({
|
|
34
|
+
accountId: z.string().uuid('Invalid account ID'),
|
|
35
|
+
message: z.string().min(1, 'Message is required').max(5000),
|
|
36
|
+
platform: z.literal('facebook_page'), // Only Facebook supports text-only posts
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export const PublishLinkSchema = z.object({
|
|
40
|
+
accountId: z.string().uuid('Invalid account ID'),
|
|
41
|
+
message: z.string().max(5000).optional(),
|
|
42
|
+
linkUrl: z.string().url('Invalid link URL'),
|
|
43
|
+
platform: z.literal('facebook_page'),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// CONNECT ACCOUNT SCHEMAS
|
|
48
|
+
// ============================================
|
|
49
|
+
|
|
50
|
+
export const ConnectAccountSchema = z.object({
|
|
51
|
+
code: z.string().min(1, 'Authorization code is required'),
|
|
52
|
+
state: z.string().min(1, 'State parameter is required'),
|
|
53
|
+
platform: z.enum(['instagram_business', 'facebook_page']),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// DISCONNECT ACCOUNT SCHEMAS
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
export const DisconnectAccountSchema = z.object({
|
|
61
|
+
accountId: z.string().uuid('Invalid account ID'),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// ============================================
|
|
65
|
+
// VALIDATION HELPERS
|
|
66
|
+
// ============================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate image URL requirements
|
|
70
|
+
*/
|
|
71
|
+
export function validateImageUrl(url: string): {
|
|
72
|
+
valid: boolean
|
|
73
|
+
error?: string
|
|
74
|
+
} {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = new URL(url)
|
|
77
|
+
|
|
78
|
+
// Must be HTTPS
|
|
79
|
+
if (parsed.protocol !== 'https:') {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: 'Image URL must use HTTPS protocol',
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Must not be localhost
|
|
87
|
+
if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
error: 'Image URL cannot be localhost',
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check file extension
|
|
95
|
+
const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp']
|
|
96
|
+
const hasValidExtension = validExtensions.some(ext =>
|
|
97
|
+
parsed.pathname.toLowerCase().endsWith(ext)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if (!hasValidExtension) {
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
error: `Image must be one of: ${validExtensions.join(', ')}`,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { valid: true }
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
error: 'Invalid URL format',
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validate caption length for platform
|
|
118
|
+
*/
|
|
119
|
+
export function validateCaption(
|
|
120
|
+
caption: string,
|
|
121
|
+
platform: 'instagram_business' | 'facebook_page'
|
|
122
|
+
): {
|
|
123
|
+
valid: boolean
|
|
124
|
+
error?: string
|
|
125
|
+
} {
|
|
126
|
+
const maxLengths = {
|
|
127
|
+
instagram_business: 2200,
|
|
128
|
+
facebook_page: 63206,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const maxLength = maxLengths[platform]
|
|
132
|
+
|
|
133
|
+
if (caption.length > maxLength) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: `Caption exceeds maximum length of ${maxLength} characters for ${platform}`,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { valid: true }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if a platform requires an image to publish
|
|
145
|
+
* Instagram: Always requires image/video
|
|
146
|
+
* Facebook: Allows text-only posts
|
|
147
|
+
*/
|
|
148
|
+
export function platformRequiresImage(platform: string): boolean {
|
|
149
|
+
const platformsRequiringImage = [
|
|
150
|
+
'instagram_business',
|
|
151
|
+
'tiktok',
|
|
152
|
+
'pinterest',
|
|
153
|
+
]
|
|
154
|
+
return platformsRequiringImage.includes(platform)
|
|
155
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
-- Migration: 009_social_media_publisher.sql
|
|
2
|
+
-- Description: Tables for social media publishing plugin
|
|
3
|
+
-- Date: 2025-10-20
|
|
4
|
+
-- Plugin: social-media-publisher
|
|
5
|
+
|
|
6
|
+
-- ============================================
|
|
7
|
+
-- TABLES
|
|
8
|
+
-- ============================================
|
|
9
|
+
|
|
10
|
+
-- Social Accounts Table
|
|
11
|
+
-- Stores OAuth-connected social media accounts for publishing
|
|
12
|
+
-- Supports multiple accounts per platform per user
|
|
13
|
+
CREATE TABLE IF NOT EXISTS "social_accounts" (
|
|
14
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
15
|
+
"userId" TEXT NOT NULL REFERENCES "users"(id) ON DELETE CASCADE,
|
|
16
|
+
platform TEXT NOT NULL CHECK (platform IN ('instagram_business', 'facebook_page')),
|
|
17
|
+
"platformAccountId" TEXT NOT NULL,
|
|
18
|
+
"username" TEXT NOT NULL,
|
|
19
|
+
"accessToken" TEXT NOT NULL, -- Encrypted (format: encrypted:iv:keyId)
|
|
20
|
+
"tokenExpiresAt" TIMESTAMPTZ NOT NULL,
|
|
21
|
+
permissions JSONB DEFAULT '[]'::jsonb,
|
|
22
|
+
"accountMetadata" JSONB DEFAULT '{}'::jsonb,
|
|
23
|
+
"isActive" BOOLEAN DEFAULT true,
|
|
24
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
25
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
26
|
+
|
|
27
|
+
-- Constraints
|
|
28
|
+
UNIQUE("platformAccountId") -- Prevent same account connected multiple times
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- Audit Logs Table
|
|
32
|
+
-- Tracks all actions performed through social media plugin
|
|
33
|
+
-- Note: accountId references clients_social_platforms.id (not enforced with FK to preserve historical logs)
|
|
34
|
+
CREATE TABLE IF NOT EXISTS "audit_logs" (
|
|
35
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
36
|
+
"userId" TEXT NOT NULL REFERENCES "users"(id) ON DELETE CASCADE,
|
|
37
|
+
"accountId" UUID, -- References clients_social_platforms.id (nullable, no FK to preserve historical logs)
|
|
38
|
+
action TEXT NOT NULL CHECK (action IN (
|
|
39
|
+
'account_connected',
|
|
40
|
+
'account_disconnected',
|
|
41
|
+
'post_published',
|
|
42
|
+
'post_failed',
|
|
43
|
+
'token_refreshed',
|
|
44
|
+
'token_refresh_failed'
|
|
45
|
+
)),
|
|
46
|
+
details JSONB DEFAULT '{}'::jsonb,
|
|
47
|
+
"ipAddress" TEXT,
|
|
48
|
+
"userAgent" TEXT,
|
|
49
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- ============================================
|
|
53
|
+
-- INDEXES
|
|
54
|
+
-- ============================================
|
|
55
|
+
|
|
56
|
+
-- Social Accounts indexes
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_social_accounts_user_platform
|
|
58
|
+
ON "social_accounts"("userId", platform);
|
|
59
|
+
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_social_accounts_platform_id_unique
|
|
61
|
+
ON "social_accounts"("platformAccountId");
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_social_accounts_active
|
|
64
|
+
ON "social_accounts"("isActive");
|
|
65
|
+
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_social_accounts_token_expiry
|
|
67
|
+
ON "social_accounts"("tokenExpiresAt");
|
|
68
|
+
|
|
69
|
+
-- Audit Logs indexes
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_created
|
|
71
|
+
ON "audit_logs"("userId", "createdAt");
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_account_action
|
|
74
|
+
ON "audit_logs"("accountId", action);
|
|
75
|
+
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_action_created
|
|
77
|
+
ON "audit_logs"(action, "createdAt");
|
|
78
|
+
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_desc
|
|
80
|
+
ON "audit_logs"("createdAt" DESC);
|
|
81
|
+
|
|
82
|
+
-- ============================================
|
|
83
|
+
-- RLS (Enable)
|
|
84
|
+
-- ============================================
|
|
85
|
+
|
|
86
|
+
ALTER TABLE "social_accounts" ENABLE ROW LEVEL SECURITY;
|
|
87
|
+
ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;
|
|
88
|
+
|
|
89
|
+
-- ============================================
|
|
90
|
+
-- POLICIES
|
|
91
|
+
-- ============================================
|
|
92
|
+
|
|
93
|
+
-- Social Accounts Policies
|
|
94
|
+
-- Users can only see and manage their own accounts
|
|
95
|
+
CREATE POLICY "social_accounts_select_own"
|
|
96
|
+
ON "social_accounts" FOR SELECT
|
|
97
|
+
USING ("userId" = current_setting('app.current_user_id', true));
|
|
98
|
+
|
|
99
|
+
CREATE POLICY "social_accounts_insert_own"
|
|
100
|
+
ON "social_accounts" FOR INSERT
|
|
101
|
+
WITH CHECK ("userId" = current_setting('app.current_user_id', true));
|
|
102
|
+
|
|
103
|
+
CREATE POLICY "social_accounts_update_own"
|
|
104
|
+
ON "social_accounts" FOR UPDATE
|
|
105
|
+
USING ("userId" = current_setting('app.current_user_id', true));
|
|
106
|
+
|
|
107
|
+
CREATE POLICY "social_accounts_delete_own"
|
|
108
|
+
ON "social_accounts" FOR DELETE
|
|
109
|
+
USING ("userId" = current_setting('app.current_user_id', true));
|
|
110
|
+
|
|
111
|
+
-- Audit Logs Policies
|
|
112
|
+
-- Users can only view their own audit logs
|
|
113
|
+
CREATE POLICY "audit_logs_select_own"
|
|
114
|
+
ON "audit_logs" FOR SELECT
|
|
115
|
+
USING ("userId" = current_setting('app.current_user_id', true));
|
|
116
|
+
|
|
117
|
+
-- Only system can insert audit logs
|
|
118
|
+
CREATE POLICY "audit_logs_insert_system"
|
|
119
|
+
ON "audit_logs" FOR INSERT
|
|
120
|
+
WITH CHECK (true); -- System role bypasses RLS
|
|
121
|
+
|
|
122
|
+
-- Audit logs are immutable (no updates)
|
|
123
|
+
-- No update policy = no one can update
|
|
124
|
+
|
|
125
|
+
-- Only admins can delete old audit logs
|
|
126
|
+
CREATE POLICY "audit_logs_delete_admin"
|
|
127
|
+
ON "audit_logs" FOR DELETE
|
|
128
|
+
USING (current_setting('app.current_user_role', true) = 'admin');
|
|
129
|
+
|
|
130
|
+
-- ============================================
|
|
131
|
+
-- TRIGGERS (updatedAt)
|
|
132
|
+
-- ============================================
|
|
133
|
+
|
|
134
|
+
DROP TRIGGER IF EXISTS social_accounts_set_updated_at ON "social_accounts";
|
|
135
|
+
CREATE TRIGGER social_accounts_set_updated_at
|
|
136
|
+
BEFORE UPDATE ON "social_accounts"
|
|
137
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
138
|
+
|
|
139
|
+
-- Note: audit_logs doesn't need updatedAt trigger (immutable)
|
|
140
|
+
|
|
141
|
+
-- ============================================
|
|
142
|
+
-- COMMENTS (Documentation)
|
|
143
|
+
-- ============================================
|
|
144
|
+
|
|
145
|
+
COMMENT ON TABLE "social_accounts" IS
|
|
146
|
+
'OAuth-connected social media accounts for publishing (Instagram Business & Facebook Pages)';
|
|
147
|
+
|
|
148
|
+
COMMENT ON COLUMN "social_accounts"."accessToken" IS
|
|
149
|
+
'Encrypted OAuth access token (format: encrypted:iv:keyId using AES-256-GCM)';
|
|
150
|
+
|
|
151
|
+
COMMENT ON COLUMN "social_accounts"."platformAccountId" IS
|
|
152
|
+
'Instagram Business Account ID or Facebook Page ID from platform';
|
|
153
|
+
|
|
154
|
+
COMMENT ON COLUMN "social_accounts"."permissions" IS
|
|
155
|
+
'Array of granted OAuth scopes (e.g., ["instagram_business_basic", "instagram_business_content_publish"])';
|
|
156
|
+
|
|
157
|
+
COMMENT ON COLUMN "social_accounts"."accountMetadata" IS
|
|
158
|
+
'Platform-specific metadata: profile picture URL, follower count, linked page info, etc.';
|
|
159
|
+
|
|
160
|
+
COMMENT ON TABLE "audit_logs" IS
|
|
161
|
+
'Immutable audit trail for all social media actions (security & compliance)';
|
|
162
|
+
|
|
163
|
+
COMMENT ON COLUMN "audit_logs"."accountId" IS
|
|
164
|
+
'References social platform account ID (clients_social_platforms.id). Nullable to preserve historical logs even after account deletion.';
|
|
165
|
+
|
|
166
|
+
COMMENT ON COLUMN "audit_logs".details IS
|
|
167
|
+
'Action details: platform, success status, error messages, post IDs, etc.';
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextsparkjs/plugin-social-media-publisher",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "./plugin.config.ts",
|
|
6
|
+
"requiredPlugins": [],
|
|
7
|
+
"dependencies": {},
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@nextsparkjs/core": "workspace:*",
|
|
10
|
+
"next": "^15.0.0",
|
|
11
|
+
"react": "^19.0.0",
|
|
12
|
+
"react-dom": "^19.0.0",
|
|
13
|
+
"zod": "^4.0.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/plugin.config.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social Media Publisher Plugin Configuration
|
|
3
|
+
*
|
|
4
|
+
* Enables publishing to Instagram Business & Facebook Pages
|
|
5
|
+
* with OAuth token management and multi-account support
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PluginConfig } from '@nextsparkjs/core/types/plugin'
|
|
9
|
+
|
|
10
|
+
// OAuth Providers configuration (plugin-specific metadata)
|
|
11
|
+
const OAUTH_PROVIDERS = {
|
|
12
|
+
facebook: {
|
|
13
|
+
name: 'Facebook',
|
|
14
|
+
authEndpoint: 'https://www.facebook.com/v18.0/dialog/oauth',
|
|
15
|
+
tokenEndpoint: 'https://graph.facebook.com/v18.0/oauth/access_token',
|
|
16
|
+
apiVersion: 'v18.0',
|
|
17
|
+
scopes: {
|
|
18
|
+
minimal: ['email', 'public_profile'],
|
|
19
|
+
publishing: [
|
|
20
|
+
'pages_show_list',
|
|
21
|
+
'pages_manage_posts',
|
|
22
|
+
'pages_read_engagement'
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
instagram: {
|
|
27
|
+
name: 'Instagram Business',
|
|
28
|
+
authEndpoint: 'https://www.facebook.com/v18.0/dialog/oauth', // Uses Facebook OAuth
|
|
29
|
+
tokenEndpoint: 'https://graph.facebook.com/v18.0/oauth/access_token',
|
|
30
|
+
apiVersion: 'v18.0',
|
|
31
|
+
scopes: {
|
|
32
|
+
minimal: ['instagram_basic'],
|
|
33
|
+
publishing: [
|
|
34
|
+
'instagram_basic',
|
|
35
|
+
'instagram_content_publish',
|
|
36
|
+
'instagram_manage_insights'
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} as const
|
|
41
|
+
|
|
42
|
+
// Feature flags (plugin-specific metadata)
|
|
43
|
+
const PLUGIN_FEATURES = {
|
|
44
|
+
multiAccountSupport: true,
|
|
45
|
+
tokenAutoRefresh: true,
|
|
46
|
+
auditLogging: true,
|
|
47
|
+
permissionValidation: true
|
|
48
|
+
} as const
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Social Media Publisher Plugin Configuration
|
|
52
|
+
* Follows PluginConfig interface for registry compatibility
|
|
53
|
+
*/
|
|
54
|
+
export const socialMediaPublisherPluginConfig: PluginConfig = {
|
|
55
|
+
name: 'social-media-publisher',
|
|
56
|
+
displayName: 'Social Media Publisher',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
description: 'Publish content to Instagram Business & Facebook Pages with OAuth integration',
|
|
59
|
+
enabled: true,
|
|
60
|
+
dependencies: [], // No plugin dependencies (uses core OAuth infrastructure)
|
|
61
|
+
|
|
62
|
+
// Plugin API - exports metadata for themes/plugins to use
|
|
63
|
+
api: {
|
|
64
|
+
providers: OAUTH_PROVIDERS,
|
|
65
|
+
features: PLUGIN_FEATURES,
|
|
66
|
+
entities: ['audit-logs']
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Plugin lifecycle hooks
|
|
70
|
+
hooks: {
|
|
71
|
+
onLoad: async () => {
|
|
72
|
+
console.log('[Social Media Publisher] Plugin loaded - OAuth publishing ready')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Default export for compatibility
|
|
78
|
+
export default socialMediaPublisherPluginConfig
|
|
79
|
+
|
|
80
|
+
// Type exports
|
|
81
|
+
export type SocialMediaPublisherConfig = typeof socialMediaPublisherPluginConfig
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"baseUrl": ".",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@/*": ["../../../*"],
|
|
9
|
+
"@/core/*": ["../../../core/*"],
|
|
10
|
+
"@/contents/*": ["../../../contents/*"],
|
|
11
|
+
"~/*": ["./*"]
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"watchOptions": {
|
|
15
|
+
"watchFile": "useFsEvents",
|
|
16
|
+
"watchDirectory": "useFsEvents",
|
|
17
|
+
"fallbackPolling": "dynamicPriority",
|
|
18
|
+
"synchronousWatchDirectory": false,
|
|
19
|
+
"excludeDirectories": [
|
|
20
|
+
"**/node_modules",
|
|
21
|
+
"**/.next",
|
|
22
|
+
"**/dist",
|
|
23
|
+
"**/build",
|
|
24
|
+
"**/.turbo",
|
|
25
|
+
"**/coverage",
|
|
26
|
+
"**/.git"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"include": [
|
|
30
|
+
"**/*.ts",
|
|
31
|
+
"**/*.tsx",
|
|
32
|
+
"plugin.config.ts"
|
|
33
|
+
],
|
|
34
|
+
"exclude": [
|
|
35
|
+
"node_modules",
|
|
36
|
+
".next",
|
|
37
|
+
"dist",
|
|
38
|
+
"build",
|
|
39
|
+
".turbo",
|
|
40
|
+
"coverage",
|
|
41
|
+
".git",
|
|
42
|
+
"**/*.test.ts",
|
|
43
|
+
"**/*.test.tsx",
|
|
44
|
+
"**/__tests__/**",
|
|
45
|
+
"**/tsconfig.tsbuildinfo"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Social Media Publisher - TypeScript Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ============================================
|
|
6
|
+
// PLATFORM TYPES
|
|
7
|
+
// ============================================
|
|
8
|
+
|
|
9
|
+
export type SocialPlatform = 'instagram_business' | 'facebook_page'
|
|
10
|
+
|
|
11
|
+
export type FacebookScope =
|
|
12
|
+
| 'email'
|
|
13
|
+
| 'public_profile'
|
|
14
|
+
| 'pages_show_list'
|
|
15
|
+
| 'pages_manage_posts'
|
|
16
|
+
| 'pages_read_engagement'
|
|
17
|
+
| 'read_insights'
|
|
18
|
+
|
|
19
|
+
export type InstagramScope =
|
|
20
|
+
| 'instagram_business_basic'
|
|
21
|
+
| 'instagram_business_content_publish'
|
|
22
|
+
| 'instagram_manage_insights'
|
|
23
|
+
|
|
24
|
+
export type OAuthScope = FacebookScope | InstagramScope
|
|
25
|
+
|
|
26
|
+
// ============================================
|
|
27
|
+
// ACCOUNT TYPES
|
|
28
|
+
// ============================================
|
|
29
|
+
|
|
30
|
+
export interface SocialAccount {
|
|
31
|
+
id: string
|
|
32
|
+
userId: string
|
|
33
|
+
platform: SocialPlatform
|
|
34
|
+
platformAccountId: string
|
|
35
|
+
username: string
|
|
36
|
+
accessToken: string // Encrypted
|
|
37
|
+
tokenExpiresAt: Date
|
|
38
|
+
permissions: OAuthScope[]
|
|
39
|
+
accountMetadata: SocialAccountMetadata
|
|
40
|
+
isActive: boolean
|
|
41
|
+
createdAt: Date
|
|
42
|
+
updatedAt: Date
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SocialAccountMetadata {
|
|
46
|
+
profilePictureUrl?: string
|
|
47
|
+
followersCount?: number
|
|
48
|
+
postsCount?: number
|
|
49
|
+
lastSyncAt?: string
|
|
50
|
+
additionalData?: Record<string, unknown>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================
|
|
54
|
+
// OAUTH TYPES
|
|
55
|
+
// ============================================
|
|
56
|
+
|
|
57
|
+
export interface OAuthCallbackParams {
|
|
58
|
+
code: string
|
|
59
|
+
state: string
|
|
60
|
+
error?: string
|
|
61
|
+
error_description?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface OAuthTokenResponse {
|
|
65
|
+
access_token: string
|
|
66
|
+
token_type: string
|
|
67
|
+
expires_in: number
|
|
68
|
+
refresh_token?: string
|
|
69
|
+
scope?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FacebookPageInfo {
|
|
73
|
+
id: string
|
|
74
|
+
name: string
|
|
75
|
+
category: string
|
|
76
|
+
access_token: string
|
|
77
|
+
tasks: string[]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface InstagramBusinessAccount {
|
|
81
|
+
id: string
|
|
82
|
+
username: string
|
|
83
|
+
profile_picture_url?: string
|
|
84
|
+
followers_count?: number
|
|
85
|
+
follows_count?: number
|
|
86
|
+
media_count?: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================
|
|
90
|
+
// PUBLISHING TYPES
|
|
91
|
+
// ============================================
|
|
92
|
+
|
|
93
|
+
export interface PublishRequest {
|
|
94
|
+
accountId: string
|
|
95
|
+
content: PublishContent
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface PublishContent {
|
|
99
|
+
// Common fields
|
|
100
|
+
caption?: string
|
|
101
|
+
|
|
102
|
+
// Media
|
|
103
|
+
imageUrl?: string
|
|
104
|
+
videoUrl?: string
|
|
105
|
+
|
|
106
|
+
// Scheduling
|
|
107
|
+
scheduledAt?: Date
|
|
108
|
+
|
|
109
|
+
// Platform-specific
|
|
110
|
+
platformOptions?: Record<string, unknown>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface PublishResult {
|
|
114
|
+
success: boolean
|
|
115
|
+
platform: SocialPlatform
|
|
116
|
+
postId?: string
|
|
117
|
+
postUrl?: string
|
|
118
|
+
error?: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================
|
|
122
|
+
// AUDIT LOG TYPES
|
|
123
|
+
// ============================================
|
|
124
|
+
|
|
125
|
+
export type AuditAction =
|
|
126
|
+
| 'account_connected'
|
|
127
|
+
| 'account_disconnected'
|
|
128
|
+
| 'post_published'
|
|
129
|
+
| 'post_failed'
|
|
130
|
+
| 'token_refreshed'
|
|
131
|
+
| 'token_refresh_failed'
|
|
132
|
+
|
|
133
|
+
export interface AuditLog {
|
|
134
|
+
id: string
|
|
135
|
+
userId: string
|
|
136
|
+
accountId?: string
|
|
137
|
+
action: AuditAction
|
|
138
|
+
details: AuditLogDetails
|
|
139
|
+
ipAddress?: string
|
|
140
|
+
userAgent?: string
|
|
141
|
+
createdAt: Date
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface AuditLogDetails {
|
|
145
|
+
platform?: SocialPlatform
|
|
146
|
+
success: boolean
|
|
147
|
+
errorMessage?: string
|
|
148
|
+
metadata?: Record<string, unknown>
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ============================================
|
|
152
|
+
// API RESPONSE TYPES
|
|
153
|
+
// ============================================
|
|
154
|
+
|
|
155
|
+
export interface ConnectAccountResponse {
|
|
156
|
+
success: boolean
|
|
157
|
+
account?: SocialAccount
|
|
158
|
+
error?: string
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DisconnectAccountResponse {
|
|
162
|
+
success: boolean
|
|
163
|
+
accountId: string
|
|
164
|
+
message?: string
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface GetAccountsResponse {
|
|
168
|
+
success: boolean
|
|
169
|
+
accounts: SocialAccount[]
|
|
170
|
+
total: number
|
|
171
|
+
}
|