shuttlepro-shared 1.4.1 → 1.4.3
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/common/repositories/call.repository.js +209 -220
- package/common/repositories/index.js +2 -0
- package/common/repositories/interactive.repository.js +124 -0
- package/models/Automation.js +10 -0
- package/models/Call.js +9 -1
- package/models/Card.js +5 -0
- package/models/Chatbot.js +1 -0
- package/models/Interactive.js +70 -0
- package/models.js +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const Call = require("../../models/Call");
|
|
2
|
-
const mongoose = require("mongoose");
|
|
3
2
|
|
|
4
3
|
class CallRepository {
|
|
5
4
|
constructor() {
|
|
@@ -9,39 +8,39 @@ class CallRepository {
|
|
|
9
8
|
};
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
// Create a new call
|
|
11
|
+
// Create a new call
|
|
13
12
|
async createCall(callData) {
|
|
14
|
-
const session = await mongoose.startSession();
|
|
15
|
-
|
|
16
13
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
14
|
+
const historyEntry = {
|
|
15
|
+
timestamp: new Date(),
|
|
16
|
+
action: "created",
|
|
17
|
+
agentId: null,
|
|
18
|
+
details: "Call created in system",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const call = await Call.create({
|
|
22
|
+
callId: callData.callId,
|
|
23
|
+
callerName: callData.callerName,
|
|
24
|
+
callerNumber: callData.callerNumber,
|
|
25
|
+
whatsappSdp: callData.whatsappSdp,
|
|
26
|
+
status: callData.status || "pending",
|
|
27
|
+
autoAccepted: callData.autoAccepted || false,
|
|
28
|
+
metadata: callData.metadata || {},
|
|
29
|
+
tags: callData.tags || [],
|
|
30
|
+
priority: callData.priority || 0,
|
|
31
|
+
platformId: callData.platformId || null,
|
|
32
|
+
workspaceId: callData.workspaceId || null,
|
|
33
|
+
profileId: callData.profileId || null,
|
|
34
|
+
platformTimestamp: callData.platformTimestamp || "",
|
|
35
|
+
receiver: callData.receiver || "",
|
|
36
|
+
callHistory: [historyEntry],
|
|
39
37
|
});
|
|
38
|
+
|
|
39
|
+
await this._updateCallMetrics("created");
|
|
40
|
+
return call;
|
|
40
41
|
} catch (error) {
|
|
41
42
|
console.error(`Failed to create call ${callData.callId}:`, error);
|
|
42
|
-
|
|
43
|
-
} finally {
|
|
44
|
-
await session.endSession();
|
|
43
|
+
return null;
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -52,7 +51,6 @@ class CallRepository {
|
|
|
52
51
|
|
|
53
52
|
if (options.lean) query.lean();
|
|
54
53
|
|
|
55
|
-
// Auto-populate unless explicitly disabled
|
|
56
54
|
if (options.populate !== false) {
|
|
57
55
|
query.populate([
|
|
58
56
|
{ path: "platformId", model: "Integration" },
|
|
@@ -68,11 +66,11 @@ class CallRepository {
|
|
|
68
66
|
return await query.exec();
|
|
69
67
|
} catch (error) {
|
|
70
68
|
console.error(`Failed to get call ${callId}:`, error);
|
|
71
|
-
|
|
69
|
+
return null;
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
// Bulk get calls
|
|
73
|
+
// Bulk get calls
|
|
76
74
|
async getCalls(filters = {}, options = {}) {
|
|
77
75
|
try {
|
|
78
76
|
const {
|
|
@@ -90,7 +88,6 @@ class CallRepository {
|
|
|
90
88
|
} = filters;
|
|
91
89
|
|
|
92
90
|
const query = {};
|
|
93
|
-
|
|
94
91
|
if (status)
|
|
95
92
|
query.status = Array.isArray(status) ? { $in: status } : status;
|
|
96
93
|
if (agentId) query.agentId = agentId;
|
|
@@ -106,7 +103,7 @@ class CallRepository {
|
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
if (priority !== undefined) query.priority = { $gte: priority };
|
|
109
|
-
if (tags
|
|
106
|
+
if (tags?.length > 0) query.tags = { $in: tags };
|
|
110
107
|
|
|
111
108
|
const skip = (page - 1) * limit;
|
|
112
109
|
|
|
@@ -140,132 +137,127 @@ class CallRepository {
|
|
|
140
137
|
};
|
|
141
138
|
} catch (error) {
|
|
142
139
|
console.error("Failed to get calls:", error);
|
|
143
|
-
|
|
140
|
+
return {
|
|
141
|
+
calls: [],
|
|
142
|
+
pagination: { page: 1, limit: 50, total: 0, pages: 0 },
|
|
143
|
+
};
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
// Update call
|
|
148
|
-
async updateCall(callId, updateData
|
|
149
|
-
const session = options.session || (await mongoose.startSession());
|
|
150
|
-
const shouldCommitSession = !options.session;
|
|
151
|
-
|
|
147
|
+
// Update call
|
|
148
|
+
async updateCall(callId, updateData) {
|
|
152
149
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
const allowedFields = [
|
|
151
|
+
"status",
|
|
152
|
+
"agentId",
|
|
153
|
+
"callerName",
|
|
154
|
+
"callerNumber",
|
|
155
|
+
"priority",
|
|
156
|
+
"tags",
|
|
157
|
+
"metadata",
|
|
158
|
+
"isOnHold",
|
|
159
|
+
"platformId",
|
|
160
|
+
"workspaceId",
|
|
161
|
+
"profileId",
|
|
162
|
+
];
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
const filteredUpdate = {};
|
|
165
|
+
Object.keys(updateData).forEach((key) => {
|
|
166
|
+
if (allowedFields.includes(key)) {
|
|
167
|
+
filteredUpdate[key] = updateData[key];
|
|
159
168
|
}
|
|
169
|
+
});
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"tags",
|
|
168
|
-
"metadata",
|
|
169
|
-
"isOnHold",
|
|
170
|
-
"platformId",
|
|
171
|
-
"workspaceId",
|
|
172
|
-
"profileId",
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
Object.keys(updateData).forEach((key) => {
|
|
176
|
-
if (allowedFields.includes(key)) {
|
|
177
|
-
call[key] = updateData[key];
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (updateData.status && updateData.status !== call.status) {
|
|
182
|
-
call.addToHistory(
|
|
183
|
-
updateData.status,
|
|
184
|
-
updateData.agentId || call.agentId,
|
|
185
|
-
updateData.reason || `Status changed to ${updateData.status}`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
171
|
+
const updateOps = {
|
|
172
|
+
$set: {
|
|
173
|
+
...filteredUpdate,
|
|
174
|
+
lastHeartbeat: new Date(),
|
|
175
|
+
},
|
|
176
|
+
};
|
|
188
177
|
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
if (updateData.status) {
|
|
179
|
+
updateOps.$push = {
|
|
180
|
+
callHistory: {
|
|
181
|
+
timestamp: new Date(),
|
|
182
|
+
action: updateData.status,
|
|
183
|
+
agentId: updateData.agentId || null,
|
|
184
|
+
details:
|
|
185
|
+
updateData.reason || `Status changed to ${updateData.status}`,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
191
189
|
|
|
192
|
-
|
|
190
|
+
const call = await Call.findOneAndUpdate({ callId }, updateOps, {
|
|
191
|
+
new: true,
|
|
193
192
|
});
|
|
193
|
+
return call || null;
|
|
194
194
|
} catch (error) {
|
|
195
195
|
console.error(`Failed to update call ${callId}:`, error);
|
|
196
|
-
|
|
197
|
-
} finally {
|
|
198
|
-
if (shouldCommitSession) await session.endSession();
|
|
196
|
+
return null;
|
|
199
197
|
}
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
// Assign call to agent
|
|
203
|
-
async assignCallToAgent(callId, agentId
|
|
204
|
-
const session = await mongoose.startSession();
|
|
205
|
-
|
|
201
|
+
async assignCallToAgent(callId, agentId) {
|
|
206
202
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
203
|
+
const call = await Call.findOneAndUpdate(
|
|
204
|
+
{ callId, status: { $in: ["pending", "hold"] } },
|
|
205
|
+
{
|
|
206
|
+
$set: {
|
|
207
|
+
agentId,
|
|
208
|
+
status: "active",
|
|
209
|
+
startTime: new Date(),
|
|
210
|
+
isOnHold: false,
|
|
211
|
+
lastHeartbeat: new Date(),
|
|
212
|
+
},
|
|
213
|
+
$push: {
|
|
214
|
+
callHistory: {
|
|
215
|
+
timestamp: new Date(),
|
|
216
|
+
action: "answered",
|
|
217
|
+
agentId,
|
|
218
|
+
details: "Call answered by agent",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{ new: true }
|
|
223
|
+
);
|
|
224
|
+
return call || null;
|
|
225
225
|
} catch (error) {
|
|
226
226
|
console.error(
|
|
227
227
|
`Failed to assign call ${callId} to agent ${agentId}:`,
|
|
228
228
|
error
|
|
229
229
|
);
|
|
230
|
-
|
|
231
|
-
} finally {
|
|
232
|
-
await session.endSession();
|
|
230
|
+
return null;
|
|
233
231
|
}
|
|
234
232
|
}
|
|
235
233
|
|
|
236
|
-
// Bulk
|
|
234
|
+
// Bulk update calls
|
|
237
235
|
async bulkUpdateCalls(operations) {
|
|
238
|
-
const session = await mongoose.startSession();
|
|
239
|
-
|
|
240
236
|
try {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
details: op.details || "Bulk operation",
|
|
256
|
-
},
|
|
237
|
+
const bulkOps = operations.map((op) => ({
|
|
238
|
+
updateOne: {
|
|
239
|
+
filter: { callId: op.callId },
|
|
240
|
+
update: {
|
|
241
|
+
$set: {
|
|
242
|
+
...op.updateData,
|
|
243
|
+
lastHeartbeat: new Date(),
|
|
244
|
+
},
|
|
245
|
+
$push: {
|
|
246
|
+
callHistory: {
|
|
247
|
+
timestamp: new Date(),
|
|
248
|
+
action: op.action || "bulk_update",
|
|
249
|
+
agentId: op.agentId,
|
|
250
|
+
details: op.details || "Bulk operation",
|
|
257
251
|
},
|
|
258
252
|
},
|
|
259
253
|
},
|
|
260
|
-
}
|
|
254
|
+
},
|
|
255
|
+
}));
|
|
261
256
|
|
|
262
|
-
|
|
263
|
-
});
|
|
257
|
+
return await Call.bulkWrite(bulkOps);
|
|
264
258
|
} catch (error) {
|
|
265
259
|
console.error("Failed to perform bulk update:", error);
|
|
266
|
-
|
|
267
|
-
} finally {
|
|
268
|
-
await session.endSession();
|
|
260
|
+
return [];
|
|
269
261
|
}
|
|
270
262
|
}
|
|
271
263
|
|
|
@@ -292,7 +284,7 @@ class CallRepository {
|
|
|
292
284
|
return await Call.aggregate(pipeline);
|
|
293
285
|
} catch (error) {
|
|
294
286
|
console.error("Failed to get hold queue:", error);
|
|
295
|
-
|
|
287
|
+
return [];
|
|
296
288
|
}
|
|
297
289
|
}
|
|
298
290
|
|
|
@@ -308,7 +300,7 @@ class CallRepository {
|
|
|
308
300
|
.lean(options.lean !== false);
|
|
309
301
|
} catch (error) {
|
|
310
302
|
console.error("Failed to get available calls:", error);
|
|
311
|
-
|
|
303
|
+
return [];
|
|
312
304
|
}
|
|
313
305
|
}
|
|
314
306
|
|
|
@@ -368,49 +360,110 @@ class CallRepository {
|
|
|
368
360
|
return result[0] || this._getEmptyStats();
|
|
369
361
|
} catch (error) {
|
|
370
362
|
console.error("Failed to get advanced statistics:", error);
|
|
371
|
-
|
|
363
|
+
return this._getEmptyStats();
|
|
372
364
|
}
|
|
373
365
|
}
|
|
374
366
|
|
|
375
367
|
// Cleanup stale connections
|
|
376
368
|
async cleanupStaleConnections(timeoutMinutes = 5) {
|
|
377
|
-
const session = await mongoose.startSession();
|
|
378
|
-
|
|
379
369
|
try {
|
|
380
|
-
|
|
381
|
-
const cutoffTime = new Date(Date.now() - timeoutMinutes * 60 * 1000);
|
|
370
|
+
const cutoffTime = new Date(Date.now() - timeoutMinutes * 60 * 1000);
|
|
382
371
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
372
|
+
const staleCalls = await Call.find({
|
|
373
|
+
status: { $in: ["active", "hold", "pending"] },
|
|
374
|
+
lastHeartbeat: { $lt: cutoffTime },
|
|
375
|
+
});
|
|
387
376
|
|
|
388
|
-
|
|
377
|
+
const results = [];
|
|
389
378
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
379
|
+
for (const call of staleCalls) {
|
|
380
|
+
if (call.agentId) {
|
|
381
|
+
const moved = await this.moveCallToHoldQueue(
|
|
382
|
+
call.callId,
|
|
383
|
+
"Connection timeout"
|
|
384
|
+
);
|
|
385
|
+
if (moved)
|
|
395
386
|
results.push({ callId: call.callId, action: "moved_to_hold" });
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
387
|
+
} else {
|
|
388
|
+
const ended = await this.endCall(call.callId);
|
|
389
|
+
if (ended) results.push({ callId: call.callId, action: "ended" });
|
|
400
390
|
}
|
|
391
|
+
}
|
|
401
392
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
});
|
|
393
|
+
console.log(`Cleaned up ${results.length} stale connections`);
|
|
394
|
+
return results;
|
|
405
395
|
} catch (error) {
|
|
406
396
|
console.error("Failed to cleanup stale connections:", error);
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async moveCallToHoldQueue(callId, reason = "Agent disconnected") {
|
|
402
|
+
try {
|
|
403
|
+
const call = await Call.findOneAndUpdate(
|
|
404
|
+
{ callId },
|
|
405
|
+
{
|
|
406
|
+
$set: {
|
|
407
|
+
agentId: null,
|
|
408
|
+
status: "hold",
|
|
409
|
+
isOnHold: true,
|
|
410
|
+
autoAccepted: true,
|
|
411
|
+
isHoldConnection: true,
|
|
412
|
+
lastHeartbeat: new Date(),
|
|
413
|
+
},
|
|
414
|
+
$inc: { priority: 1 },
|
|
415
|
+
$push: {
|
|
416
|
+
callHistory: {
|
|
417
|
+
timestamp: new Date(),
|
|
418
|
+
action: "disconnected",
|
|
419
|
+
agentId: null,
|
|
420
|
+
details: reason,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{ new: true }
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (call) {
|
|
428
|
+
console.log(
|
|
429
|
+
`Call ${callId} moved back to hold queue due to: ${reason}`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
return call || null;
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error(`Failed to move call ${callId} to hold queue:`, error);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async endCall(callId, agentId = null) {
|
|
440
|
+
try {
|
|
441
|
+
const call = await Call.findOneAndUpdate(
|
|
442
|
+
{ callId },
|
|
443
|
+
{
|
|
444
|
+
$set: {
|
|
445
|
+
status: "ended",
|
|
446
|
+
endTime: new Date(),
|
|
447
|
+
lastHeartbeat: new Date(),
|
|
448
|
+
},
|
|
449
|
+
$push: {
|
|
450
|
+
callHistory: {
|
|
451
|
+
timestamp: new Date(),
|
|
452
|
+
action: "ended",
|
|
453
|
+
agentId,
|
|
454
|
+
details: "Call ended",
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
{ new: true }
|
|
459
|
+
);
|
|
460
|
+
return call || null;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error(`Failed to end call ${callId}:`, error);
|
|
463
|
+
return null;
|
|
410
464
|
}
|
|
411
465
|
}
|
|
412
466
|
|
|
413
|
-
// Helper methods
|
|
414
467
|
_buildDateFilter(timeframe) {
|
|
415
468
|
const now = new Date();
|
|
416
469
|
switch (timeframe) {
|
|
@@ -464,77 +517,13 @@ class CallRepository {
|
|
|
464
517
|
};
|
|
465
518
|
}
|
|
466
519
|
|
|
467
|
-
async _updateCallMetrics(action
|
|
520
|
+
async _updateCallMetrics(action) {
|
|
468
521
|
try {
|
|
469
522
|
console.log(`Updating metrics for action: ${action}`);
|
|
470
523
|
} catch (error) {
|
|
471
524
|
console.error("Failed to update metrics:", error);
|
|
472
525
|
}
|
|
473
526
|
}
|
|
474
|
-
|
|
475
|
-
async moveCallToHoldQueue(
|
|
476
|
-
callId,
|
|
477
|
-
reason = "Agent disconnected",
|
|
478
|
-
options = {}
|
|
479
|
-
) {
|
|
480
|
-
const session = options.session || (await mongoose.startSession());
|
|
481
|
-
const shouldCommitSession = !options.session;
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
return await session.withTransaction(async () => {
|
|
485
|
-
const call = await Call.findOne({ callId }).session(session);
|
|
486
|
-
if (!call) throw new Error(`Call ${callId} not found`);
|
|
487
|
-
|
|
488
|
-
const previousAgentId = call.agentId;
|
|
489
|
-
|
|
490
|
-
call.agentId = null;
|
|
491
|
-
call.status = "hold";
|
|
492
|
-
call.isOnHold = true;
|
|
493
|
-
call.autoAccepted = true;
|
|
494
|
-
call.isHoldConnection = true;
|
|
495
|
-
call.priority = Math.min(call.priority + 1, 10);
|
|
496
|
-
call.addToHistory("disconnected", previousAgentId, reason);
|
|
497
|
-
call.updateHeartbeat();
|
|
498
|
-
|
|
499
|
-
await call.save({ session });
|
|
500
|
-
|
|
501
|
-
console.log(
|
|
502
|
-
`Call ${callId} moved back to hold queue due to: ${reason}`
|
|
503
|
-
);
|
|
504
|
-
return call;
|
|
505
|
-
});
|
|
506
|
-
} catch (error) {
|
|
507
|
-
console.error(`Failed to move call ${callId} to hold queue:`, error);
|
|
508
|
-
throw error;
|
|
509
|
-
} finally {
|
|
510
|
-
if (shouldCommitSession) await session.endSession();
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
async endCall(callId, agentId = null, options = {}) {
|
|
515
|
-
const session = options.session || (await mongoose.startSession());
|
|
516
|
-
const shouldCommitSession = !options.session;
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
return await session.withTransaction(async () => {
|
|
520
|
-
const call = await Call.findOne({ callId }).session(session);
|
|
521
|
-
if (!call) throw new Error(`Call ${callId} not found`);
|
|
522
|
-
|
|
523
|
-
call.status = "ended";
|
|
524
|
-
call.endTime = new Date();
|
|
525
|
-
call.duration = call.currentDuration;
|
|
526
|
-
call.addToHistory("ended", agentId, "Call ended");
|
|
527
|
-
|
|
528
|
-
await call.save({ session });
|
|
529
|
-
return call;
|
|
530
|
-
});
|
|
531
|
-
} catch (error) {
|
|
532
|
-
console.error(`Failed to end call ${callId}:`, error);
|
|
533
|
-
throw error;
|
|
534
|
-
} finally {
|
|
535
|
-
if (shouldCommitSession) await session.endSession();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
527
|
}
|
|
539
528
|
|
|
540
529
|
module.exports = new CallRepository();
|
|
@@ -16,6 +16,7 @@ const customerProfileRepository = require("./customerProfile.repository");
|
|
|
16
16
|
const customerTimelineRepository = require("./customerTimeline.repository");
|
|
17
17
|
const shopRepository = require("./shop.repository");
|
|
18
18
|
const callRepository = require("./call.repository");
|
|
19
|
+
const interactiveRepository = require("./interactive.repository");
|
|
19
20
|
|
|
20
21
|
exports.module = {
|
|
21
22
|
workspaceRepository,
|
|
@@ -36,4 +37,5 @@ exports.module = {
|
|
|
36
37
|
customerTimelineRepository,
|
|
37
38
|
shopRepository,
|
|
38
39
|
callRepository,
|
|
40
|
+
interactiveRepository,
|
|
39
41
|
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// repositories/interactive.repository.js
|
|
2
|
+
const Interactive = require("../../models/Interactive");
|
|
3
|
+
const { getRedisData, setRedisData } = require("../../config/redis");
|
|
4
|
+
|
|
5
|
+
const CACHE_KEY_ALL = "interactive_all";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get cached interactive for all workspaces.
|
|
9
|
+
*/
|
|
10
|
+
const getCachedAllInteractive = async () => {
|
|
11
|
+
let interactive = await getRedisData(CACHE_KEY_ALL);
|
|
12
|
+
if (!interactive) {
|
|
13
|
+
interactive = await Interactive.find({}).lean().exec();
|
|
14
|
+
await setRedisData(CACHE_KEY_ALL, interactive);
|
|
15
|
+
}
|
|
16
|
+
return interactive;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update cached interactive for all workspaces.
|
|
21
|
+
*/
|
|
22
|
+
const updateCachedAllInteractive = async () => {
|
|
23
|
+
const interactive = await Interactive.find({}).lean().exec();
|
|
24
|
+
await setRedisData(CACHE_KEY_ALL, interactive);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get interactive for a specific workspace from cache.
|
|
29
|
+
*/
|
|
30
|
+
const getWorkspaceInteractive = async (workspaceId) => {
|
|
31
|
+
if (!workspaceId) return [];
|
|
32
|
+
const allInteractive = await getCachedAllInteractive();
|
|
33
|
+
return allInteractive.filter(
|
|
34
|
+
(item) => item.workspaceId?.toString() === workspaceId.toString()
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Get interactive by ID (cache first).
|
|
39
|
+
*/
|
|
40
|
+
const getInteractiveById = async (id) => {
|
|
41
|
+
if (!id) return null;
|
|
42
|
+
|
|
43
|
+
const allInteractive = await getCachedAllInteractive();
|
|
44
|
+
let interactive = allInteractive.find(
|
|
45
|
+
(item) => item._id?.toString() === id.toString()
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// If not found in cache, fetch from DB and update cache
|
|
49
|
+
if (!interactive) {
|
|
50
|
+
interactive = await Interactive.findById(id).lean().exec();
|
|
51
|
+
if (interactive) {
|
|
52
|
+
await updateCachedAllInteractive();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return interactive || null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create new interactive and refresh cache.
|
|
61
|
+
*/
|
|
62
|
+
const createInteractive = async (data) => {
|
|
63
|
+
const newInteractive = await Interactive.create(data);
|
|
64
|
+
if (newInteractive) {
|
|
65
|
+
await updateCachedAllInteractive();
|
|
66
|
+
}
|
|
67
|
+
return newInteractive;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update interactive by ID and refresh cache.
|
|
72
|
+
*/
|
|
73
|
+
const updateInteractiveById = async (id, data) => {
|
|
74
|
+
const updated = await Interactive.findByIdAndUpdate(id, data, {
|
|
75
|
+
new: true,
|
|
76
|
+
}).exec();
|
|
77
|
+
if (updated) {
|
|
78
|
+
await updateCachedAllInteractive();
|
|
79
|
+
}
|
|
80
|
+
return updated;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Update interactive by filter and refresh cache.
|
|
85
|
+
*/
|
|
86
|
+
const updateSingleInteractiveByFilter = async (filter, data) => {
|
|
87
|
+
const updated = await Interactive.findOneAndUpdate(filter, data, {
|
|
88
|
+
new: true,
|
|
89
|
+
}).exec();
|
|
90
|
+
if (updated) {
|
|
91
|
+
await updateCachedAllInteractive();
|
|
92
|
+
}
|
|
93
|
+
return updated;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Delete interactive by ID and refresh cache.
|
|
98
|
+
*/
|
|
99
|
+
const deleteInteractiveById = async (id) => {
|
|
100
|
+
const deleted = await Interactive.findByIdAndDelete(id).exec();
|
|
101
|
+
if (deleted) {
|
|
102
|
+
await updateCachedAllInteractive();
|
|
103
|
+
}
|
|
104
|
+
return deleted;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Delete interactive by filter and refresh cache.
|
|
109
|
+
*/
|
|
110
|
+
const deleteInteractiveByFilter = async (filter) => {
|
|
111
|
+
await Interactive.deleteMany(filter).exec();
|
|
112
|
+
await updateCachedAllInteractive();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
updateCachedAllInteractive,
|
|
117
|
+
getWorkspaceInteractive,
|
|
118
|
+
createInteractive,
|
|
119
|
+
updateInteractiveById,
|
|
120
|
+
updateSingleInteractiveByFilter,
|
|
121
|
+
deleteInteractiveById,
|
|
122
|
+
deleteInteractiveByFilter,
|
|
123
|
+
getInteractiveById,
|
|
124
|
+
};
|
package/models/Automation.js
CHANGED
|
@@ -61,6 +61,9 @@ const AutomationAction = new Schema({
|
|
|
61
61
|
deleteComment: {
|
|
62
62
|
enabled: { type: Boolean, default: false },
|
|
63
63
|
},
|
|
64
|
+
hideComment: {
|
|
65
|
+
enabled: { type: Boolean, default: false },
|
|
66
|
+
},
|
|
64
67
|
closeChat: {
|
|
65
68
|
enabled: { type: Boolean, default: false },
|
|
66
69
|
},
|
|
@@ -96,6 +99,11 @@ const AutomationAction = new Schema({
|
|
|
96
99
|
default: null,
|
|
97
100
|
},
|
|
98
101
|
members: [userJoin],
|
|
102
|
+
templateFormId: {
|
|
103
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
104
|
+
ref: "FormTemplate",
|
|
105
|
+
default: null,
|
|
106
|
+
},
|
|
99
107
|
},
|
|
100
108
|
escalation: {
|
|
101
109
|
enabled: { type: Boolean, default: false },
|
|
@@ -160,6 +168,7 @@ const AutomationCondition = new Schema({
|
|
|
160
168
|
"post",
|
|
161
169
|
"shift",
|
|
162
170
|
"newCommentPost",
|
|
171
|
+
"profile",
|
|
163
172
|
],
|
|
164
173
|
},
|
|
165
174
|
keyValue: {
|
|
@@ -183,6 +192,7 @@ const AutomationCondition = new Schema({
|
|
|
183
192
|
"orderConfirmation",
|
|
184
193
|
"orderPublish",
|
|
185
194
|
"newCommentPost",
|
|
195
|
+
"profile",
|
|
186
196
|
],
|
|
187
197
|
},
|
|
188
198
|
subKeyValue: {
|
package/models/Call.js
CHANGED
|
@@ -55,7 +55,7 @@ const callSchema = new mongoose.Schema(
|
|
|
55
55
|
default: null,
|
|
56
56
|
},
|
|
57
57
|
duration: {
|
|
58
|
-
type: Number,
|
|
58
|
+
type: Number,
|
|
59
59
|
default: 0,
|
|
60
60
|
},
|
|
61
61
|
whatsappSdp: {
|
|
@@ -104,6 +104,14 @@ const callSchema = new mongoose.Schema(
|
|
|
104
104
|
details: String,
|
|
105
105
|
},
|
|
106
106
|
],
|
|
107
|
+
platformTimestamp: {
|
|
108
|
+
type: String,
|
|
109
|
+
default: "",
|
|
110
|
+
},
|
|
111
|
+
receiver: {
|
|
112
|
+
type: String,
|
|
113
|
+
default: "",
|
|
114
|
+
},
|
|
107
115
|
platformId: {
|
|
108
116
|
type: Schema.Types.ObjectId,
|
|
109
117
|
ref: "Integration",
|
package/models/Card.js
CHANGED
|
@@ -156,6 +156,11 @@ const cardSchema = new mongoose.Schema(
|
|
|
156
156
|
type: mongoose.Schema.Types.Mixed,
|
|
157
157
|
default: {},
|
|
158
158
|
},
|
|
159
|
+
templateFormId: {
|
|
160
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
161
|
+
ref: "FormTemplate",
|
|
162
|
+
default: null,
|
|
163
|
+
},
|
|
159
164
|
},
|
|
160
165
|
{ timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
|
|
161
166
|
);
|
package/models/Chatbot.js
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const { Schema, Types, model } = require("mongoose");
|
|
2
|
+
|
|
3
|
+
const ActionSchema = new Schema({
|
|
4
|
+
type: {
|
|
5
|
+
type: String,
|
|
6
|
+
default: "",
|
|
7
|
+
},
|
|
8
|
+
interactiveId: { type: Types.ObjectId, ref: "Interactive", default: null },
|
|
9
|
+
value: { type: Schema.Types.Mixed, default: "" },
|
|
10
|
+
});
|
|
11
|
+
const InteractiveSchema = new Schema({
|
|
12
|
+
name: { type: String, default: "" },
|
|
13
|
+
workspace: { type: Types.ObjectId, ref: "Workspace", default: null },
|
|
14
|
+
type: {
|
|
15
|
+
type: String,
|
|
16
|
+
enum: ["button", "list"],
|
|
17
|
+
default: "list",
|
|
18
|
+
},
|
|
19
|
+
header: {
|
|
20
|
+
type: {
|
|
21
|
+
type: String,
|
|
22
|
+
enum: ["text", "image", "video"],
|
|
23
|
+
default: "text",
|
|
24
|
+
},
|
|
25
|
+
value: { type: String, default: "" },
|
|
26
|
+
},
|
|
27
|
+
body: {
|
|
28
|
+
type: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: "text",
|
|
31
|
+
},
|
|
32
|
+
value: { type: String, default: "" },
|
|
33
|
+
},
|
|
34
|
+
footer: {
|
|
35
|
+
type: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: "text",
|
|
38
|
+
},
|
|
39
|
+
value: { type: String, default: "" },
|
|
40
|
+
},
|
|
41
|
+
buttonTitle: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: "Menu",
|
|
44
|
+
},
|
|
45
|
+
sections: [
|
|
46
|
+
{
|
|
47
|
+
title: { type: String, default: "" },
|
|
48
|
+
rows: [
|
|
49
|
+
{
|
|
50
|
+
id: { type: String, default: "" },
|
|
51
|
+
title: { type: String, default: "" },
|
|
52
|
+
description: { type: String, default: "" },
|
|
53
|
+
action: ActionSchema,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
buttons: [
|
|
59
|
+
{
|
|
60
|
+
type: { type: String, default: "reply" },
|
|
61
|
+
reply: {
|
|
62
|
+
id: { type: String, default: "" },
|
|
63
|
+
title: { type: String, default: "" },
|
|
64
|
+
action: ActionSchema,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
module.exports = model("Interactive", InteractiveSchema);
|
package/models.js
CHANGED
|
@@ -43,6 +43,7 @@ const FormTemplate = require("./models/FormTemplate");
|
|
|
43
43
|
const GoogleClientInfo = require("./models/GoogleClientInfo");
|
|
44
44
|
const GoogleCredentials = require("./models/GoogleCredentials");
|
|
45
45
|
const Integration = require("./models/Integration");
|
|
46
|
+
const Interactive = require("./models/Interactive");
|
|
46
47
|
const InternalComments = require("./models/InternalComments");
|
|
47
48
|
const InternalThreads = require("./models/InternalThreads");
|
|
48
49
|
const JobDesign = require("./models/JobDesign");
|
|
@@ -152,6 +153,7 @@ module.exports = {
|
|
|
152
153
|
GoogleClientInfo,
|
|
153
154
|
GoogleCredentials,
|
|
154
155
|
Integration,
|
|
156
|
+
Interactive,
|
|
155
157
|
InternalComments,
|
|
156
158
|
InternalThreads,
|
|
157
159
|
JobDesign,
|