-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathopenapi.js
More file actions
342 lines (339 loc) · 16.7 KB
/
Copy pathopenapi.js
File metadata and controls
342 lines (339 loc) · 16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/**
* OpenAPI 3.1 specification for the InfluenceX API.
*
* Hand-curated (not auto-generated) to avoid introducing a decorator library.
* Kept close to the source — when you add a new endpoint, append it here too.
* Served at /api/openapi.json and rendered by a lightweight Swagger UI page.
*/
function buildOpenApiSpec(basePath = '/InfluenceX') {
return {
openapi: '3.1.0',
info: {
title: 'InfluenceX API',
version: '1.0.0',
description: 'KOL marketing automation platform. Discover creators, run outreach pipelines, track ROI.',
license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' },
},
servers: [
{ url: `${basePath}/api`, description: 'Default server' },
],
components: {
securitySchemes: {
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
},
schemas: {
Error: {
type: 'object',
properties: { error: { type: 'string' } },
required: ['error'],
},
Campaign: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
description: { type: 'string' },
status: { type: 'string', enum: ['active', 'paused', 'complete'] },
budget: { type: 'number' },
budget_spent: { type: 'number' },
daily_target: { type: 'integer' },
platforms: { type: 'array', items: { type: 'string' } },
},
},
Kol: {
type: 'object',
properties: {
id: { type: 'string' },
campaign_id: { type: 'string' },
platform: { type: 'string', enum: ['youtube', 'tiktok', 'instagram', 'twitch', 'x'] },
username: { type: 'string' },
display_name: { type: 'string' },
followers: { type: 'integer' },
engagement_rate: { type: 'number' },
avg_views: { type: 'integer' },
email: { type: 'string', format: 'email' },
ai_score: { type: 'number' },
status: { type: 'string', enum: ['pending', 'approved', 'rejected'] },
},
},
Contact: {
type: 'object',
properties: {
id: { type: 'string' },
kol_id: { type: 'string' },
campaign_id: { type: 'string' },
email_subject: { type: 'string' },
email_body: { type: 'string' },
cooperation_type: { type: 'string', enum: ['affiliate', 'paid'] },
status: { type: 'string', enum: ['draft', 'sent', 'replied'] },
contract_status: { type: 'string', enum: ['none', 'sent', 'signed', 'declined'] },
content_status: { type: 'string' },
payment_status: { type: 'string', enum: ['unpaid', 'pending', 'paid'] },
payment_amount: { type: 'number' },
},
},
LoginResponse: {
type: 'object',
properties: {
token: { type: 'string' },
expiresAt: { type: 'string', format: 'date-time' },
user: {
type: 'object',
properties: {
id: { type: 'string' },
email: { type: 'string' },
name: { type: 'string' },
role: { type: 'string' },
},
},
},
},
},
},
security: [{ bearerAuth: [] }],
tags: [
{ name: 'auth', description: 'Authentication and permissions' },
{ name: 'campaigns', description: 'Campaign management' },
{ name: 'kols', description: 'KOL (creator) management' },
{ name: 'contacts', description: 'Contact / outreach management' },
{ name: 'pipeline', description: 'Auto-scrape / write / send pipeline' },
{ name: 'discovery', description: 'KOL discovery by keyword search' },
{ name: 'marketplace', description: 'Shared cross-workspace catalog of public creator profiles' },
{ name: 'data', description: 'Content and registration analytics' },
{ name: 'exports', description: 'CSV exports' },
{ name: 'users', description: 'User management (admin only)' },
{ name: 'system', description: 'Health, quota, notifications' },
],
paths: {
'/auth/login': {
post: {
tags: ['auth'],
summary: 'Authenticate and obtain a bearer token',
security: [],
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: { email: { type: 'string' }, password: { type: 'string' } },
required: ['email', 'password'],
},
},
},
},
responses: {
'200': { description: 'Login success', content: { 'application/json': { schema: { $ref: '#/components/schemas/LoginResponse' } } } },
'400': { description: 'Invalid credentials', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } },
'429': { description: 'Rate limit exceeded', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } },
},
},
},
'/auth/register': {
post: {
tags: ['auth'],
summary: 'Register a new user account',
security: [],
requestBody: {
required: true,
content: { 'application/json': { schema: { type: 'object', properties: { email: { type: 'string' }, password: { type: 'string' }, name: { type: 'string' } }, required: ['email', 'password', 'name'] } } },
},
responses: { '200': { description: 'Registered and logged in' } },
},
},
'/auth/me': {
get: { tags: ['auth'], summary: 'Get current user', responses: { '200': { description: 'Current user' } } },
},
'/auth/permissions': {
get: { tags: ['auth'], summary: 'Get permissions for current user', responses: { '200': { description: 'Permissions list' } } },
},
'/auth/roles': {
get: { tags: ['auth'], summary: 'List available roles', security: [], responses: { '200': { description: 'Roles' } } },
},
'/campaigns': {
get: { tags: ['campaigns'], summary: 'List campaigns', responses: { '200': { description: 'Campaigns' } } },
post: { tags: ['campaigns'], summary: 'Create campaign', responses: { '200': { description: 'Created' } } },
},
'/campaigns/{id}': {
get: { tags: ['campaigns'], summary: 'Get campaign', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Campaign' } } },
put: { tags: ['campaigns'], summary: 'Update campaign', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Updated' } } },
delete: { tags: ['campaigns'], summary: 'Delete campaign', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Deleted' } } },
},
'/campaigns/{id}/roi': {
get: {
tags: ['campaigns'],
summary: 'Get aggregated ROI metrics for a campaign',
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
responses: { '200': { description: 'ROI summary with funnel, content perf, cost metrics' } },
},
},
'/campaigns/{campaignId}/kols': {
get: { tags: ['kols'], summary: 'List KOLs in campaign', parameters: [{ name: 'campaignId', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'KOLs' } } },
post: { tags: ['kols'], summary: 'Add KOL to campaign', parameters: [{ name: 'campaignId', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Added' } } },
},
'/campaigns/{campaignId}/kols/collect': {
post: { tags: ['kols'], summary: 'Auto-collect KOLs for campaign (real YouTube API if configured)', parameters: [{ name: 'campaignId', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Collection result with source' } } },
},
'/campaigns/{id}/kols/export': {
get: { tags: ['exports'], summary: 'Export campaign KOLs as CSV', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'CSV file', content: { 'text/csv': {} } } } },
},
'/campaigns/{id}/contacts/export': {
get: { tags: ['exports'], summary: 'Export campaign contacts as CSV', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'CSV file' } } },
},
'/kol-database': {
get: { tags: ['kols'], summary: 'List global KOL database', responses: { '200': { description: 'KOLs' } } },
post: { tags: ['kols'], summary: 'Add KOL to global database by URL', responses: { '200': { description: 'Added, scraping queued' } } },
},
'/kol-database/export': {
get: { tags: ['exports'], summary: 'Export entire KOL database as CSV', responses: { '200': { description: 'CSV file' } } },
},
// Creator Marketplace (roadmap D2). Unlike every other resource here,
// the catalog is shared across workspaces — which is why it carries
// public profile fields only and never contact data.
'/marketplace/creators': {
get: {
tags: ['marketplace'],
summary: 'Browse / search the shared creator catalog (public profile fields only — no email or contact data)',
parameters: [
{ name: 'platform', in: 'query', schema: { type: 'string' } },
{ name: 'category', in: 'query', schema: { type: 'string' } },
{ name: 'min_followers', in: 'query', schema: { type: 'integer' } },
{ name: 'q', in: 'query', description: 'Handle or display name', schema: { type: 'string' } },
{ name: 'sort', in: 'query', schema: { type: 'string', enum: ['followers', 'engagement', 'recent'] } },
{ name: 'limit', in: 'query', description: 'Capped server-side', schema: { type: 'integer', default: 24, maximum: 100 } },
{ name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
],
responses: { '200': { description: 'Creators, total, categories, has_sample' } },
},
},
'/marketplace/creators/{id}/add-to-campaign': {
post: {
tags: ['marketplace'],
summary: 'Copy a catalog listing into one of your own campaigns (workspace-scoped write)',
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
responses: {
'200': { description: 'KOL created in the caller workspace' },
'400': { description: 'Missing campaign_id, or the listing is sample data' },
'404': { description: 'Creator or campaign not found in this workspace' },
'409': { description: 'Already in the campaign' },
},
},
},
'/marketplace/contribute': {
post: {
tags: ['marketplace'],
summary: 'Promote this workspace\'s scraped KOLs into the shared catalog (platform admin). Public fields only; provenance recorded per row',
responses: {
'200': { description: '{ contributed, refreshed, skipped, total }' },
'403': { description: 'Platform admins only' },
},
},
},
'/contacts/{id}/send': {
post: {
tags: ['contacts'],
summary: 'Send an outreach email via Resend/SMTP',
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
responses: {
'200': { description: 'Sent (or marked as sent in dry-run)' },
'400': { description: 'Missing recipient or body' },
'429': { description: 'Rate limit exceeded' },
'502': { description: 'Email provider rejected' },
},
},
},
'/contacts/{id}/schedule': {
post: { tags: ['contacts'], summary: 'Schedule email for future send', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Scheduled' } } },
delete: { tags: ['contacts'], summary: 'Cancel scheduled send', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Cancelled' } } },
},
'/pipeline/start': {
post: { tags: ['pipeline'], summary: 'Start auto pipeline for a creator URL', responses: { '200': { description: 'Pipeline job created' } } },
},
'/pipeline/jobs': {
get: { tags: ['pipeline'], summary: 'List pipeline jobs', responses: { '200': { description: 'Jobs' } } },
},
'/discovery/start': {
post: { tags: ['discovery'], summary: 'Start YouTube keyword discovery', responses: { '200': { description: 'Discovery job created' } } },
},
'/email-templates': {
get: { tags: ['contacts'], summary: 'List built-in email templates', responses: { '200': { description: 'Templates' } } },
},
'/email-templates/{id}/render': {
post: { tags: ['contacts'], summary: 'Render a template with variables', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Rendered subject + body' } } },
},
'/users': {
get: { tags: ['users'], summary: 'List all users (admin only)', responses: { '200': { description: 'Users' } } },
},
'/users/invite': {
post: { tags: ['users'], summary: 'Invite a new user (admin only)', responses: { '200': { description: 'Invited' } } },
},
'/users/{id}/role': {
patch: { tags: ['users'], summary: 'Change a user\'s role (admin only)', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Updated' } } },
},
'/users/{id}': {
delete: { tags: ['users'], summary: 'Delete a user (admin only)', parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }], responses: { '200': { description: 'Deleted' } } },
},
'/usage': {
get: {
tags: ['data'],
summary: 'Token/cost usage for the current workspace, bucketed by month and agent',
parameters: [
{ name: 'months', in: 'query', required: false, schema: { type: 'integer', default: 6, minimum: 1, maximum: 24 }, description: 'Window size in months, including the current partial month' },
{ name: 'agent', in: 'query', required: false, schema: { type: 'string' }, description: 'Restrict to a single agent id' },
],
responses: { '200': { description: 'month × agent grid plus per-month, per-agent and grand totals' } },
},
},
'/admin/usage': {
get: {
tags: ['system'],
summary: 'Token/cost usage across every workspace (platform admin only)',
parameters: [
{ name: 'months', in: 'query', required: false, schema: { type: 'integer', default: 6, minimum: 1, maximum: 24 } },
{ name: 'agent', in: 'query', required: false, schema: { type: 'string' } },
],
responses: { '200': { description: 'Same aggregation as /usage plus a per-workspace breakdown' }, '403': { description: 'Not a platform admin' } },
},
},
'/quota/youtube': {
get: { tags: ['system'], summary: 'YouTube Data API daily quota usage', responses: { '200': { description: 'Quota status' } } },
},
'/notifications/status': {
get: { tags: ['system'], summary: 'Enabled notification sinks', responses: { '200': { description: 'Sink list' } } },
},
},
};
}
/**
* Minimal Swagger UI HTML (uses the official CDN-hosted bundle).
* Self-contained — no build step, no new deps.
*/
function swaggerUiHtml(specUrl) {
return `<!DOCTYPE html>
<html>
<head>
<title>InfluenceX API Docs</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
<style>
body { margin: 0; background: #fafafa; }
.topbar { display: none; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>
window.ui = SwaggerUIBundle({
url: ${JSON.stringify(specUrl)},
dom_id: '#swagger-ui',
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis],
layout: 'BaseLayout',
});
</script>
</body>
</html>`;
}
module.exports = { buildOpenApiSpec, swaggerUiHtml };