shuttlepro-shared 1.4.3 → 1.4.5
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/customerProfile.repository.js +37 -0
- package/common/repositories/index.js +0 -3
- package/config/socket.js +30 -249
- package/models/Attribute.js +5 -0
- package/models/Category.js +5 -0
- package/models/Checkpoint.js +5 -0
- package/models/Customer.js +5 -0
- package/models/Location.js +5 -0
- package/models/Order.js +6 -0
- package/models/OrderPdf.js +5 -0
- package/models/OrderProduct.js +5 -0
- package/models/Product.js +5 -0
- package/models/ProductAttachment.js +5 -0
- package/models/ProductAttribute.js +5 -0
- package/models/ProductCategory.js +5 -0
- package/models/ProductTag.js +5 -0
- package/models/ProductVariant.js +5 -0
- package/models/Tag.js +5 -0
- package/models/VariantLocation.js +5 -0
- package/models.js +0 -2
- package/package.json +1 -1
- package/common/repositories/call.repository.js +0 -529
- package/models/Call.js +0 -196
package/package.json
CHANGED
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
const Call = require("../../models/Call");
|
|
2
|
-
|
|
3
|
-
class CallRepository {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.pipelines = {
|
|
6
|
-
callStats: this._buildCallStatsPipeline(),
|
|
7
|
-
holdQueue: this._buildHoldQueuePipeline(),
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Create a new call
|
|
12
|
-
async createCall(callData) {
|
|
13
|
-
try {
|
|
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],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
await this._updateCallMetrics("created");
|
|
40
|
-
return call;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(`Failed to create call ${callData.callId}:`, error);
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Get call by ID
|
|
48
|
-
async getCall(callId, options = {}) {
|
|
49
|
-
try {
|
|
50
|
-
const query = Call.findOne({ callId });
|
|
51
|
-
|
|
52
|
-
if (options.lean) query.lean();
|
|
53
|
-
|
|
54
|
-
if (options.populate !== false) {
|
|
55
|
-
query.populate([
|
|
56
|
-
{ path: "platformId", model: "Integration" },
|
|
57
|
-
{ path: "workspaceId", model: "Workspace" },
|
|
58
|
-
{ path: "profileId", model: "Profile" },
|
|
59
|
-
]);
|
|
60
|
-
} else if (options.populate) {
|
|
61
|
-
query.populate(options.populate);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (options.select) query.select(options.select);
|
|
65
|
-
|
|
66
|
-
return await query.exec();
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error(`Failed to get call ${callId}:`, error);
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Bulk get calls
|
|
74
|
-
async getCalls(filters = {}, options = {}) {
|
|
75
|
-
try {
|
|
76
|
-
const {
|
|
77
|
-
status,
|
|
78
|
-
agentId,
|
|
79
|
-
dateRange,
|
|
80
|
-
priority,
|
|
81
|
-
tags,
|
|
82
|
-
platformId,
|
|
83
|
-
workspaceId,
|
|
84
|
-
profileId,
|
|
85
|
-
page = 1,
|
|
86
|
-
limit = 50,
|
|
87
|
-
sort = { createdAt: -1 },
|
|
88
|
-
} = filters;
|
|
89
|
-
|
|
90
|
-
const query = {};
|
|
91
|
-
if (status)
|
|
92
|
-
query.status = Array.isArray(status) ? { $in: status } : status;
|
|
93
|
-
if (agentId) query.agentId = agentId;
|
|
94
|
-
if (platformId) query.platformId = platformId;
|
|
95
|
-
if (workspaceId) query.workspaceId = workspaceId;
|
|
96
|
-
if (profileId) query.profileId = profileId;
|
|
97
|
-
|
|
98
|
-
if (dateRange) {
|
|
99
|
-
query.createdAt = {
|
|
100
|
-
$gte: new Date(dateRange.start),
|
|
101
|
-
$lte: new Date(dateRange.end),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (priority !== undefined) query.priority = { $gte: priority };
|
|
106
|
-
if (tags?.length > 0) query.tags = { $in: tags };
|
|
107
|
-
|
|
108
|
-
const skip = (page - 1) * limit;
|
|
109
|
-
|
|
110
|
-
const [calls, total] = await Promise.all([
|
|
111
|
-
Call.find(query)
|
|
112
|
-
.sort(sort)
|
|
113
|
-
.skip(skip)
|
|
114
|
-
.limit(limit)
|
|
115
|
-
.lean(options.lean !== false)
|
|
116
|
-
.populate(
|
|
117
|
-
options.populate !== false
|
|
118
|
-
? [
|
|
119
|
-
{ path: "platformId", model: "Integration" },
|
|
120
|
-
{ path: "workspaceId", model: "Workspace" },
|
|
121
|
-
{ path: "profileId", model: "Profile" },
|
|
122
|
-
]
|
|
123
|
-
: []
|
|
124
|
-
)
|
|
125
|
-
.exec(),
|
|
126
|
-
Call.countDocuments(query),
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
calls,
|
|
131
|
-
pagination: {
|
|
132
|
-
page,
|
|
133
|
-
limit,
|
|
134
|
-
total,
|
|
135
|
-
pages: Math.ceil(total / limit),
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error("Failed to get calls:", error);
|
|
140
|
-
return {
|
|
141
|
-
calls: [],
|
|
142
|
-
pagination: { page: 1, limit: 50, total: 0, pages: 0 },
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Update call
|
|
148
|
-
async updateCall(callId, updateData) {
|
|
149
|
-
try {
|
|
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
|
-
];
|
|
163
|
-
|
|
164
|
-
const filteredUpdate = {};
|
|
165
|
-
Object.keys(updateData).forEach((key) => {
|
|
166
|
-
if (allowedFields.includes(key)) {
|
|
167
|
-
filteredUpdate[key] = updateData[key];
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const updateOps = {
|
|
172
|
-
$set: {
|
|
173
|
-
...filteredUpdate,
|
|
174
|
-
lastHeartbeat: new Date(),
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
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
|
-
}
|
|
189
|
-
|
|
190
|
-
const call = await Call.findOneAndUpdate({ callId }, updateOps, {
|
|
191
|
-
new: true,
|
|
192
|
-
});
|
|
193
|
-
return call || null;
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error(`Failed to update call ${callId}:`, error);
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Assign call to agent
|
|
201
|
-
async assignCallToAgent(callId, agentId) {
|
|
202
|
-
try {
|
|
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
|
-
} catch (error) {
|
|
226
|
-
console.error(
|
|
227
|
-
`Failed to assign call ${callId} to agent ${agentId}:`,
|
|
228
|
-
error
|
|
229
|
-
);
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Bulk update calls
|
|
235
|
-
async bulkUpdateCalls(operations) {
|
|
236
|
-
try {
|
|
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",
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
}));
|
|
256
|
-
|
|
257
|
-
return await Call.bulkWrite(bulkOps);
|
|
258
|
-
} catch (error) {
|
|
259
|
-
console.error("Failed to perform bulk update:", error);
|
|
260
|
-
return [];
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Hold queue with priority
|
|
265
|
-
async getHoldQueue(options = {}) {
|
|
266
|
-
try {
|
|
267
|
-
const pipeline = [
|
|
268
|
-
{ $match: { status: "hold", isOnHold: true } },
|
|
269
|
-
{
|
|
270
|
-
$addFields: {
|
|
271
|
-
waitingTime: {
|
|
272
|
-
$divide: [{ $subtract: [new Date(), "$createdAt"] }, 1000 * 60],
|
|
273
|
-
},
|
|
274
|
-
priorityScore: {
|
|
275
|
-
$add: ["$priority", { $multiply: ["$waitingTime", 0.1] }],
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
{ $sort: { priorityScore: -1, createdAt: 1 } },
|
|
280
|
-
];
|
|
281
|
-
|
|
282
|
-
if (options.limit) pipeline.push({ $limit: options.limit });
|
|
283
|
-
|
|
284
|
-
return await Call.aggregate(pipeline);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error("Failed to get hold queue:", error);
|
|
287
|
-
return [];
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Get available calls
|
|
292
|
-
async getAvailableCalls(options = {}) {
|
|
293
|
-
try {
|
|
294
|
-
return await Call.find({
|
|
295
|
-
status: { $in: ["pending", "hold"] },
|
|
296
|
-
agentId: null,
|
|
297
|
-
})
|
|
298
|
-
.sort({ priority: -1, createdAt: 1 })
|
|
299
|
-
.limit(options.limit || 20)
|
|
300
|
-
.lean(options.lean !== false);
|
|
301
|
-
} catch (error) {
|
|
302
|
-
console.error("Failed to get available calls:", error);
|
|
303
|
-
return [];
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Advanced stats
|
|
308
|
-
async getAdvancedStatistics(timeframe = "today", groupBy = null) {
|
|
309
|
-
try {
|
|
310
|
-
const dateFilter = this._buildDateFilter(timeframe);
|
|
311
|
-
|
|
312
|
-
const pipeline = [
|
|
313
|
-
{ $match: dateFilter },
|
|
314
|
-
{
|
|
315
|
-
$group: {
|
|
316
|
-
_id: groupBy ? `$${groupBy}` : null,
|
|
317
|
-
totalCalls: { $sum: 1 },
|
|
318
|
-
activeCalls: {
|
|
319
|
-
$sum: { $cond: [{ $in: ["$status", ["active", "hold"]] }, 1, 0] },
|
|
320
|
-
},
|
|
321
|
-
completedCalls: {
|
|
322
|
-
$sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] },
|
|
323
|
-
},
|
|
324
|
-
abandonedCalls: {
|
|
325
|
-
$sum: { $cond: [{ $eq: ["$status", "abandoned"] }, 1, 0] },
|
|
326
|
-
},
|
|
327
|
-
averageDuration: { $avg: { $ifNull: ["$duration", 0] } },
|
|
328
|
-
totalDuration: { $sum: { $ifNull: ["$duration", 0] } },
|
|
329
|
-
averageWaitTime: {
|
|
330
|
-
$avg: {
|
|
331
|
-
$divide: [
|
|
332
|
-
{ $subtract: ["$startTime", "$createdAt"] },
|
|
333
|
-
1000 * 60,
|
|
334
|
-
],
|
|
335
|
-
},
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
$addFields: {
|
|
341
|
-
answerRate: {
|
|
342
|
-
$cond: [
|
|
343
|
-
{ $gt: ["$totalCalls", 0] },
|
|
344
|
-
{ $divide: ["$completedCalls", "$totalCalls"] },
|
|
345
|
-
0,
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
abandonRate: {
|
|
349
|
-
$cond: [
|
|
350
|
-
{ $gt: ["$totalCalls", 0] },
|
|
351
|
-
{ $divide: ["$abandonedCalls", "$totalCalls"] },
|
|
352
|
-
0,
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
];
|
|
358
|
-
|
|
359
|
-
const result = await Call.aggregate(pipeline);
|
|
360
|
-
return result[0] || this._getEmptyStats();
|
|
361
|
-
} catch (error) {
|
|
362
|
-
console.error("Failed to get advanced statistics:", error);
|
|
363
|
-
return this._getEmptyStats();
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Cleanup stale connections
|
|
368
|
-
async cleanupStaleConnections(timeoutMinutes = 5) {
|
|
369
|
-
try {
|
|
370
|
-
const cutoffTime = new Date(Date.now() - timeoutMinutes * 60 * 1000);
|
|
371
|
-
|
|
372
|
-
const staleCalls = await Call.find({
|
|
373
|
-
status: { $in: ["active", "hold", "pending"] },
|
|
374
|
-
lastHeartbeat: { $lt: cutoffTime },
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const results = [];
|
|
378
|
-
|
|
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)
|
|
386
|
-
results.push({ callId: call.callId, action: "moved_to_hold" });
|
|
387
|
-
} else {
|
|
388
|
-
const ended = await this.endCall(call.callId);
|
|
389
|
-
if (ended) results.push({ callId: call.callId, action: "ended" });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
console.log(`Cleaned up ${results.length} stale connections`);
|
|
394
|
-
return results;
|
|
395
|
-
} catch (error) {
|
|
396
|
-
console.error("Failed to cleanup stale connections:", error);
|
|
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;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
_buildDateFilter(timeframe) {
|
|
468
|
-
const now = new Date();
|
|
469
|
-
switch (timeframe) {
|
|
470
|
-
case "today":
|
|
471
|
-
const today = new Date(now);
|
|
472
|
-
today.setHours(0, 0, 0, 0);
|
|
473
|
-
return { createdAt: { $gte: today } };
|
|
474
|
-
case "week":
|
|
475
|
-
const weekAgo = new Date(now);
|
|
476
|
-
weekAgo.setDate(weekAgo.getDate() - 7);
|
|
477
|
-
return { createdAt: { $gte: weekAgo } };
|
|
478
|
-
case "month":
|
|
479
|
-
const monthAgo = new Date(now);
|
|
480
|
-
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
|
481
|
-
return { createdAt: { $gte: monthAgo } };
|
|
482
|
-
default:
|
|
483
|
-
return {};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
_buildCallStatsPipeline() {
|
|
488
|
-
return [
|
|
489
|
-
{
|
|
490
|
-
$group: {
|
|
491
|
-
_id: "$status",
|
|
492
|
-
count: { $sum: 1 },
|
|
493
|
-
avgDuration: { $avg: "$duration" },
|
|
494
|
-
},
|
|
495
|
-
},
|
|
496
|
-
];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
_buildHoldQueuePipeline() {
|
|
500
|
-
return [
|
|
501
|
-
{ $match: { status: "hold", isOnHold: true } },
|
|
502
|
-
{ $sort: { priority: -1, createdAt: 1 } },
|
|
503
|
-
];
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
_getEmptyStats() {
|
|
507
|
-
return {
|
|
508
|
-
totalCalls: 0,
|
|
509
|
-
activeCalls: 0,
|
|
510
|
-
completedCalls: 0,
|
|
511
|
-
abandonedCalls: 0,
|
|
512
|
-
averageDuration: 0,
|
|
513
|
-
totalDuration: 0,
|
|
514
|
-
averageWaitTime: 0,
|
|
515
|
-
answerRate: 0,
|
|
516
|
-
abandonRate: 0,
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async _updateCallMetrics(action) {
|
|
521
|
-
try {
|
|
522
|
-
console.log(`Updating metrics for action: ${action}`);
|
|
523
|
-
} catch (error) {
|
|
524
|
-
console.error("Failed to update metrics:", error);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
module.exports = new CallRepository();
|