@app-connect/core 1.6.4 → 1.7.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -4,6 +4,8 @@ const bodyParser = require('body-parser');
4
4
  const dynamoose = require('dynamoose');
5
5
  const axios = require('axios');
6
6
  const { UserModel } = require('./models/userModel');
7
+ const { CallDownListModel } = require('./models/callDownListModel');
8
+ const { Op } = require('sequelize');
7
9
  const { CallLogModel } = require('./models/callLogModel');
8
10
  const { MessageLogModel } = require('./models/messageLogModel');
9
11
  const { AdminConfigModel } = require('./models/adminConfigModel');
@@ -20,6 +22,7 @@ const releaseNotes = require('./releaseNotes.json');
20
22
  const analytics = require('./lib/analytics');
21
23
  const util = require('./lib/util');
22
24
  const adapterRegistry = require('./adapter/registry');
25
+ const calldown = require('./handlers/calldown');
23
26
 
24
27
  let packageJson = null;
25
28
  try {
@@ -44,6 +47,7 @@ async function initDB() {
44
47
  await MessageLogModel.sync();
45
48
  await AdminConfigModel.sync();
46
49
  await CacheModel.sync();
50
+ await CallDownListModel.sync();
47
51
  }
48
52
  }
49
53
 
@@ -331,6 +335,58 @@ function createCoreRouter() {
331
335
  });
332
336
  });
333
337
 
338
+ router.post('/admin/userMapping', async function (req, res) {
339
+ const requestStartTime = new Date().getTime();
340
+ let platformName = null;
341
+ let success = false;
342
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
343
+ try {
344
+ const jwtToken = req.query.jwtToken;
345
+ if (jwtToken) {
346
+ const unAuthData = jwt.decodeJwt(jwtToken);
347
+ platformName = unAuthData?.platform ?? 'Unknown';
348
+ const user = await UserModel.findByPk(unAuthData?.id);
349
+ if (!user) {
350
+ res.status(400).send('User not found');
351
+ return;
352
+ }
353
+ const { isValidated, rcAccountId } = await adminCore.validateAdminRole({ rcAccessToken: req.query.rcAccessToken });
354
+ const hashedRcAccountId = util.getHashValue(rcAccountId, process.env.HASH_KEY);
355
+ if (isValidated) {
356
+ const userMapping = await adminCore.getUserMapping({ user, hashedRcAccountId, rcExtensionList: req.body.rcExtensionList });
357
+ res.status(200).send(userMapping);
358
+ success = true;
359
+ }
360
+ else {
361
+ res.status(401).send('Admin validation failed');
362
+ success = true;
363
+ }
364
+ }
365
+ else {
366
+ res.status(400).send('Please go to Settings and authorize CRM platform');
367
+ success = false;
368
+ }
369
+ }
370
+ catch (e) {
371
+ console.log(`${e.stack}`);
372
+ res.status(400).send(e);
373
+ }
374
+ const requestEndTime = new Date().getTime();
375
+ analytics.track({
376
+ eventName: 'Get user mapping',
377
+ interfaceName: 'getUserMapping',
378
+ adapterName: platformName,
379
+ accountId: hashedAccountId,
380
+ extensionId: hashedExtensionId,
381
+ success,
382
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
383
+ userAgent,
384
+ ip,
385
+ author,
386
+ eventAddedVia
387
+ });
388
+ });
389
+
334
390
  router.get('/admin/serverLoggingSettings', async function (req, res) {
335
391
  const requestStartTime = new Date().getTime();
336
392
  let platformName = null;
@@ -948,7 +1004,7 @@ function createCoreRouter() {
948
1004
  }
949
1005
  const { id: userId, platform } = decodedToken;
950
1006
  platformName = platform;
951
- const { successful, logId, returnMessage, extraDataTracking } = await logCore.createCallLog({ platform, userId, incomingData: req.body, isFromSSCL: userAgent === 'SSCL' });
1007
+ const { successful, logId, returnMessage, extraDataTracking } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
952
1008
  if (extraDataTracking) {
953
1009
  extraData = extraDataTracking;
954
1010
  }
@@ -1000,7 +1056,7 @@ function createCoreRouter() {
1000
1056
  }
1001
1057
  const { id: userId, platform } = decodedToken;
1002
1058
  platformName = platform;
1003
- const { successful, logId, updatedNote, returnMessage, extraDataTracking } = await logCore.updateCallLog({ platform, userId, incomingData: req.body, isFromSSCL: userAgent === 'SSCL' });
1059
+ const { successful, logId, updatedNote, returnMessage, extraDataTracking } = await logCore.updateCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
1004
1060
  if (extraDataTracking) {
1005
1061
  extraData = extraDataTracking;
1006
1062
  }
@@ -1147,6 +1203,174 @@ function createCoreRouter() {
1147
1203
  });
1148
1204
  });
