@jiggai/kitchen-plugin-marketing 0.2.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.
@@ -0,0 +1,1486 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc6) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc6 = __getOwnPropDesc(from, key)) || desc6.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+
32
+ // src/db/schema.ts
33
+ var schema_exports = {};
34
+ __export(schema_exports, {
35
+ accountMetrics: () => accountMetrics,
36
+ accountMetricsRelations: () => accountMetricsRelations,
37
+ media: () => media,
38
+ postMetrics: () => postMetrics,
39
+ postMetricsRelations: () => postMetricsRelations,
40
+ posts: () => posts,
41
+ postsRelations: () => postsRelations,
42
+ socialAccounts: () => socialAccounts,
43
+ socialAccountsRelations: () => socialAccountsRelations,
44
+ templates: () => templates,
45
+ webhooks: () => webhooks
46
+ });
47
+ var import_sqlite_core, import_drizzle_orm, posts, media, templates, socialAccounts, postMetrics, accountMetrics, webhooks, postsRelations, postMetricsRelations, socialAccountsRelations, accountMetricsRelations;
48
+ var init_schema = __esm({
49
+ "src/db/schema.ts"() {
50
+ import_sqlite_core = require("drizzle-orm/sqlite-core");
51
+ import_drizzle_orm = require("drizzle-orm");
52
+ posts = (0, import_sqlite_core.sqliteTable)("posts", {
53
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
54
+ teamId: (0, import_sqlite_core.text)("team_id").notNull(),
55
+ content: (0, import_sqlite_core.text)("content").notNull(),
56
+ platforms: (0, import_sqlite_core.text)("platforms").notNull(),
57
+ // JSON array
58
+ status: (0, import_sqlite_core.text)("status").notNull(),
59
+ // draft, scheduled, published, failed
60
+ scheduledAt: (0, import_sqlite_core.text)("scheduled_at"),
61
+ // ISO string
62
+ publishedAt: (0, import_sqlite_core.text)("published_at"),
63
+ // ISO string
64
+ tags: (0, import_sqlite_core.text)("tags"),
65
+ // JSON array
66
+ mediaIds: (0, import_sqlite_core.text)("media_ids"),
67
+ // JSON array
68
+ templateId: (0, import_sqlite_core.text)("template_id"),
69
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull(),
70
+ updatedAt: (0, import_sqlite_core.text)("updated_at").notNull(),
71
+ createdBy: (0, import_sqlite_core.text)("created_by").notNull()
72
+ });
73
+ media = (0, import_sqlite_core.sqliteTable)("media", {
74
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
75
+ teamId: (0, import_sqlite_core.text)("team_id").notNull(),
76
+ filename: (0, import_sqlite_core.text)("filename").notNull(),
77
+ originalName: (0, import_sqlite_core.text)("original_name").notNull(),
78
+ mimeType: (0, import_sqlite_core.text)("mime_type").notNull(),
79
+ size: (0, import_sqlite_core.integer)("size").notNull(),
80
+ width: (0, import_sqlite_core.integer)("width"),
81
+ height: (0, import_sqlite_core.integer)("height"),
82
+ alt: (0, import_sqlite_core.text)("alt"),
83
+ tags: (0, import_sqlite_core.text)("tags"),
84
+ // JSON array
85
+ url: (0, import_sqlite_core.text)("url").notNull(),
86
+ thumbnailUrl: (0, import_sqlite_core.text)("thumbnail_url"),
87
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull(),
88
+ createdBy: (0, import_sqlite_core.text)("created_by").notNull()
89
+ });
90
+ templates = (0, import_sqlite_core.sqliteTable)("templates", {
91
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
92
+ teamId: (0, import_sqlite_core.text)("team_id").notNull(),
93
+ name: (0, import_sqlite_core.text)("name").notNull(),
94
+ content: (0, import_sqlite_core.text)("content").notNull(),
95
+ variables: (0, import_sqlite_core.text)("variables"),
96
+ // JSON array of variable definitions
97
+ tags: (0, import_sqlite_core.text)("tags"),
98
+ // JSON array
99
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull(),
100
+ updatedAt: (0, import_sqlite_core.text)("updated_at").notNull(),
101
+ createdBy: (0, import_sqlite_core.text)("created_by").notNull()
102
+ });
103
+ socialAccounts = (0, import_sqlite_core.sqliteTable)("social_accounts", {
104
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
105
+ teamId: (0, import_sqlite_core.text)("team_id").notNull(),
106
+ platform: (0, import_sqlite_core.text)("platform").notNull(),
107
+ // twitter, linkedin, instagram, etc.
108
+ displayName: (0, import_sqlite_core.text)("display_name").notNull(),
109
+ username: (0, import_sqlite_core.text)("username"),
110
+ avatar: (0, import_sqlite_core.text)("avatar"),
111
+ isActive: (0, import_sqlite_core.integer)("is_active", { mode: "boolean" }).notNull().default(true),
112
+ credentials: (0, import_sqlite_core.blob)("credentials").notNull(),
113
+ // Encrypted JSON
114
+ settings: (0, import_sqlite_core.text)("settings"),
115
+ // JSON object
116
+ lastSync: (0, import_sqlite_core.text)("last_sync"),
117
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull(),
118
+ updatedAt: (0, import_sqlite_core.text)("updated_at").notNull()
119
+ });
120
+ postMetrics = (0, import_sqlite_core.sqliteTable)("post_metrics", {
121
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
122
+ postId: (0, import_sqlite_core.text)("post_id").notNull(),
123
+ platform: (0, import_sqlite_core.text)("platform").notNull(),
124
+ impressions: (0, import_sqlite_core.integer)("impressions").default(0),
125
+ likes: (0, import_sqlite_core.integer)("likes").default(0),
126
+ shares: (0, import_sqlite_core.integer)("shares").default(0),
127
+ comments: (0, import_sqlite_core.integer)("comments").default(0),
128
+ clicks: (0, import_sqlite_core.integer)("clicks").default(0),
129
+ engagementRate: (0, import_sqlite_core.text)("engagement_rate"),
130
+ // Stored as string to avoid float precision issues
131
+ syncedAt: (0, import_sqlite_core.text)("synced_at").notNull()
132
+ });
133
+ accountMetrics = (0, import_sqlite_core.sqliteTable)("account_metrics", {
134
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
135
+ accountId: (0, import_sqlite_core.text)("account_id").notNull(),
136
+ date: (0, import_sqlite_core.text)("date").notNull(),
137
+ // YYYY-MM-DD format
138
+ followers: (0, import_sqlite_core.integer)("followers").default(0),
139
+ following: (0, import_sqlite_core.integer)("following").default(0),
140
+ posts: (0, import_sqlite_core.integer)("posts").default(0),
141
+ engagement: (0, import_sqlite_core.integer)("engagement").default(0),
142
+ reach: (0, import_sqlite_core.integer)("reach").default(0),
143
+ syncedAt: (0, import_sqlite_core.text)("synced_at").notNull()
144
+ });
145
+ webhooks = (0, import_sqlite_core.sqliteTable)("webhooks", {
146
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
147
+ teamId: (0, import_sqlite_core.text)("team_id").notNull(),
148
+ url: (0, import_sqlite_core.text)("url").notNull(),
149
+ events: (0, import_sqlite_core.text)("events").notNull(),
150
+ // JSON array
151
+ secret: (0, import_sqlite_core.text)("secret"),
152
+ isActive: (0, import_sqlite_core.integer)("is_active", { mode: "boolean" }).notNull().default(true),
153
+ createdAt: (0, import_sqlite_core.text)("created_at").notNull(),
154
+ lastTriggered: (0, import_sqlite_core.text)("last_triggered")
155
+ });
156
+ postsRelations = (0, import_drizzle_orm.relations)(posts, ({ many }) => ({
157
+ metrics: many(postMetrics)
158
+ }));
159
+ postMetricsRelations = (0, import_drizzle_orm.relations)(postMetrics, ({ one }) => ({
160
+ post: one(posts, {
161
+ fields: [postMetrics.postId],
162
+ references: [posts.id]
163
+ })
164
+ }));
165
+ socialAccountsRelations = (0, import_drizzle_orm.relations)(socialAccounts, ({ many }) => ({
166
+ metrics: many(accountMetrics)
167
+ }));
168
+ accountMetricsRelations = (0, import_drizzle_orm.relations)(accountMetrics, ({ one }) => ({
169
+ account: one(socialAccounts, {
170
+ fields: [accountMetrics.accountId],
171
+ references: [socialAccounts.id]
172
+ })
173
+ }));
174
+ }
175
+ });
176
+
177
+ // src/db/index.ts
178
+ function createDatabase(teamId) {
179
+ const dbPath = process.env.KITCHEN_PLUGIN_DB_PATH || "./data";
180
+ const teamDbFile = `${dbPath}/marketing-${teamId}.db`;
181
+ const sqlite = new import_better_sqlite3.default(teamDbFile);
182
+ const db = (0, import_better_sqlite32.drizzle)(sqlite, { schema: schema_exports });
183
+ return { db, sqlite };
184
+ }
185
+ function encryptCredentials(credentials) {
186
+ const plaintext = JSON.stringify(credentials);
187
+ const hash = (0, import_crypto.createHash)("sha256").update(ENCRYPTION_KEY).digest();
188
+ const cipher = (0, import_crypto.createCipher)("aes-256-cbc", hash);
189
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
190
+ encrypted += cipher.final("hex");
191
+ return Buffer.from(encrypted, "hex");
192
+ }
193
+ function initializeDatabase(teamId) {
194
+ const { db, sqlite } = createDatabase(teamId);
195
+ try {
196
+ (0, import_migrator.migrate)(db, { migrationsFolder: "./db/migrations" });
197
+ } catch (error) {
198
+ console.warn("Migration warning:", error.message);
199
+ }
200
+ return { db, sqlite };
201
+ }
202
+ var import_better_sqlite3, import_better_sqlite32, import_migrator, import_crypto, ENCRYPTION_KEY;
203
+ var init_db = __esm({
204
+ "src/db/index.ts"() {
205
+ import_better_sqlite3 = __toESM(require("better-sqlite3"));
206
+ import_better_sqlite32 = require("drizzle-orm/better-sqlite3");
207
+ init_schema();
208
+ import_migrator = require("drizzle-orm/better-sqlite3/migrator");
209
+ import_crypto = require("crypto");
210
+ ENCRYPTION_KEY = process.env.KITCHEN_ENCRYPTION_KEY || "fallback-key-change-in-production";
211
+ }
212
+ });
213
+
214
+ // src/api/templates.ts
215
+ var templates_exports = {};
216
+ __export(templates_exports, {
217
+ registerTemplateRoutes: () => registerTemplateRoutes
218
+ });
219
+ function getTeamId(req) {
220
+ return req.headers["x-team-id"] || req.query.teamId;
221
+ }
222
+ function getUserId(req) {
223
+ return req.headers["x-user-id"] || "system";
224
+ }
225
+ function sendError(res, status, error, message, details) {
226
+ const response = { error, message, details };
227
+ res.status(status).json(response);
228
+ }
229
+ function parsePagination(req) {
230
+ const limit = Math.min(parseInt(req.query.limit) || 20, 100);
231
+ const offset = parseInt(req.query.offset) || 0;
232
+ return { limit, offset };
233
+ }
234
+ function registerTemplateRoutes(app) {
235
+ app.get("/templates", async (req, res) => {
236
+ try {
237
+ const teamId = getTeamId(req);
238
+ if (!teamId) return sendError(res, 400, "MISSING_TEAM_ID", "Team ID is required");
239
+ const { db } = initializeDatabase(teamId);
240
+ const { limit, offset } = parsePagination(req);
241
+ const conditions = [(0, import_drizzle_orm2.eq)(templates.teamId, teamId)];
242
+ if (req.query.tag) {
243
+ conditions.push((0, import_drizzle_orm2.like)(templates.tags, `%"${req.query.tag}"%`));
244
+ }
245
+ const templates2 = await db.select().from(templates).where((0, import_drizzle_orm2.and)(...conditions)).orderBy((0, import_drizzle_orm2.desc)(templates.createdAt)).limit(limit).offset(offset);
246
+ const response = templates2.map((template) => ({
247
+ id: template.id,
248
+ name: template.name,
249
+ content: template.content,
250
+ variables: JSON.parse(template.variables || "[]"),
251
+ tags: JSON.parse(template.tags || "[]"),
252
+ createdAt: template.createdAt,
253
+ updatedAt: template.updatedAt,
254
+ createdBy: template.createdBy
255
+ }));
256
+ res.json({ templates: response });
257
+ } catch (error) {
258
+ sendError(res, 500, "DATABASE_ERROR", error.message);
259
+ }
260
+ });
261
+ app.post("/templates", async (req, res) => {
262
+ try {
263
+ const teamId = getTeamId(req);
264
+ const userId = getUserId(req);
265
+ if (!teamId) return sendError(res, 400, "MISSING_TEAM_ID", "Team ID is required");
266
+ const body = req.body;
267
+ if (!body.name || !body.content) {
268
+ return sendError(res, 400, "VALIDATION_ERROR", "Name and content are required");
269
+ }
270
+ const { db } = initializeDatabase(teamId);
271
+ const now = (/* @__PURE__ */ new Date()).toISOString();
272
+ const newTemplate = {
273
+ id: (0, import_crypto2.randomUUID)(),
274
+ teamId,
275
+ name: body.name,
276
+ content: body.content,
277
+ variables: JSON.stringify(body.variables || []),
278
+ tags: JSON.stringify(body.tags || []),
279
+ createdAt: now,
280
+ updatedAt: now,
281
+ createdBy: userId
282
+ };
283
+ await db.insert(templates).values(newTemplate);
284
+ const response = {
285
+ id: newTemplate.id,
286
+ name: newTemplate.name,
287
+ content: newTemplate.content,
288
+ variables: JSON.parse(newTemplate.variables),
289
+ tags: JSON.parse(newTemplate.tags),
290
+ createdAt: newTemplate.createdAt,
291
+ updatedAt: newTemplate.updatedAt,
292
+ createdBy: newTemplate.createdBy
293
+ };
294
+ res.status(201).json(response);
295
+ } catch (error) {
296
+ sendError(res, 500, "DATABASE_ERROR", error.message);
297
+ }
298
+ });
299
+ app.get("/templates/:id", async (req, res) => {
300
+ try {
301
+ const teamId = getTeamId(req);
302
+ if (!teamId) return sendError(res, 400, "MISSING_TEAM_ID", "Team ID is required");
303
+ const { db } = initializeDatabase(teamId);
304
+ const template = await db.select().from(templates).where((0, import_drizzle_orm2.and)(
305
+ (0, import_drizzle_orm2.eq)(templates.id, req.params.id),
306
+ (0, import_drizzle_orm2.eq)(templates.teamId, teamId)
307
+ )).get();
308
+ if (!template) {
309
+ return sendError(res, 404, "TEMPLATE_NOT_FOUND", "Template not found");
310
+ }
311
+ const response = {
312
+ id: template.id,
313
+ name: template.name,
314
+ content: template.content,
315
+ variables: JSON.parse(template.variables || "[]"),
316
+ tags: JSON.parse(template.tags || "[]"),
317
+ createdAt: template.createdAt,
318
+ updatedAt: template.updatedAt,
319
+ createdBy: template.createdBy
320
+ };
321
+ res.json(response);
322
+ } catch (error) {
323
+ sendError(res, 500, "DATABASE_ERROR", error.message);
324
+ }
325
+ });
326
+ app.put("/templates/:id", async (req, res) => {
327
+ try {
328
+ const teamId = getTeamId(req);
329
+ if (!teamId) return sendError(res, 400, "MISSING_TEAM_ID", "Team ID is required");
330
+ const { db } = initializeDatabase(teamId);
331
+ const body = req.body;
332
+ const updateData = {
333
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
334
+ };
335
+ if (body.name !== void 0) updateData.name = body.name;
336
+ if (body.content !== void 0) updateData.content = body.content;
337
+ if (body.variables !== void 0) updateData.variables = JSON.stringify(body.variables);
338
+ if (body.tags !== void 0) updateData.tags = JSON.stringify(body.tags);
339
+ const result = await db.update(templates).set(updateData).where((0, import_drizzle_orm2.and)(
340
+ (0, import_drizzle_orm2.eq)(templates.id, req.params.id),
341
+ (0, import_drizzle_orm2.eq)(templates.teamId, teamId)
342
+ ));
343
+ if (result.changes === 0) {
344
+ return sendError(res, 404, "TEMPLATE_NOT_FOUND", "Template not found");
345
+ }
346
+ const updatedTemplate = await db.select().from(templates).where((0, import_drizzle_orm2.eq)(templates.id, req.params.id)).get();
347
+ const response = {
348
+ id: updatedTemplate.id,
349
+ name: updatedTemplate.name,
350
+ content: updatedTemplate.content,
351
+ variables: JSON.parse(updatedTemplate.variables || "[]"),
352
+ tags: JSON.parse(updatedTemplate.tags || "[]"),
353
+ createdAt: updatedTemplate.createdAt,
354
+ updatedAt: updatedTemplate.updatedAt,
355
+ createdBy: updatedTemplate.createdBy
356
+ };
357
+ res.json(response);
358
+ } catch (error) {
359
+ sendError(res, 500, "DATABASE_ERROR", error.message);
360
+ }
361
+ });
362
+ app.delete("/templates/:id", async (req, res) => {
363
+ try {
364
+ const teamId = getTeamId(req);
365
+ if (!teamId) return sendError(res, 400, "MISSING_TEAM_ID", "Team ID is required");
366
+ const { db } = initializeDatabase(teamId);
367
+ const result = await db.delete(templates).where((0, import_drizzle_orm2.and)(
368
+ (0, import_drizzle_orm2.eq)(templates.id, req.params.id),
369
+ (0, import_drizzle_orm2.eq)(templates.teamId, teamId)
370
+ ));
371
+ if (result.changes === 0) {
372
+ return sendError(res, 404, "TEMPLATE_NOT_FOUND", "Template not found");
373
+ }
374
+ res.status(204).send();
375
+ } catch (error) {
376
+ sendError(res, 500, "DATABASE_ERROR", error.message);
377
+ }
378
+ });
379
+ }
380
+ var import_drizzle_orm2, import_crypto2;
381
+ var init_templates = __esm({
382
+ "src/api/templates.ts"() {
383
+ import_drizzle_orm2 = require("drizzle-orm");
384
+ init_db();
385
+ init_schema();
386
+ import_crypto2 = require("crypto");
387
+ }
388
+ });
389
+
390
+ // src/api/social-accounts.ts
391
+ var social_accounts_exports = {};
392
+ __export(social_accounts_exports, {
393
+ registerSocialAccountRoutes: () => registerSocialAccountRoutes
394
+ });
395
+ function getTeamId2(req) {
396
+ return req.headers["x-team-id"] || req.query.teamId;
397
+ }
398
+ function getUserId2(req) {
399
+ return req.headers["x-user-id"] || "system";
400
+ }
401
+ function sendError2(res, status, error, message, details) {
402
+ const response = { error, message, details };
403
+ res.status(status).json(response);
404
+ }
405
+ function registerSocialAccountRoutes(app) {
406
+ app.get("/accounts", async (req, res) => {
407
+ try {
408
+ const teamId = getTeamId2(req);
409
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
410
+ const { db } = initializeDatabase(teamId);
411
+ const accounts = await db.select().from(socialAccounts).where((0, import_drizzle_orm3.eq)(socialAccounts.teamId, teamId)).orderBy((0, import_drizzle_orm3.desc)(socialAccounts.createdAt));
412
+ const response = accounts.map((account) => ({
413
+ id: account.id,
414
+ platform: account.platform,
415
+ displayName: account.displayName,
416
+ username: account.username || void 0,
417
+ avatar: account.avatar || void 0,
418
+ isActive: account.isActive,
419
+ settings: JSON.parse(account.settings || "{}"),
420
+ lastSync: account.lastSync || void 0,
421
+ createdAt: account.createdAt,
422
+ updatedAt: account.updatedAt
423
+ // Note: credentials are never returned for security
424
+ }));
425
+ res.json({ accounts: response });
426
+ } catch (error) {
427
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
428
+ }
429
+ });
430
+ app.post("/accounts", async (req, res) => {
431
+ try {
432
+ const teamId = getTeamId2(req);
433
+ const userId = getUserId2(req);
434
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
435
+ const body = req.body;
436
+ if (!body.platform || !body.displayName || !body.credentials) {
437
+ return sendError2(res, 400, "VALIDATION_ERROR", "Platform, displayName, and credentials are required");
438
+ }
439
+ const { db } = initializeDatabase(teamId);
440
+ const now = (/* @__PURE__ */ new Date()).toISOString();
441
+ const newAccount = {
442
+ id: (0, import_crypto3.randomUUID)(),
443
+ teamId,
444
+ platform: body.platform,
445
+ displayName: body.displayName,
446
+ username: body.username || null,
447
+ avatar: null,
448
+ isActive: true,
449
+ credentials: encryptCredentials(body.credentials),
450
+ settings: JSON.stringify(body.settings || {}),
451
+ lastSync: null,
452
+ createdAt: now,
453
+ updatedAt: now
454
+ };
455
+ await db.insert(socialAccounts).values(newAccount);
456
+ const response = {
457
+ id: newAccount.id,
458
+ platform: newAccount.platform,
459
+ displayName: newAccount.displayName,
460
+ username: newAccount.username || void 0,
461
+ isActive: newAccount.isActive,
462
+ settings: JSON.parse(newAccount.settings),
463
+ createdAt: newAccount.createdAt,
464
+ updatedAt: newAccount.updatedAt
465
+ };
466
+ res.status(201).json(response);
467
+ } catch (error) {
468
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
469
+ }
470
+ });
471
+ app.get("/accounts/:id", async (req, res) => {
472
+ try {
473
+ const teamId = getTeamId2(req);
474
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
475
+ const { db } = initializeDatabase(teamId);
476
+ const account = await db.select().from(socialAccounts).where((0, import_drizzle_orm3.and)(
477
+ (0, import_drizzle_orm3.eq)(socialAccounts.id, req.params.id),
478
+ (0, import_drizzle_orm3.eq)(socialAccounts.teamId, teamId)
479
+ )).get();
480
+ if (!account) {
481
+ return sendError2(res, 404, "ACCOUNT_NOT_FOUND", "Account not found");
482
+ }
483
+ const response = {
484
+ id: account.id,
485
+ platform: account.platform,
486
+ displayName: account.displayName,
487
+ username: account.username || void 0,
488
+ avatar: account.avatar || void 0,
489
+ isActive: account.isActive,
490
+ settings: JSON.parse(account.settings || "{}"),
491
+ lastSync: account.lastSync || void 0,
492
+ createdAt: account.createdAt,
493
+ updatedAt: account.updatedAt
494
+ };
495
+ res.json(response);
496
+ } catch (error) {
497
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
498
+ }
499
+ });
500
+ app.put("/accounts/:id", async (req, res) => {
501
+ try {
502
+ const teamId = getTeamId2(req);
503
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
504
+ const { db } = initializeDatabase(teamId);
505
+ const body = req.body;
506
+ const updateData = {
507
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
508
+ };
509
+ if (body.displayName !== void 0) updateData.displayName = body.displayName;
510
+ if (body.username !== void 0) updateData.username = body.username;
511
+ if (body.avatar !== void 0) updateData.avatar = body.avatar;
512
+ if (body.isActive !== void 0) updateData.isActive = body.isActive;
513
+ if (body.settings !== void 0) updateData.settings = JSON.stringify(body.settings);
514
+ if (body.credentials !== void 0) updateData.credentials = encryptCredentials(body.credentials);
515
+ const result = await db.update(socialAccounts).set(updateData).where((0, import_drizzle_orm3.and)(
516
+ (0, import_drizzle_orm3.eq)(socialAccounts.id, req.params.id),
517
+ (0, import_drizzle_orm3.eq)(socialAccounts.teamId, teamId)
518
+ ));
519
+ if (result.changes === 0) {
520
+ return sendError2(res, 404, "ACCOUNT_NOT_FOUND", "Account not found");
521
+ }
522
+ res.json({ success: true });
523
+ } catch (error) {
524
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
525
+ }
526
+ });
527
+ app.delete("/accounts/:id", async (req, res) => {
528
+ try {
529
+ const teamId = getTeamId2(req);
530
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
531
+ const { db } = initializeDatabase(teamId);
532
+ const result = await db.delete(socialAccounts).where((0, import_drizzle_orm3.and)(
533
+ (0, import_drizzle_orm3.eq)(socialAccounts.id, req.params.id),
534
+ (0, import_drizzle_orm3.eq)(socialAccounts.teamId, teamId)
535
+ ));
536
+ if (result.changes === 0) {
537
+ return sendError2(res, 404, "ACCOUNT_NOT_FOUND", "Account not found");
538
+ }
539
+ await db.delete(accountMetrics).where((0, import_drizzle_orm3.eq)(accountMetrics.accountId, req.params.id));
540
+ res.status(204).send();
541
+ } catch (error) {
542
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
543
+ }
544
+ });
545
+ app.get("/accounts/:id/metrics", async (req, res) => {
546
+ try {
547
+ const teamId = getTeamId2(req);
548
+ if (!teamId) return sendError2(res, 400, "MISSING_TEAM_ID", "Team ID is required");
549
+ const { db } = initializeDatabase(teamId);
550
+ const period = req.query.period || "7d";
551
+ const endDate = /* @__PURE__ */ new Date();
552
+ const startDate = /* @__PURE__ */ new Date();
553
+ const days = parseInt(period.replace("d", "")) || 7;
554
+ startDate.setDate(endDate.getDate() - days);
555
+ const account = await db.select().from(socialAccounts).where((0, import_drizzle_orm3.and)(
556
+ (0, import_drizzle_orm3.eq)(socialAccounts.id, req.params.id),
557
+ (0, import_drizzle_orm3.eq)(socialAccounts.teamId, teamId)
558
+ )).get();
559
+ if (!account) {
560
+ return sendError2(res, 404, "ACCOUNT_NOT_FOUND", "Account not found");
561
+ }
562
+ const metrics = await db.select().from(accountMetrics).where((0, import_drizzle_orm3.and)(
563
+ (0, import_drizzle_orm3.eq)(accountMetrics.accountId, req.params.id)
564
+ // Add date range filtering here when implemented
565
+ )).orderBy((0, import_drizzle_orm3.desc)(accountMetrics.date)).limit(parseInt(period.replace("d", "")) || 7);
566
+ const latestMetric = metrics[0];
567
+ const oldestMetric = metrics[metrics.length - 1];
568
+ const followerGrowth = latestMetric && oldestMetric ? (latestMetric.followers || 0) - (oldestMetric.followers || 0) : 0;
569
+ const totalEngagement = metrics.reduce((sum, m) => sum + (m.engagement || 0), 0);
570
+ const response = {
571
+ account: {
572
+ id: account.id,
573
+ platform: account.platform,
574
+ username: account.username
575
+ },
576
+ period,
577
+ metrics: {
578
+ followerGrowth,
579
+ totalEngagement,
580
+ averageEngagement: metrics.length > 0 ? totalEngagement / metrics.length : 0,
581
+ currentFollowers: latestMetric?.followers || 0,
582
+ currentFollowing: latestMetric?.following || 0
583
+ },
584
+ dailyMetrics: metrics.map((m) => ({
585
+ date: m.date,
586
+ followers: m.followers || 0,
587
+ following: m.following || 0,
588
+ posts: m.posts || 0,
589
+ engagement: m.engagement || 0,
590
+ reach: m.reach || 0
591
+ }))
592
+ };
593
+ res.json(response);
594
+ } catch (error) {
595
+ sendError2(res, 500, "DATABASE_ERROR", error.message);
596
+ }
597
+ });
598
+ }
599
+ var import_drizzle_orm3, import_crypto3;
600
+ var init_social_accounts = __esm({
601
+ "src/api/social-accounts.ts"() {
602
+ import_drizzle_orm3 = require("drizzle-orm");
603
+ init_db();
604
+ init_schema();
605
+ import_crypto3 = require("crypto");
606
+ }
607
+ });
608
+
609
+ // src/api/calendar.ts
610
+ var calendar_exports = {};
611
+ __export(calendar_exports, {
612
+ registerCalendarRoutes: () => registerCalendarRoutes
613
+ });
614
+ function getTeamId3(req) {
615
+ return req.headers["x-team-id"] || req.query.teamId;
616
+ }
617
+ function sendError3(res, status, error, message, details) {
618
+ const response = { error, message, details };
619
+ res.status(status).json(response);
620
+ }
621
+ function formatDate(date) {
622
+ return date.toISOString().split("T")[0];
623
+ }
624
+ function registerCalendarRoutes(app) {
625
+ app.get("/calendar", async (req, res) => {
626
+ try {
627
+ const teamId = getTeamId3(req);
628
+ if (!teamId) return sendError3(res, 400, "MISSING_TEAM_ID", "Team ID is required");
629
+ const { db } = initializeDatabase(teamId);
630
+ const start = req.query.start;
631
+ const end = req.query.end;
632
+ const view = req.query.view || "month";
633
+ if (!start || !end) {
634
+ return sendError3(res, 400, "MISSING_DATES", "Start and end dates are required");
635
+ }
636
+ const posts2 = await db.select().from(posts).where((0, import_drizzle_orm4.and)(
637
+ (0, import_drizzle_orm4.eq)(posts.teamId, teamId),
638
+ (0, import_drizzle_orm4.gte)(posts.scheduledAt, start),
639
+ (0, import_drizzle_orm4.lte)(posts.scheduledAt, end),
640
+ (0, import_drizzle_orm4.eq)(posts.status, "scheduled")
641
+ )).orderBy(posts.scheduledAt);
642
+ const eventsByDate = {};
643
+ const platformCounts = {};
644
+ posts2.forEach((post) => {
645
+ if (!post.scheduledAt) return;
646
+ const date = formatDate(new Date(post.scheduledAt));
647
+ if (!eventsByDate[date]) {
648
+ eventsByDate[date] = [];
649
+ }
650
+ const postResponse = {
651
+ id: post.id,
652
+ content: post.content,
653
+ platforms: JSON.parse(post.platforms || "[]"),
654
+ status: post.status,
655
+ scheduledAt: post.scheduledAt,
656
+ publishedAt: post.publishedAt || void 0,
657
+ tags: JSON.parse(post.tags || "[]"),
658
+ mediaIds: JSON.parse(post.mediaIds || "[]"),
659
+ templateId: post.templateId || void 0,
660
+ createdAt: post.createdAt,
661
+ updatedAt: post.updatedAt,
662
+ createdBy: post.createdBy
663
+ };
664
+ eventsByDate[date].push(postResponse);
665
+ postResponse.platforms.forEach((platform) => {
666
+ platformCounts[platform] = (platformCounts[platform] || 0) + 1;
667
+ });
668
+ });
669
+ const events = Object.entries(eventsByDate).map(([date, posts3]) => ({
670
+ date,
671
+ posts: posts3
672
+ }));
673
+ const response = {
674
+ view,
675
+ period: `${start} to ${end}`,
676
+ events,
677
+ summary: {
678
+ totalScheduled: posts2.length,
679
+ byPlatform: platformCounts
680
+ }
681
+ };
682
+ res.json(response);
683
+ } catch (error) {
684
+ sendError3(res, 500, "DATABASE_ERROR", error.message);
685
+ }
686
+ });
687
+ app.post("/calendar/schedule", async (req, res) => {
688
+ try {
689
+ const teamId = getTeamId3(req);
690
+ if (!teamId) return sendError3(res, 400, "MISSING_TEAM_ID", "Team ID is required");
691
+ const { postId, scheduledAt, platforms } = req.body;
692
+ if (!postId || !scheduledAt) {
693
+ return sendError3(res, 400, "VALIDATION_ERROR", "postId and scheduledAt are required");
694
+ }
695
+ const { db } = initializeDatabase(teamId);
696
+ const updateData = {
697
+ scheduledAt,
698
+ status: "scheduled",
699
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
700
+ };
701
+ if (platforms) {
702
+ updateData.platforms = JSON.stringify(platforms);
703
+ }
704
+ const result = await db.update(posts).set(updateData).where((0, import_drizzle_orm4.and)(
705
+ (0, import_drizzle_orm4.eq)(posts.id, postId),
706
+ (0, import_drizzle_orm4.eq)(posts.teamId, teamId)
707
+ ));
708
+ if (result.changes === 0) {
709
+ return sendError3(res, 404, "POST_NOT_FOUND", "Post not found");
710
+ }
711
+ res.json({ success: true, scheduledAt });
712
+ } catch (error) {
713
+ sendError3(res, 500, "DATABASE_ERROR", error.message);
714
+ }
715
+ });
716
+ app.get("/calendar/scheduled", async (req, res) => {
717
+ try {
718
+ const teamId = getTeamId3(req);
719
+ if (!teamId) return sendError3(res, 400, "MISSING_TEAM_ID", "Team ID is required");
720
+ const { db } = initializeDatabase(teamId);
721
+ const posts2 = await db.select().from(posts).where((0, import_drizzle_orm4.and)(
722
+ (0, import_drizzle_orm4.eq)(posts.teamId, teamId),
723
+ (0, import_drizzle_orm4.eq)(posts.status, "scheduled")
724
+ )).orderBy(posts.scheduledAt);
725
+ const response = posts2.map((post) => ({
726
+ id: post.id,
727
+ content: post.content,
728
+ platforms: JSON.parse(post.platforms || "[]"),
729
+ status: post.status,
730
+ scheduledAt: post.scheduledAt || void 0,
731
+ publishedAt: post.publishedAt || void 0,
732
+ tags: JSON.parse(post.tags || "[]"),
733
+ mediaIds: JSON.parse(post.mediaIds || "[]"),
734
+ templateId: post.templateId || void 0,
735
+ createdAt: post.createdAt,
736
+ updatedAt: post.updatedAt,
737
+ createdBy: post.createdBy
738
+ }));
739
+ res.json({ scheduled: response });
740
+ } catch (error) {
741
+ sendError3(res, 500, "DATABASE_ERROR", error.message);
742
+ }
743
+ });
744
+ app.put("/calendar/scheduled/:id", async (req, res) => {
745
+ try {
746
+ const teamId = getTeamId3(req);
747
+ if (!teamId) return sendError3(res, 400, "MISSING_TEAM_ID", "Team ID is required");
748
+ const { scheduledAt, platforms } = req.body;
749
+ if (!scheduledAt) {
750
+ return sendError3(res, 400, "VALIDATION_ERROR", "scheduledAt is required");
751
+ }
752
+ const { db } = initializeDatabase(teamId);
753
+ const updateData = {
754
+ scheduledAt,
755
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
756
+ };
757
+ if (platforms) {
758
+ updateData.platforms = JSON.stringify(platforms);
759
+ }
760
+ const result = await db.update(posts).set(updateData).where((0, import_drizzle_orm4.and)(
761
+ (0, import_drizzle_orm4.eq)(posts.id, req.params.id),
762
+ (0, import_drizzle_orm4.eq)(posts.teamId, teamId),
763
+ (0, import_drizzle_orm4.eq)(posts.status, "scheduled")
764
+ ));
765
+ if (result.changes === 0) {
766
+ return sendError3(res, 404, "SCHEDULED_POST_NOT_FOUND", "Scheduled post not found");
767
+ }
768
+ res.json({ success: true, scheduledAt });
769
+ } catch (error) {
770
+ sendError3(res, 500, "DATABASE_ERROR", error.message);
771
+ }
772
+ });
773
+ app.post("/calendar/bulk-schedule", async (req, res) => {
774
+ try {
775
+ const teamId = getTeamId3(req);
776
+ const userId = req.headers["x-user-id"] || "system";
777
+ if (!teamId) return sendError3(res, 400, "MISSING_TEAM_ID", "Team ID is required");
778
+ const { posts: posts2 } = req.body;
779
+ if (!Array.isArray(posts2) || posts2.length === 0) {
780
+ return sendError3(res, 400, "VALIDATION_ERROR", "Posts array is required");
781
+ }
782
+ const { db } = initializeDatabase(teamId);
783
+ const now = (/* @__PURE__ */ new Date()).toISOString();
784
+ const createdPosts = [];
785
+ for (const postData of posts2) {
786
+ if (!postData.content || !postData.scheduledAt || !postData.platforms?.length) {
787
+ continue;
788
+ }
789
+ const newPost = {
790
+ id: require("crypto").randomUUID(),
791
+ teamId,
792
+ content: postData.content,
793
+ platforms: JSON.stringify(postData.platforms),
794
+ status: "scheduled",
795
+ scheduledAt: postData.scheduledAt,
796
+ publishedAt: null,
797
+ tags: JSON.stringify(postData.tags || []),
798
+ mediaIds: JSON.stringify(postData.mediaIds || []),
799
+ templateId: postData.templateId || null,
800
+ createdAt: now,
801
+ updatedAt: now,
802
+ createdBy: userId
803
+ };
804
+ await db.insert(posts).values(newPost);
805
+ createdPosts.push({
806
+ id: newPost.id,
807
+ content: newPost.content,
808
+ platforms: JSON.parse(newPost.platforms),
809
+ status: newPost.status,
810
+ scheduledAt: newPost.scheduledAt || void 0,
811
+ tags: JSON.parse(newPost.tags),
812
+ mediaIds: JSON.parse(newPost.mediaIds),
813
+ templateId: newPost.templateId || void 0,
814
+ createdAt: newPost.createdAt,
815
+ updatedAt: newPost.updatedAt,
816
+ createdBy: newPost.createdBy
817
+ });
818
+ }
819
+ res.status(201).json({
820
+ success: true,
821
+ created: createdPosts.length,
822
+ posts: createdPosts
823
+ });
824
+ } catch (error) {
825
+ sendError3(res, 500, "DATABASE_ERROR", error.message);
826
+ }
827
+ });
828
+ }
829
+ var import_drizzle_orm4;
830
+ var init_calendar = __esm({
831
+ "src/api/calendar.ts"() {
832
+ import_drizzle_orm4 = require("drizzle-orm");
833
+ init_db();
834
+ init_schema();
835
+ }
836
+ });
837
+
838
+ // src/api/webhooks.ts
839
+ var webhooks_exports = {};
840
+ __export(webhooks_exports, {
841
+ registerWebhookRoutes: () => registerWebhookRoutes
842
+ });
843
+ function getTeamId4(req) {
844
+ return req.headers["x-team-id"] || req.query.teamId;
845
+ }
846
+ function sendError4(res, status, error, message, details) {
847
+ const response = { error, message, details };
848
+ res.status(status).json(response);
849
+ }
850
+ function registerWebhookRoutes(app) {
851
+ app.get("/webhooks", async (req, res) => {
852
+ try {
853
+ const teamId = getTeamId4(req);
854
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
855
+ const { db } = initializeDatabase(teamId);
856
+ const webhooks2 = await db.select().from(webhooks).where((0, import_drizzle_orm5.eq)(webhooks.teamId, teamId)).orderBy((0, import_drizzle_orm5.desc)(webhooks.createdAt));
857
+ const response = webhooks2.map((webhook) => ({
858
+ id: webhook.id,
859
+ url: webhook.url,
860
+ events: JSON.parse(webhook.events || "[]"),
861
+ isActive: webhook.isActive,
862
+ createdAt: webhook.createdAt,
863
+ lastTriggered: webhook.lastTriggered || void 0
864
+ }));
865
+ res.json({ webhooks: response });
866
+ } catch (error) {
867
+ sendError4(res, 500, "DATABASE_ERROR", error.message);
868
+ }
869
+ });
870
+ app.post("/webhooks", async (req, res) => {
871
+ try {
872
+ const teamId = getTeamId4(req);
873
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
874
+ const body = req.body;
875
+ if (!body.url || !body.events?.length) {
876
+ return sendError4(res, 400, "VALIDATION_ERROR", "URL and events are required");
877
+ }
878
+ try {
879
+ new URL(body.url);
880
+ } catch {
881
+ return sendError4(res, 400, "INVALID_URL", "Invalid webhook URL");
882
+ }
883
+ const { db } = initializeDatabase(teamId);
884
+ const now = (/* @__PURE__ */ new Date()).toISOString();
885
+ const newWebhook = {
886
+ id: (0, import_crypto4.randomUUID)(),
887
+ teamId,
888
+ url: body.url,
889
+ events: JSON.stringify(body.events),
890
+ secret: body.secret || null,
891
+ isActive: true,
892
+ createdAt: now,
893
+ lastTriggered: null
894
+ };
895
+ await db.insert(webhooks).values(newWebhook);
896
+ const response = {
897
+ id: newWebhook.id,
898
+ url: newWebhook.url,
899
+ events: JSON.parse(newWebhook.events),
900
+ isActive: newWebhook.isActive,
901
+ createdAt: newWebhook.createdAt
902
+ };
903
+ res.status(201).json(response);
904
+ } catch (error) {
905
+ sendError4(res, 500, "DATABASE_ERROR", error.message);
906
+ }
907
+ });
908
+ app.get("/webhooks/:id", async (req, res) => {
909
+ try {
910
+ const teamId = getTeamId4(req);
911
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
912
+ const { db } = initializeDatabase(teamId);
913
+ const webhook = await db.select().from(webhooks).where((0, import_drizzle_orm5.and)(
914
+ (0, import_drizzle_orm5.eq)(webhooks.id, req.params.id),
915
+ (0, import_drizzle_orm5.eq)(webhooks.teamId, teamId)
916
+ )).get();
917
+ if (!webhook) {
918
+ return sendError4(res, 404, "WEBHOOK_NOT_FOUND", "Webhook not found");
919
+ }
920
+ const response = {
921
+ id: webhook.id,
922
+ url: webhook.url,
923
+ events: JSON.parse(webhook.events || "[]"),
924
+ isActive: webhook.isActive,
925
+ createdAt: webhook.createdAt,
926
+ lastTriggered: webhook.lastTriggered || void 0
927
+ };
928
+ res.json(response);
929
+ } catch (error) {
930
+ sendError4(res, 500, "DATABASE_ERROR", error.message);
931
+ }
932
+ });
933
+ app.put("/webhooks/:id", async (req, res) => {
934
+ try {
935
+ const teamId = getTeamId4(req);
936
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
937
+ const { db } = initializeDatabase(teamId);
938
+ const body = req.body;
939
+ const updateData = {};
940
+ if (body.url !== void 0) {
941
+ try {
942
+ new URL(body.url);
943
+ updateData.url = body.url;
944
+ } catch {
945
+ return sendError4(res, 400, "INVALID_URL", "Invalid webhook URL");
946
+ }
947
+ }
948
+ if (body.events !== void 0) updateData.events = JSON.stringify(body.events);
949
+ if (body.secret !== void 0) updateData.secret = body.secret;
950
+ if (body.isActive !== void 0) updateData.isActive = body.isActive;
951
+ const result = await db.update(webhooks).set(updateData).where((0, import_drizzle_orm5.and)(
952
+ (0, import_drizzle_orm5.eq)(webhooks.id, req.params.id),
953
+ (0, import_drizzle_orm5.eq)(webhooks.teamId, teamId)
954
+ ));
955
+ if (result.changes === 0) {
956
+ return sendError4(res, 404, "WEBHOOK_NOT_FOUND", "Webhook not found");
957
+ }
958
+ res.json({ success: true });
959
+ } catch (error) {
960
+ sendError4(res, 500, "DATABASE_ERROR", error.message);
961
+ }
962
+ });
963
+ app.delete("/webhooks/:id", async (req, res) => {
964
+ try {
965
+ const teamId = getTeamId4(req);
966
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
967
+ const { db } = initializeDatabase(teamId);
968
+ const result = await db.delete(webhooks).where((0, import_drizzle_orm5.and)(
969
+ (0, import_drizzle_orm5.eq)(webhooks.id, req.params.id),
970
+ (0, import_drizzle_orm5.eq)(webhooks.teamId, teamId)
971
+ ));
972
+ if (result.changes === 0) {
973
+ return sendError4(res, 404, "WEBHOOK_NOT_FOUND", "Webhook not found");
974
+ }
975
+ res.status(204).send();
976
+ } catch (error) {
977
+ sendError4(res, 500, "DATABASE_ERROR", error.message);
978
+ }
979
+ });
980
+ app.post("/webhooks/test", async (req, res) => {
981
+ try {
982
+ const teamId = getTeamId4(req);
983
+ if (!teamId) return sendError4(res, 400, "MISSING_TEAM_ID", "Team ID is required");
984
+ const { webhookId, event, data } = req.body;
985
+ if (!webhookId || !event) {
986
+ return sendError4(res, 400, "VALIDATION_ERROR", "webhookId and event are required");
987
+ }
988
+ const { db } = initializeDatabase(teamId);
989
+ const webhook = await db.select().from(webhooks).where((0, import_drizzle_orm5.and)(
990
+ (0, import_drizzle_orm5.eq)(webhooks.id, webhookId),
991
+ (0, import_drizzle_orm5.eq)(webhooks.teamId, teamId),
992
+ (0, import_drizzle_orm5.eq)(webhooks.isActive, true)
993
+ )).get();
994
+ if (!webhook) {
995
+ return sendError4(res, 404, "WEBHOOK_NOT_FOUND", "Active webhook not found");
996
+ }
997
+ const events = JSON.parse(webhook.events || "[]");
998
+ if (!events.includes(event)) {
999
+ return sendError4(res, 400, "EVENT_NOT_SUBSCRIBED", "Webhook not subscribed to this event");
1000
+ }
1001
+ const payload = {
1002
+ event,
1003
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1004
+ teamId,
1005
+ data: data || { test: true }
1006
+ };
1007
+ try {
1008
+ const response = await fetch(webhook.url, {
1009
+ method: "POST",
1010
+ headers: {
1011
+ "Content-Type": "application/json",
1012
+ "User-Agent": "KitchenPlugin-Marketing/1.0",
1013
+ ...webhook.secret && {
1014
+ "X-Webhook-Signature": require("crypto").createHmac("sha256", webhook.secret).update(JSON.stringify(payload)).digest("hex")
1015
+ }
1016
+ },
1017
+ body: JSON.stringify(payload),
1018
+ signal: AbortSignal.timeout(1e4)
1019
+ });
1020
+ const responseText = await response.text();
1021
+ await db.update(webhooks).set({ lastTriggered: (/* @__PURE__ */ new Date()).toISOString() }).where((0, import_drizzle_orm5.eq)(webhooks.id, webhookId));
1022
+ res.json({
1023
+ success: response.ok,
1024
+ status: response.status,
1025
+ response: responseText,
1026
+ url: webhook.url
1027
+ });
1028
+ } catch (error) {
1029
+ res.json({
1030
+ success: false,
1031
+ error: error.message,
1032
+ url: webhook.url
1033
+ });
1034
+ }
1035
+ } catch (error) {
1036
+ sendError4(res, 500, "WEBHOOK_TEST_ERROR", error.message);
1037
+ }
1038
+ });
1039
+ }
1040
+ var import_drizzle_orm5, import_crypto4;
1041
+ var init_webhooks = __esm({
1042
+ "src/api/webhooks.ts"() {
1043
+ import_drizzle_orm5 = require("drizzle-orm");
1044
+ init_db();
1045
+ init_schema();
1046
+ import_crypto4 = require("crypto");
1047
+ }
1048
+ });
1049
+
1050
+ // src/api/routes.ts
1051
+ var routes_exports = {};
1052
+ __export(routes_exports, {
1053
+ default: () => createRoutes
1054
+ });
1055
+ module.exports = __toCommonJS(routes_exports);
1056
+ var import_drizzle_orm6 = require("drizzle-orm");
1057
+ init_db();
1058
+ init_schema();
1059
+ var import_crypto5 = require("crypto");
1060
+ var import_multer = __toESM(require("multer"));
1061
+ var import_path = __toESM(require("path"));
1062
+ var import_promises = __toESM(require("fs/promises"));
1063
+ var upload = (0, import_multer.default)({
1064
+ dest: "uploads/",
1065
+ limits: {
1066
+ fileSize: 10 * 1024 * 1024
1067
+ // 10MB limit
1068
+ },
1069
+ fileFilter: (req, file, cb) => {
1070
+ const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp", "video/mp4"];
1071
+ if (allowedTypes.includes(file.mimetype)) {
1072
+ cb(null, true);
1073
+ } else {
1074
+ cb(new Error("Invalid file type"));
1075
+ }
1076
+ }
1077
+ });
1078
+ function getTeamId5(req) {
1079
+ return req.headers["x-team-id"] || req.query.teamId;
1080
+ }
1081
+ function getUserId3(req) {
1082
+ return req.headers["x-user-id"] || "system";
1083
+ }
1084
+ function sendError5(res, status, error, message, details) {
1085
+ const response = { error, message, details };
1086
+ res.status(status).json(response);
1087
+ }
1088
+ function parsePagination2(req) {
1089
+ const limit = Math.min(parseInt(req.query.limit) || 20, 100);
1090
+ const offset = parseInt(req.query.offset) || 0;
1091
+ return { limit, offset };
1092
+ }
1093
+ function createRoutes(app) {
1094
+ app.get("/posts", async (req, res) => {
1095
+ try {
1096
+ const teamId = getTeamId5(req);
1097
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1098
+ const { db } = initializeDatabase(teamId);
1099
+ const { limit, offset } = parsePagination2(req);
1100
+ const conditions = [(0, import_drizzle_orm6.eq)(posts.teamId, teamId)];
1101
+ if (req.query.status) {
1102
+ conditions.push((0, import_drizzle_orm6.eq)(posts.status, req.query.status));
1103
+ }
1104
+ if (req.query.platform) {
1105
+ conditions.push((0, import_drizzle_orm6.like)(posts.platforms, `%"${req.query.platform}"%`));
1106
+ }
1107
+ if (req.query.tag) {
1108
+ conditions.push((0, import_drizzle_orm6.like)(posts.tags, `%"${req.query.tag}"%`));
1109
+ }
1110
+ const totalResult = await db.select({ count: import_drizzle_orm6.sql`count(*)` }).from(posts).where((0, import_drizzle_orm6.and)(...conditions));
1111
+ const total = totalResult[0].count;
1112
+ const posts2 = await db.select().from(posts).where((0, import_drizzle_orm6.and)(...conditions)).orderBy((0, import_drizzle_orm6.desc)(posts.createdAt)).limit(limit).offset(offset);
1113
+ const transformedPosts = posts2.map((post) => ({
1114
+ id: post.id,
1115
+ content: post.content,
1116
+ platforms: JSON.parse(post.platforms || "[]"),
1117
+ status: post.status,
1118
+ scheduledAt: post.scheduledAt || void 0,
1119
+ publishedAt: post.publishedAt || void 0,
1120
+ tags: JSON.parse(post.tags || "[]"),
1121
+ mediaIds: JSON.parse(post.mediaIds || "[]"),
1122
+ templateId: post.templateId || void 0,
1123
+ createdAt: post.createdAt,
1124
+ updatedAt: post.updatedAt,
1125
+ createdBy: post.createdBy
1126
+ }));
1127
+ const response = {
1128
+ data: transformedPosts,
1129
+ total,
1130
+ offset,
1131
+ limit,
1132
+ hasMore: offset + limit < total
1133
+ };
1134
+ res.json(response);
1135
+ } catch (error) {
1136
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1137
+ }
1138
+ });
1139
+ app.post("/posts", async (req, res) => {
1140
+ try {
1141
+ const teamId = getTeamId5(req);
1142
+ const userId = getUserId3(req);
1143
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1144
+ const body = req.body;
1145
+ if (!body.content || !body.platforms?.length) {
1146
+ return sendError5(res, 400, "VALIDATION_ERROR", "Content and platforms are required");
1147
+ }
1148
+ const { db } = initializeDatabase(teamId);
1149
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1150
+ const newPost = {
1151
+ id: (0, import_crypto5.randomUUID)(),
1152
+ teamId,
1153
+ content: body.content,
1154
+ platforms: JSON.stringify(body.platforms),
1155
+ status: body.status || "draft",
1156
+ scheduledAt: body.scheduledAt || null,
1157
+ publishedAt: null,
1158
+ tags: JSON.stringify(body.tags || []),
1159
+ mediaIds: JSON.stringify(body.mediaIds || []),
1160
+ templateId: body.templateId || null,
1161
+ createdAt: now,
1162
+ updatedAt: now,
1163
+ createdBy: userId
1164
+ };
1165
+ await db.insert(posts).values(newPost);
1166
+ const response = {
1167
+ id: newPost.id,
1168
+ content: newPost.content,
1169
+ platforms: JSON.parse(newPost.platforms),
1170
+ status: newPost.status,
1171
+ scheduledAt: newPost.scheduledAt || void 0,
1172
+ publishedAt: void 0,
1173
+ tags: JSON.parse(newPost.tags),
1174
+ mediaIds: JSON.parse(newPost.mediaIds),
1175
+ templateId: newPost.templateId || void 0,
1176
+ createdAt: newPost.createdAt,
1177
+ updatedAt: newPost.updatedAt,
1178
+ createdBy: newPost.createdBy
1179
+ };
1180
+ res.status(201).json(response);
1181
+ } catch (error) {
1182
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1183
+ }
1184
+ });
1185
+ app.get("/posts/:id", async (req, res) => {
1186
+ try {
1187
+ const teamId = getTeamId5(req);
1188
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1189
+ const { db } = initializeDatabase(teamId);
1190
+ const post = await db.select().from(posts).where((0, import_drizzle_orm6.and)(
1191
+ (0, import_drizzle_orm6.eq)(posts.id, req.params.id),
1192
+ (0, import_drizzle_orm6.eq)(posts.teamId, teamId)
1193
+ )).get();
1194
+ if (!post) {
1195
+ return sendError5(res, 404, "POST_NOT_FOUND", "Post not found");
1196
+ }
1197
+ const metrics = await db.select().from(postMetrics).where((0, import_drizzle_orm6.eq)(postMetrics.postId, post.id));
1198
+ const platformMetrics = {};
1199
+ metrics.forEach((metric) => {
1200
+ platformMetrics[metric.platform] = {
1201
+ impressions: metric.impressions || 0,
1202
+ likes: metric.likes || 0,
1203
+ shares: metric.shares || 0,
1204
+ comments: metric.comments || 0,
1205
+ clicks: metric.clicks || 0,
1206
+ engagementRate: parseFloat(metric.engagementRate || "0")
1207
+ };
1208
+ });
1209
+ const response = {
1210
+ id: post.id,
1211
+ content: post.content,
1212
+ platforms: JSON.parse(post.platforms || "[]"),
1213
+ status: post.status,
1214
+ scheduledAt: post.scheduledAt || void 0,
1215
+ publishedAt: post.publishedAt || void 0,
1216
+ tags: JSON.parse(post.tags || "[]"),
1217
+ mediaIds: JSON.parse(post.mediaIds || "[]"),
1218
+ templateId: post.templateId || void 0,
1219
+ createdAt: post.createdAt,
1220
+ updatedAt: post.updatedAt,
1221
+ createdBy: post.createdBy,
1222
+ metrics: Object.keys(platformMetrics).length > 0 ? platformMetrics : void 0
1223
+ };
1224
+ res.json(response);
1225
+ } catch (error) {
1226
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1227
+ }
1228
+ });
1229
+ app.put("/posts/:id", async (req, res) => {
1230
+ try {
1231
+ const teamId = getTeamId5(req);
1232
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1233
+ const { db } = initializeDatabase(teamId);
1234
+ const body = req.body;
1235
+ const updateData = {
1236
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1237
+ };
1238
+ if (body.content !== void 0) updateData.content = body.content;
1239
+ if (body.platforms !== void 0) updateData.platforms = JSON.stringify(body.platforms);
1240
+ if (body.status !== void 0) updateData.status = body.status;
1241
+ if (body.scheduledAt !== void 0) updateData.scheduledAt = body.scheduledAt;
1242
+ if (body.tags !== void 0) updateData.tags = JSON.stringify(body.tags);
1243
+ if (body.mediaIds !== void 0) updateData.mediaIds = JSON.stringify(body.mediaIds);
1244
+ const result = await db.update(posts).set(updateData).where((0, import_drizzle_orm6.and)(
1245
+ (0, import_drizzle_orm6.eq)(posts.id, req.params.id),
1246
+ (0, import_drizzle_orm6.eq)(posts.teamId, teamId)
1247
+ ));
1248
+ if (result.changes === 0) {
1249
+ return sendError5(res, 404, "POST_NOT_FOUND", "Post not found");
1250
+ }
1251
+ const updatedPost = await db.select().from(posts).where((0, import_drizzle_orm6.eq)(posts.id, req.params.id)).get();
1252
+ const response = {
1253
+ id: updatedPost.id,
1254
+ content: updatedPost.content,
1255
+ platforms: JSON.parse(updatedPost.platforms || "[]"),
1256
+ status: updatedPost.status,
1257
+ scheduledAt: updatedPost.scheduledAt || void 0,
1258
+ publishedAt: updatedPost.publishedAt || void 0,
1259
+ tags: JSON.parse(updatedPost.tags || "[]"),
1260
+ mediaIds: JSON.parse(updatedPost.mediaIds || "[]"),
1261
+ templateId: updatedPost.templateId || void 0,
1262
+ createdAt: updatedPost.createdAt,
1263
+ updatedAt: updatedPost.updatedAt,
1264
+ createdBy: updatedPost.createdBy
1265
+ };
1266
+ res.json(response);
1267
+ } catch (error) {
1268
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1269
+ }
1270
+ });
1271
+ app.delete("/posts/:id", async (req, res) => {
1272
+ try {
1273
+ const teamId = getTeamId5(req);
1274
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1275
+ const { db } = initializeDatabase(teamId);
1276
+ const result = await db.delete(posts).where((0, import_drizzle_orm6.and)(
1277
+ (0, import_drizzle_orm6.eq)(posts.id, req.params.id),
1278
+ (0, import_drizzle_orm6.eq)(posts.teamId, teamId)
1279
+ ));
1280
+ if (result.changes === 0) {
1281
+ return sendError5(res, 404, "POST_NOT_FOUND", "Post not found");
1282
+ }
1283
+ await db.delete(postMetrics).where((0, import_drizzle_orm6.eq)(postMetrics.postId, req.params.id));
1284
+ res.status(204).send();
1285
+ } catch (error) {
1286
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1287
+ }
1288
+ });
1289
+ app.post("/posts/:id/publish", async (req, res) => {
1290
+ try {
1291
+ const teamId = getTeamId5(req);
1292
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1293
+ const { db } = initializeDatabase(teamId);
1294
+ const { platforms } = req.body;
1295
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1296
+ const updateData = {
1297
+ status: "published",
1298
+ publishedAt: now,
1299
+ updatedAt: now
1300
+ };
1301
+ if (platforms) {
1302
+ updateData.platforms = JSON.stringify(platforms);
1303
+ }
1304
+ const result = await db.update(posts).set(updateData).where((0, import_drizzle_orm6.and)(
1305
+ (0, import_drizzle_orm6.eq)(posts.id, req.params.id),
1306
+ (0, import_drizzle_orm6.eq)(posts.teamId, teamId)
1307
+ ));
1308
+ if (result.changes === 0) {
1309
+ return sendError5(res, 404, "POST_NOT_FOUND", "Post not found");
1310
+ }
1311
+ res.json({ success: true, publishedAt: now });
1312
+ } catch (error) {
1313
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1314
+ }
1315
+ });
1316
+ app.get("/media", async (req, res) => {
1317
+ try {
1318
+ const teamId = getTeamId5(req);
1319
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1320
+ const { db } = initializeDatabase(teamId);
1321
+ const { limit, offset } = parsePagination2(req);
1322
+ const conditions = [(0, import_drizzle_orm6.eq)(media.teamId, teamId)];
1323
+ if (req.query.tag) {
1324
+ conditions.push((0, import_drizzle_orm6.like)(media.tags, `%"${req.query.tag}"%`));
1325
+ }
1326
+ if (req.query.type) {
1327
+ conditions.push((0, import_drizzle_orm6.like)(media.mimeType, `${req.query.type}%`));
1328
+ }
1329
+ const media2 = await db.select().from(media).where((0, import_drizzle_orm6.and)(...conditions)).orderBy((0, import_drizzle_orm6.desc)(media.createdAt)).limit(limit).offset(offset);
1330
+ const response = media2.map((item) => ({
1331
+ id: item.id,
1332
+ filename: item.filename,
1333
+ originalName: item.originalName,
1334
+ mimeType: item.mimeType,
1335
+ size: item.size,
1336
+ width: item.width || void 0,
1337
+ height: item.height || void 0,
1338
+ alt: item.alt || void 0,
1339
+ tags: JSON.parse(item.tags || "[]"),
1340
+ url: item.url,
1341
+ thumbnailUrl: item.thumbnailUrl || void 0,
1342
+ createdAt: item.createdAt,
1343
+ createdBy: item.createdBy
1344
+ }));
1345
+ res.json({ media: response });
1346
+ } catch (error) {
1347
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1348
+ }
1349
+ });
1350
+ app.post("/media", upload.single("file"), async (req, res) => {
1351
+ try {
1352
+ const teamId = getTeamId5(req);
1353
+ const userId = getUserId3(req);
1354
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1355
+ if (!req.file) {
1356
+ return sendError5(res, 400, "NO_FILE", "File is required");
1357
+ }
1358
+ const { db } = initializeDatabase(teamId);
1359
+ const body = req.body;
1360
+ const mediaId = (0, import_crypto5.randomUUID)();
1361
+ const filename = `${mediaId}${import_path.default.extname(req.file.originalname)}`;
1362
+ const mediaDir = `./uploads/media/${teamId}`;
1363
+ await import_promises.default.mkdir(mediaDir, { recursive: true });
1364
+ const finalPath = import_path.default.join(mediaDir, filename);
1365
+ await import_promises.default.rename(req.file.path, finalPath);
1366
+ const newMedia = {
1367
+ id: mediaId,
1368
+ teamId,
1369
+ filename,
1370
+ originalName: req.file.originalname,
1371
+ mimeType: req.file.mimetype,
1372
+ size: req.file.size,
1373
+ width: null,
1374
+ // TODO: Extract dimensions for images
1375
+ height: null,
1376
+ alt: body.alt || null,
1377
+ tags: JSON.stringify(body.tags || []),
1378
+ url: `/api/plugins/kitchen-plugin-marketing/media/${mediaId}/file`,
1379
+ thumbnailUrl: null,
1380
+ // TODO: Generate thumbnails
1381
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1382
+ createdBy: userId
1383
+ };
1384
+ await db.insert(media).values(newMedia);
1385
+ const response = {
1386
+ id: newMedia.id,
1387
+ filename: newMedia.filename,
1388
+ originalName: newMedia.originalName,
1389
+ mimeType: newMedia.mimeType,
1390
+ size: newMedia.size,
1391
+ alt: newMedia.alt || void 0,
1392
+ tags: JSON.parse(newMedia.tags),
1393
+ url: newMedia.url,
1394
+ createdAt: newMedia.createdAt,
1395
+ createdBy: newMedia.createdBy
1396
+ };
1397
+ res.status(201).json(response);
1398
+ } catch (error) {
1399
+ sendError5(res, 500, "UPLOAD_ERROR", error.message);
1400
+ }
1401
+ });
1402
+ app.get("/media/:id/file", async (req, res) => {
1403
+ try {
1404
+ const teamId = getTeamId5(req);
1405
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1406
+ const { db } = initializeDatabase(teamId);
1407
+ const media2 = await db.select().from(media).where((0, import_drizzle_orm6.and)(
1408
+ (0, import_drizzle_orm6.eq)(media.id, req.params.id),
1409
+ (0, import_drizzle_orm6.eq)(media.teamId, teamId)
1410
+ )).get();
1411
+ if (!media2) {
1412
+ return sendError5(res, 404, "MEDIA_NOT_FOUND", "Media not found");
1413
+ }
1414
+ const filePath = `./uploads/media/${teamId}/${media2.filename}`;
1415
+ res.set("Content-Type", media2.mimeType);
1416
+ res.sendFile(import_path.default.resolve(filePath));
1417
+ } catch (error) {
1418
+ sendError5(res, 500, "FILE_ERROR", error.message);
1419
+ }
1420
+ });
1421
+ app.get("/analytics/overview", async (req, res) => {
1422
+ try {
1423
+ const teamId = getTeamId5(req);
1424
+ if (!teamId) return sendError5(res, 400, "MISSING_TEAM_ID", "Team ID is required");
1425
+ const { db } = initializeDatabase(teamId);
1426
+ const period = req.query.period || "30d";
1427
+ const endDate = /* @__PURE__ */ new Date();
1428
+ const startDate = /* @__PURE__ */ new Date();
1429
+ const days = parseInt(period.replace("d", "")) || 30;
1430
+ startDate.setDate(endDate.getDate() - days);
1431
+ const metrics = await db.select({
1432
+ platform: postMetrics.platform,
1433
+ totalImpressions: import_drizzle_orm6.sql`sum(${postMetrics.impressions})`,
1434
+ totalLikes: import_drizzle_orm6.sql`sum(${postMetrics.likes})`,
1435
+ totalShares: import_drizzle_orm6.sql`sum(${postMetrics.shares})`,
1436
+ totalComments: import_drizzle_orm6.sql`sum(${postMetrics.comments})`,
1437
+ totalClicks: import_drizzle_orm6.sql`sum(${postMetrics.clicks})`,
1438
+ postCount: import_drizzle_orm6.sql`count(distinct ${postMetrics.postId})`
1439
+ }).from(postMetrics).innerJoin(posts, (0, import_drizzle_orm6.eq)(posts.id, postMetrics.postId)).where((0, import_drizzle_orm6.and)(
1440
+ (0, import_drizzle_orm6.eq)(posts.teamId, teamId),
1441
+ (0, import_drizzle_orm6.gte)(posts.publishedAt, startDate.toISOString())
1442
+ )).groupBy(postMetrics.platform);
1443
+ let totalPosts = 0;
1444
+ let totalImpressions = 0;
1445
+ let totalEngagements = 0;
1446
+ let totalClicks = 0;
1447
+ const platformBreakdown = {};
1448
+ metrics.forEach((metric) => {
1449
+ const engagements = (metric.totalLikes || 0) + (metric.totalShares || 0) + (metric.totalComments || 0);
1450
+ totalPosts += metric.postCount || 0;
1451
+ totalImpressions += metric.totalImpressions || 0;
1452
+ totalEngagements += engagements;
1453
+ totalClicks += metric.totalClicks || 0;
1454
+ platformBreakdown[metric.platform] = {
1455
+ posts: metric.postCount || 0,
1456
+ impressions: metric.totalImpressions || 0,
1457
+ engagements
1458
+ };
1459
+ });
1460
+ const response = {
1461
+ period,
1462
+ metrics: {
1463
+ totalPosts,
1464
+ totalImpressions,
1465
+ totalEngagements,
1466
+ totalClicks,
1467
+ engagementRate: totalImpressions > 0 ? totalEngagements / totalImpressions * 100 : 0,
1468
+ averageImpressions: totalPosts > 0 ? totalImpressions / totalPosts : 0
1469
+ },
1470
+ platformBreakdown
1471
+ };
1472
+ res.json(response);
1473
+ } catch (error) {
1474
+ sendError5(res, 500, "DATABASE_ERROR", error.message);
1475
+ }
1476
+ });
1477
+ const { registerTemplateRoutes: registerTemplateRoutes2 } = (init_templates(), __toCommonJS(templates_exports));
1478
+ const { registerSocialAccountRoutes: registerSocialAccountRoutes2 } = (init_social_accounts(), __toCommonJS(social_accounts_exports));
1479
+ const { registerCalendarRoutes: registerCalendarRoutes2 } = (init_calendar(), __toCommonJS(calendar_exports));
1480
+ const { registerWebhookRoutes: registerWebhookRoutes2 } = (init_webhooks(), __toCommonJS(webhooks_exports));
1481
+ registerTemplateRoutes2(app);
1482
+ registerSocialAccountRoutes2(app);
1483
+ registerCalendarRoutes2(app);
1484
+ registerWebhookRoutes2(app);
1485
+ console.log("Marketing plugin API routes registered");
1486
+ }