@jiggai/kitchen-plugin-marketing 0.2.10 → 0.2.12
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/dist/api/handler.js +6 -2
- package/dist/tabs/accounts.js +11 -2
- package/package.json +3 -7
- package/dist/api/routes.js +0 -1486
package/dist/api/routes.js
DELETED
|
@@ -1,1486 +0,0 @@
|
|
|
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
|
-
}
|