1149
1205
 
1206
+ router.post('/calldown', async function (req, res) {
1207
+ const requestStartTime = new Date().getTime();
1208
+ let platformName = null;
1209
+ let success = false;
1210
+ let statusCode = 200;
1211
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1212
+ try {
1213
+ const jwtToken = req.query.jwtToken;
1214
+ if (!jwtToken) {
1215
+ res.status(400).send('Please go to Settings and authorize CRM platform');
1216
+ return;
1217
+ }
1218
+ const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
1219
+ success = true;
1220
+ res.status(200).send({ successful: true, id });
1221
+ } catch (e) {
1222
+ console.log(`platform: ${platformName} \n${e.stack}`);
1223
+ statusCode = e.response?.status ?? 'unknown';
1224
+ res.status(400).send(e);
1225
+ success = false;
1226
+ }
1227
+ const requestEndTime = new Date().getTime();
1228
+ analytics.track({
1229
+ eventName: 'Schedule call down',
1230
+ interfaceName: 'scheduleCallDown',
1231
+ adapterName: platformName,
1232
+ accountId: hashedAccountId,
1233
+ extensionId: hashedExtensionId,
1234
+ success,
1235
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1236
+ userAgent,
1237
+ ip,
1238
+ author,
1239
+ extras: {
1240
+ statusCode
1241
+ },
1242
+ eventAddedVia
1243
+ });
1244
+ });
1245
+
1246
+
1247
+ router.get('/calldown', async function (req, res) {
1248
+ const requestStartTime = new Date().getTime();
1249
+ let platformName = null;
1250
+ let success = false;
1251
+ let statusCode = 200;
1252
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1253
+ try {
1254
+ const jwtToken = req.query.jwtToken;
1255
+ if (!jwtToken) {
1256
+ res.status(400).send('Please go to Settings and authorize CRM platform');
1257
+ return;
1258
+ }
1259
+ const { items } = await calldown.list({ jwtToken, status: req.query.status });
1260
+ success = true;
1261
+ res.status(200).send({ successful: true, items });
1262
+ } catch (e) {
1263
+ console.log(`platform: ${platformName} \n${e.stack}`);
1264
+ statusCode = e.response?.status ?? 'unknown';
1265
+ res.status(400).send(e);
1266
+ success = false;
1267
+ }
1268
+ const requestEndTime = new Date().getTime();
1269
+ analytics.track({
1270
+ eventName: 'Get call down list',
1271
+ interfaceName: 'getCallDownList',
1272
+ adapterName: platformName,
1273
+ accountId: hashedAccountId,
1274
+ extensionId: hashedExtensionId,
1275
+ success,
1276
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1277
+ userAgent,
1278
+ ip,
1279
+ author,
1280
+ extras: { statusCode },
1281
+ eventAddedVia
1282
+ });
1283
+ });
1284
+
1285
+
1286
+ router.delete('/calldown/:id', async function (req, res) {
1287
+ const requestStartTime = new Date().getTime();
1288
+ let platformName = null;
1289
+ let success = false;
1290
+ let statusCode = 200;
1291
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1292
+ try {
1293
+ const jwtToken = req.query.jwtToken;
1294
+ const id = req.query.id;
1295
+ if (!jwtToken) {
1296
+ res.status(400).send('Please go to Settings and authorize CRM platform');
1297
+ return;
1298
+ }
1299
+ const rid = req.params.id || id;
1300
+ if (!rid) {
1301
+ res.status(400).send('Missing id');
1302
+ return;
1303
+ }
1304
+ await calldown.remove({ jwtToken, id: rid });
1305
+ success = true;
1306
+ res.status(200).send({ successful: true });
1307
+ } catch (e) {
1308
+ console.log(`platform: ${platformName} \n${e.stack}`);
1309
+ statusCode = e.response?.status ?? 'unknown';
1310
+ res.status(400).send(e);
1311
+ success = false;
1312
+ }
1313
+ const requestEndTime = new Date().getTime();
1314
+ analytics.track({
1315
+ eventName: 'Delete call down item',
1316
+ interfaceName: 'deleteCallDownItem',
1317
+ adapterName: platformName,
1318
+ accountId: hashedAccountId,
1319
+ extensionId: hashedExtensionId,
1320
+ success,
1321
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1322
+ userAgent,
1323
+ ip,
1324
+ author,
1325
+ extras: { statusCode },
1326
+ eventAddedVia
1327
+ });
1328
+ });
1329
+
1330
+
1331
+ router.patch('/calldown/:id', async function (req, res) {
1332
+ const requestStartTime = new Date().getTime();
1333
+ let platformName = null;
1334
+ let success = false;
1335
+ let statusCode = 200;
1336
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1337
+ try {
1338
+ const jwtToken = req.query.jwtToken;
1339
+ if (!jwtToken) {
1340
+ res.status(400).send('Please go to Settings and authorize CRM platform');
1341
+ return;
1342
+ }
1343
+ const id = req.params.id || req.body?.id;
1344
+ if (!id) {
1345
+ res.status(400).send('Missing id');
1346
+ return;
1347
+ }
1348
+ await calldown.markCalled({ jwtToken, id, lastCallAt: req.body?.lastCallAt });
1349
+ success = true;
1350
+ res.status(200).send({ successful: true });
1351
+ } catch (e) {
1352
+ console.log(`platform: ${platformName} \n${e.stack}`);
1353
+ statusCode = e.response?.status ?? 'unknown';
1354
+ res.status(400).send(e);
1355
+ success = false;
1356
+ }
1357
+ const requestEndTime = new Date().getTime();
1358
+ analytics.track({
1359
+ eventName: 'Mark call down called',
1360
+ interfaceName: 'markCallDownCalled',
1361
+ adapterName: platformName,
1362
+ accountId: hashedAccountId,
1363
+ extensionId: hashedExtensionId,
1364
+ success,
1365
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1366
+ userAgent,
1367
+ ip,
1368
+ author,
1369
+ extras: { statusCode },
1370
+ eventAddedVia
1371
+ });
1372
+ });
1373
+
1150
1374
  router.get('/custom/contact/search', async function (req, res) {
1151
1375
  const requestStartTime = new Date().getTime();
1152
1376
  let platformName = null;
@@ -1191,8 +1415,91 @@ function createCoreRouter() {
1191
1415
  statusCode
1192
1416
  }
1193
1417
  });
1418
+ });
1419
+
1420
+ router.get('/ringcentral/admin/report', async function (req, res) {
1421
+ const requestStartTime = new Date().getTime();
1422
+ let platformName = null;
1423
+ let success = false;
1424
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1425
+ const jwtToken = req.query.jwtToken;
1426
+ try {
1427
+ if (jwtToken) {
1428
+ const unAuthData = jwt.decodeJwt(jwtToken);
1429
+ const user = await UserModel.findByPk(unAuthData?.id);
1430
+ if (!user) {
1431
+ res.status(400).send('User not found');
1432
+ return;
1433
+ }
1434
+ const report = await adminCore.getAdminReport({ rcAccountId: user.rcAccountId, timezone: req.query.timezone, timeFrom: req.query.timeFrom, timeTo: req.query.timeTo });
1435
+ res.status(200).send(report);
1436
+ success = true;
1437
+ return;
1438
+ }
1439
+ res.status(400).send('Invalid request');
1440
+ success = false;
1441
+ }
1442
+ catch (e) {
1443
+ console.log(`${e.stack}`);
1444
+ res.status(400).send(e);
1445
+ }
1446
+ const requestEndTime = new Date().getTime();
1447
+ analytics.track({
1448
+ eventName: 'Get admin report',
1449
+ interfaceName: 'getAdminReport',
1450
+ adapterName: platformName,
1451
+ accountId: hashedAccountId,
1452
+ extensionId: hashedExtensionId,
1453
+ success,
1454
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1455
+ userAgent,
1456
+ ip,
1457
+ author,
1458
+ eventAddedVia
1459
+ });
1460
+ });
1194
1461
 
1462
+ router.get('/ringcentral/admin/userReport', async function (req, res) {
1463
+ const requestStartTime = new Date().getTime();
1464
+ let platformName = null;
1465
+ let success = false;
1466
+ const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1467
+ const jwtToken = req.query.jwtToken;
1468
+ try {
1469
+ if (jwtToken) {
1470
+ const unAuthData = jwt.decodeJwt(jwtToken);
1471
+ const user = await UserModel.findByPk(unAuthData?.id);
1472
+ if (!user) {
1473
+ res.status(400).send('User not found');
1474
+ return;
1475
+ }
1476
+ const report = await adminCore.getUserReport({ rcAccountId: user.rcAccountId, rcExtensionId: req.query.rcExtensionId, timezone: req.query.timezone, timeFrom: req.query.timeFrom, timeTo: req.query.timeTo });
1477
+ res.status(200).send(report);
1478
+ return;
1479
+ }
1480
+ res.status(400).send('Invalid request');
1481
+ success = false;
1482
+ }
1483
+ catch (e) {
1484
+ console.log(`${e.stack}`);
1485
+ res.status(400).send(e);
1486
+ }
1487
+ const requestEndTime = new Date().getTime();
1488
+ analytics.track({
1489
+ eventName: 'Get user report',
1490
+ interfaceName: 'getUserReport',
1491
+ adapterName: platformName,
1492
+ accountId: hashedAccountId,
1493
+ extensionId: hashedExtensionId,
1494
+ success,
1495
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1496
+ userAgent,
1497
+ ip,
1498
+ author,
1499
+ eventAddedVia
1500
+ });
1195
1501
  });
1502
+
1196
1503
  if (process.env.IS_PROD === 'false') {
1197
1504
  router.post('/registerMockUser', async function (req, res) {
1198
1505
  const secretKey = req.query.secretKey;
@@ -1254,7 +1561,7 @@ function createCoreMiddleware() {
1254
1561
  return [
1255
1562
  bodyParser.json(),
1256
1563
  cors({
1257
- methods: ['GET', 'POST', 'PATCH', 'PUT']
1564
+ methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE']
1258
1565
  })
1259
1566
  ];
1260
1567
  }
@@ -61,10 +61,10 @@ async function composeCallLog(params) {
61
61
  body = upsertCallSessionId({ body, id: callLog.sessionId, logFormat });
62
62
  }
63
63
 
64
- const ringcentralUsername = callLog.direction === 'Inbound' ? callLog?.to?.name : callLog?.from?.name;
65
- if (ringcentralUsername && (userSettings?.addRingCentralUserName?.value ?? false)) {
64
+ if (userSettings?.addRingCentralUserName?.value) {
65
+ const ringcentralUsername = (callLog.direction === 'Inbound' ? callLog?.to?.name : callLog?.from?.name) ?? '(pending...)';
66
66
  body = upsertRingCentralUserName({ body, userName: ringcentralUsername, logFormat });
67
- }
67
+ }
68
68
 
69
69
  const ringcentralNumber = callLog.direction === 'Inbound' ? callLog?.to?.phoneNumber : callLog?.from?.phoneNumber;
70
70
  if (ringcentralNumber && (userSettings?.addRingCentralNumber?.value ?? false)) {
@@ -115,6 +115,10 @@ async function composeCallLog(params) {
115
115
  body = upsertTranscript({ body, transcript, logFormat });
116
116
  }
117
117
 
118
+ if (callLog?.legs && (userSettings?.addCallLogLegs?.value ?? true)) {
119
+ body = upsertLegs({ body, legs: callLog.legs, logFormat });
120
+ }
121
+
118
122
  return body;
119
123
  }
120
124
 
@@ -184,22 +188,37 @@ function upsertRingCentralUserName({ body, userName, logFormat }) {
184
188
 
185
189
  if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
186
190
  const userNameRegex = /(?:<li>)?<b>RingCentral user name<\/b>:\s*([^<\n]+)(?:<\/li>|(?=<|$))/i;
187
- if (userNameRegex.test(body)) {
188
- return body.replace(userNameRegex, `<li><b>RingCentral user name</b>: ${userName}</li>`);
191
+ const match = body.match(userNameRegex);
192
+ if (match) {
193
+ // Only replace if existing value is (pending...)
194
+ if (match[1].trim() === '(pending...)') {
195
+ return body.replace(userNameRegex, `<li><b>RingCentral user name</b>: ${userName}</li>`);
196
+ }
197
+ return body;
189
198
  } else {
190
199
  return body + `<li><b>RingCentral user name</b>: ${userName}</li>`;
191
200
  }
192
201
  } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
193
- const userNameRegex = /\*\*RingCentral user name\*\*: [^\n]*\n*/i;
194
- if (userNameRegex.test(body)) {
195
- return body.replace(userNameRegex, `**RingCentral user name**: ${userName}\n`);
202
+ const userNameRegex = /\*\*RingCentral user name\*\*: ([^\n]*)\n*/i;
203
+ const match = body.match(userNameRegex);
204
+ if (match) {
205
+ // Only replace if existing value is (pending...)
206
+ if (match[1].trim() === '(pending...)') {
207
+ return body.replace(userNameRegex, `**RingCentral user name**: ${userName}\n`);
208
+ }
209
+ return body;
196
210
  } else {
197
211
  return body + `**RingCentral user name**: ${userName}\n`;
198
212
  }
199
213
  } else {
200
- const userNameRegex = /- RingCentral user name: [^\n]*\n*/;
201
- if (userNameRegex.test(body)) {
202
- return body.replace(userNameRegex, `- RingCentral user name: ${userName}\n`);
214
+ const userNameRegex = /- RingCentral user name: ([^\n]*)\n*/;
215
+ const match = body.match(userNameRegex);
216
+ if (match) {
217
+ // Only replace if existing value is (pending...)
218
+ if (match[1].trim() === '(pending...)') {
219
+ return body.replace(userNameRegex, `- RingCentral user name: ${userName}\n`);
220
+ }
221
+ return body;
203
222
  } else {
204
223
  return body + `- RingCentral user name: ${userName}\n`;
205
224
  }
@@ -327,9 +346,9 @@ function upsertCallDateTime({ body, startTime, timezoneOffset, logFormat, logDat
327
346
  }
328
347
  } else {
329
348
  // Handle duplicated Date/Time entries and match complete date/time values
330
- const dateTimeRegex = /(?:- Date\/Time: [^-]*(?:-[^-]*)*)+/;
349
+ const dateTimeRegex = /^(- Date\/Time:).*$/m;
331
350
  if (dateTimeRegex.test(result)) {
332
- result = result.replace(dateTimeRegex, `- Date/Time: ${formattedDateTime}\n`);
351
+ result = result.replace(dateTimeRegex, `- Date/Time: ${formattedDateTime}`);
333
352
  } else {
334
353
  result += `- Date/Time: ${formattedDateTime}\n`;
335
354
  }
@@ -521,6 +540,81 @@ function upsertTranscript({ body, transcript, logFormat }) {
521
540
  return result;
522
541
  }
523
542
 
543
+ function getLegPartyInfo(info) {
544
+ let phoneNumber = info.phoneNumber;
545
+ let extensionNumber = info.extensionNumber;
546
+ let numberInfo = phoneNumber;
547
+ if (!phoneNumber && !extensionNumber) {
548
+ return '';
549
+ }
550
+ if (extensionNumber && phoneNumber) {
551
+ numberInfo = `${phoneNumber}, ext ${extensionNumber}`;
552
+ }
553
+ if (phoneNumber && !extensionNumber) {
554
+ numberInfo = phoneNumber;
555
+ }
556
+ if (!phoneNumber && extensionNumber) {
557
+ numberInfo = `ext ${extensionNumber}`;
558
+ }
559
+ if (info.name) {
560
+ return `${info.name}, ${numberInfo}`;
561
+ }
562
+ return numberInfo;
563
+ }
564
+
565
+ function getLegsJourney(legs) {
566
+ return legs.map((leg, index) => {
567
+ if (index === 0) {
568
+ if (leg.direction === 'Outbound') {
569
+ return `Made call from ${getLegPartyInfo(leg.from)}`;
570
+ } else {
571
+ return `Received call at ${getLegPartyInfo(leg.to)}`;
572
+ }
573
+ }
574
+ if (leg.direction === 'Outbound') {
575
+ let party = leg.from;
576
+ if (leg.legType === 'PstnToSip') {
577
+ party = leg.to;
578
+ }
579
+ return `Transferred to ${getLegPartyInfo(party)}, duration: ${leg.duration} second${leg.duration > 1 ? 's' : ''}`;
580
+ } else {
581
+ return `Transferred to ${getLegPartyInfo(leg.to)}, duration: ${leg.duration} second${leg.duration > 1 ? 's' : ''}`;
582
+ }
583
+ }).join('\n');
584
+ }
585
+
586
+ function upsertLegs({ body, legs, logFormat }) {
587
+ if (!legs || legs.length === 0) return body;
588
+
589
+ let result = body;
590
+ let legsJourney = getLegsJourney(legs);
591
+ if (logFormat === LOG_DETAILS_FORMAT_TYPE.HTML) {
592
+ legsJourney = legsJourney.replace(/(?:\r\n|\r|\n)/g, '<br>');
593
+ const legsRegex = /<div><b>Call journey<\/b><br>(.+?)<\/div>/;
594
+ if (legsRegex.test(result)) {
595
+ result = result.replace(legsRegex, `<div><b>Call journey</b><br>${legsJourney}</div>`);
596
+ } else {
597
+ result += `<div><b>Call journey</b><br>${legsJourney}</div>`;
598
+ }
599
+ } else if (logFormat === LOG_DETAILS_FORMAT_TYPE.MARKDOWN) {
600
+ const legsRegex = /### Call journey\n([\s\S]*?)(?=\n### |\n$|$)/;
601
+ if (legsRegex.test(result)) {
602
+ result = result.replace(legsRegex, `### Call journey\n${legsJourney}\n`);
603
+ } else {
604
+ result += `### Call journey\n${legsJourney}\n`;
605
+ }
606
+ } else {
607
+ const legsRegex = /- Call journey:([\s\S]*?)--- JOURNEY END/;
608
+ if (legsRegex.test(result)) {
609
+ result = result.replace(legsRegex, `- Call journey:\n${legsJourney}\n--- JOURNEY END`);
610
+ } else {
611
+ result += `- Call journey:\n${legsJourney}\n--- JOURNEY END\n`;
612
+ }
613
+ }
614
+
615
+ return result;
616
+ }
617
+
524
618
  /**
525
619
  * Helper function to determine format type for a CRM platform
526
620
  * @param {string} platform - CRM platform name
@@ -547,5 +641,6 @@ module.exports = {
547
641
  upsertCallResult,
548
642
  upsertCallRecording,
549
643
  upsertAiNote,
550
- upsertTranscript
551
- };
644
+ upsertTranscript,
645
+ upsertLegs,
646
+ };