@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/handlers/admin.js +267 -0
- package/handlers/auth.js +29 -3
- package/handlers/calldown.js +60 -0
- package/handlers/log.js +28 -6
- package/index.js +310 -3
- package/lib/callLogComposer.js +110 -15
- package/lib/ringcentral.js +275 -0
- package/lib/util.js +26 -1
- package/models/adminConfigModel.js +18 -1
- package/models/callDownListModel.js +35 -0
- package/models/userModel.js +4 -0
- package/package.json +3 -3
- package/releaseNotes.json +44 -0
package/handlers/admin.js
CHANGED
|
@@ -2,6 +2,7 @@ const axios = require('axios');
|
|
|
2
2
|
const { AdminConfigModel } = require('../models/adminConfigModel');
|
|
3
3
|
const adapterRegistry = require('../adapter/registry');
|
|
4
4
|
const oauth = require('../lib/oauth');
|
|
5
|
+
const { RingCentral } = require('../lib/ringcentral');
|
|
5
6
|
|
|
6
7
|
async function validateAdminRole({ rcAccessToken }) {
|
|
7
8
|
const rcExtensionResponse = await axios.get(
|
|
@@ -36,6 +37,21 @@ async function getAdminSettings({ hashedRcAccountId }) {
|
|
|
36
37
|
return existingAdminConfig;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
async function updateAdminRcTokens({ hashedRcAccountId, adminAccessToken, adminRefreshToken, adminTokenExpiry }) {
|
|
41
|
+
const existingAdminConfig = await AdminConfigModel.findByPk(hashedRcAccountId);
|
|
42
|
+
if (existingAdminConfig) {
|
|
43
|
+
await existingAdminConfig.update({ adminAccessToken, adminRefreshToken, adminTokenExpiry });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await AdminConfigModel.create({
|
|
47
|
+
id: hashedRcAccountId,
|
|
48
|
+
adminAccessToken,
|
|
49
|
+
adminRefreshToken,
|
|
50
|
+
adminTokenExpiry
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
async function getServerLoggingSettings({ user }) {
|
|
40
56
|
const platformModule = adapterRegistry.getAdapter(user.platform);
|
|
41
57
|
if (platformModule.getServerLoggingSettings) {
|
|
@@ -55,8 +71,259 @@ async function updateServerLoggingSettings({ user, additionalFieldValues }) {
|
|
|
55
71
|
return {};
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo }) {
|
|
75
|
+
try {
|
|
76
|
+
const rcSDK = new RingCentral({
|
|
77
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
78
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
79
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
80
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
81
|
+
});
|
|
82
|
+
let adminConfig = await AdminConfigModel.findByPk(rcAccountId);
|
|
83
|
+
const isTokenExpired = adminConfig.adminTokenExpiry < new Date();
|
|
84
|
+
if (isTokenExpired) {
|
|
85
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.refreshToken({
|
|
86
|
+
refresh_token: adminConfig.adminRefreshToken,
|
|
87
|
+
expires_in: adminConfig.adminTokenExpiry,
|
|
88
|
+
refresh_token_expires_in: adminConfig.adminTokenExpiry
|
|
89
|
+
});
|
|
90
|
+
adminConfig = await AdminConfigModel.update({ adminAccessToken: access_token, adminRefreshToken: refresh_token, adminTokenExpiry: expire_time }, { where: { id: rcAccountId } });
|
|
91
|
+
}
|
|
92
|
+
const callsAggregationData = await rcSDK.getCallsAggregationData({
|
|
93
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
94
|
+
timezone,
|
|
95
|
+
timeFrom,
|
|
96
|
+
timeTo
|
|
97
|
+
});
|
|
98
|
+
var dataCounter = callsAggregationData.data.records[0].counters;
|
|
99
|
+
var inboundCallCount = dataCounter.callsByDirection.values.inbound;
|
|
100
|
+
var outboundCallCount = dataCounter.callsByDirection.values.outbound;
|
|
101
|
+
var answeredCallCount = dataCounter.callsByResponse.values.answered;
|
|
102
|
+
// keep 2 decimal places
|
|
103
|
+
var answeredCallPercentage = inboundCallCount === 0 ? '0%' : `${((answeredCallCount / inboundCallCount) * 100).toFixed(2)}%`;
|
|
104
|
+
|
|
105
|
+
var dataTimer = callsAggregationData.data.records[0].timers;
|
|
106
|
+
// keep 2 decimal places
|
|
107
|
+
var totalTalkTime = (dataTimer.allCalls.values / 60).toFixed(2);
|
|
108
|
+
// keep 2 decimal places
|
|
109
|
+
var averageTalkTime = (totalTalkTime / (inboundCallCount + outboundCallCount)).toFixed(2);
|
|
110
|
+
return {
|
|
111
|
+
callLogStats: {
|
|
112
|
+
inboundCallCount,
|
|
113
|
+
outboundCallCount,
|
|
114
|
+
answeredCallCount,
|
|
115
|
+
answeredCallPercentage,
|
|
116
|
+
totalTalkTime,
|
|
117
|
+
averageTalkTime
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(error);
|
|
122
|
+
return {
|
|
123
|
+
callLogStats: {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function getUserReport({ rcAccountId, rcExtensionId, timezone, timeFrom, timeTo }) {
|
|
129
|
+
try {
|
|
130
|
+
const rcSDK = new RingCentral({
|
|
131
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
132
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
133
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
134
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
135
|
+
});
|
|
136
|
+
let adminConfig = await AdminConfigModel.findByPk(rcAccountId);
|
|
137
|
+
const isTokenExpired = adminConfig.adminTokenExpiry < new Date();
|
|
138
|
+
if (isTokenExpired) {
|
|
139
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.refreshToken({
|
|
140
|
+
refresh_token: adminConfig.adminRefreshToken,
|
|
141
|
+
expires_in: adminConfig.adminTokenExpiry,
|
|
142
|
+
refresh_token_expires_in: adminConfig.adminTokenExpiry
|
|
143
|
+
});
|
|
144
|
+
adminConfig = await AdminConfigModel.update({ adminAccessToken: access_token, adminRefreshToken: refresh_token, adminTokenExpiry: expire_time }, { where: { id: rcAccountId } });
|
|
145
|
+
}
|
|
146
|
+
const callLogData = await rcSDK.getCallLogData({
|
|
147
|
+
extensionId: rcExtensionId,
|
|
148
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
149
|
+
timezone,
|
|
150
|
+
timeFrom,
|
|
151
|
+
timeTo
|
|
152
|
+
});
|
|
153
|
+
// phone activity
|
|
154
|
+
const inboundCallCount = callLogData.records.filter(call => call.direction === 'Inbound').length;
|
|
155
|
+
const outboundCallCount = callLogData.records.filter(call => call.direction === 'Outbound').length;
|
|
156
|
+
const answeredCallCount = callLogData.records.filter(call => call.direction === 'Inbound' && (call.result === 'Call connected' || call.result === 'Accepted' || call.result === 'Answered Not Accepted')).length;
|
|
157
|
+
const answeredCallPercentage = answeredCallCount === 0 ? '0%' : `${((answeredCallCount / (inboundCallCount || 1)) * 100).toFixed(2)}%`;
|
|
158
|
+
// phone engagement
|
|
159
|
+
const totalTalkTime = Math.round(callLogData.records.reduce((acc, call) => acc + (call.duration || 0), 0) / 60) || 0;
|
|
160
|
+
const averageTalkTime = Math.round(totalTalkTime / (inboundCallCount + outboundCallCount)) || 0;
|
|
161
|
+
const smsLogData = await rcSDK.getSMSData({
|
|
162
|
+
extensionId: rcExtensionId,
|
|
163
|
+
token: { access_token: adminConfig.adminAccessToken, token_type: 'Bearer' },
|
|
164
|
+
timezone,
|
|
165
|
+
timeFrom,
|
|
166
|
+
timeTo
|
|
167
|
+
});
|
|
168
|
+
const smsSentCount = smsLogData.records.filter(sms => sms.direction === 'Outbound').length;
|
|
169
|
+
const smsReceivedCount = smsLogData.records.filter(sms => sms.direction === 'Inbound').length;
|
|
170
|
+
const reportStats = {
|
|
171
|
+
callLogStats: {
|
|
172
|
+
inboundCallCount,
|
|
173
|
+
outboundCallCount,
|
|
174
|
+
answeredCallCount,
|
|
175
|
+
answeredCallPercentage,
|
|
176
|
+
totalTalkTime,
|
|
177
|
+
averageTalkTime
|
|
178
|
+
},
|
|
179
|
+
smsLogStats: {
|
|
180
|
+
smsSentCount,
|
|
181
|
+
smsReceivedCount
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
return reportStats;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(error);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function getUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
192
|
+
const adminConfig = await getAdminSettings({ hashedRcAccountId });
|
|
193
|
+
const platformModule = adapterRegistry.getAdapter(user.platform);
|
|
194
|
+
if (platformModule.getUserList) {
|
|
195
|
+
const authType = platformModule.getAuthType();
|
|
196
|
+
let authHeader = '';
|
|
197
|
+
switch (authType) {
|
|
198
|
+
case 'oauth':
|
|
199
|
+
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname })));
|
|
200
|
+
// eslint-disable-next-line no-param-reassign
|
|
201
|
+
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
202
|
+
authHeader = `Bearer ${user.accessToken}`;
|
|
203
|
+
break;
|
|
204
|
+
case 'apiKey':
|
|
205
|
+
const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
|
|
206
|
+
authHeader = `Basic ${basicAuth}`;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
const crmUserList = await platformModule.getUserList({ user, authHeader });
|
|
210
|
+
const userMappingResult = [];
|
|
211
|
+
const newUserMappings = [];
|
|
212
|
+
for (const crmUser of crmUserList) {
|
|
213
|
+
const existingMapping = adminConfig?.userMappings?.find(u => u.crmUserId == crmUser.id);
|
|
214
|
+
let existingMappingRcExtensionIds = [];
|
|
215
|
+
// TEMP: backward compatibility for string value
|
|
216
|
+
if (existingMapping?.rcExtensionId) {
|
|
217
|
+
if (typeof (existingMapping.rcExtensionId) === 'string') {
|
|
218
|
+
existingMappingRcExtensionIds = [existingMapping.rcExtensionId];
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
existingMappingRcExtensionIds = existingMapping.rcExtensionId;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const rcExtension = rcExtensionList.filter(e => existingMappingRcExtensionIds.includes(e.id));
|
|
225
|
+
// Case: existing mapping
|
|
226
|
+
if (existingMapping) {
|
|
227
|
+
userMappingResult.push({
|
|
228
|
+
crmUser: {
|
|
229
|
+
id: crmUser.id,
|
|
230
|
+
name: crmUser.name ?? '',
|
|
231
|
+
email: crmUser.email ?? '',
|
|
232
|
+
},
|
|
233
|
+
rcUser: rcExtension.map(e => ({
|
|
234
|
+
extensionId: e.id,
|
|
235
|
+
name: e?.name || `${e.firstName} ${e.lastName}`,
|
|
236
|
+
extensionNumber: e?.extensionNumber ?? '',
|
|
237
|
+
email: e?.email ?? ''
|
|
238
|
+
}))
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// Case: new mapping
|
|
242
|
+
else {
|
|
243
|
+
const rcExtensionForNewMapping = rcExtensionList.find(u =>
|
|
244
|
+
u.email === crmUser.email ||
|
|
245
|
+
u.name === crmUser.name ||
|
|
246
|
+
(`${u.firstName} ${u.lastName}` === crmUser.name)
|
|
247
|
+
);
|
|
248
|
+
if (rcExtensionForNewMapping) {
|
|
249
|
+
userMappingResult.push({
|
|
250
|
+
crmUser: {
|
|
251
|
+
id: crmUser.id,
|
|
252
|
+
name: crmUser.name ?? '',
|
|
253
|
+
email: crmUser.email ?? '',
|
|
254
|
+
},
|
|
255
|
+
rcUser: [{
|
|
256
|
+
extensionId: rcExtensionForNewMapping.id,
|
|
257
|
+
name: rcExtensionForNewMapping.name || `${rcExtensionForNewMapping.firstName} ${rcExtensionForNewMapping.lastName}`,
|
|
258
|
+
extensionNumber: rcExtensionForNewMapping?.extensionNumber ?? '',
|
|
259
|
+
email: rcExtensionForNewMapping?.email ?? ''
|
|
260
|
+
}]
|
|
261
|
+
});
|
|
262
|
+
newUserMappings.push({
|
|
263
|
+
crmUserId: crmUser.id.toString(),
|
|
264
|
+
rcExtensionId: [rcExtensionForNewMapping.id.toString()]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
userMappingResult.push({
|
|
269
|
+
crmUser: {
|
|
270
|
+
id: crmUser.id,
|
|
271
|
+
name: crmUser.name ?? '',
|
|
272
|
+
email: crmUser.email ?? '',
|
|
273
|
+
},
|
|
274
|
+
rcUser: []
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// One-time init
|
|
280
|
+
if (!adminConfig?.userMappings) {
|
|
281
|
+
const initialUserMappings = [];
|
|
282
|
+
for (const userMapping of userMappingResult) {
|
|
283
|
+
if (userMapping.rcUser?.extensionId) {
|
|
284
|
+
initialUserMappings.push({
|
|
285
|
+
crmUserId: userMapping.crmUser.id.toString(),
|
|
286
|
+
rcExtensionId: [userMapping.rcUser.extensionId.toString()]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
await upsertAdminSettings({
|
|
291
|
+
hashedRcAccountId,
|
|
292
|
+
adminSettings: {
|
|
293
|
+
userMappings: initialUserMappings
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// Incremental update
|
|
298
|
+
if (newUserMappings.length > 0) {
|
|
299
|
+
// TEMP: convert string to array
|
|
300
|
+
if (adminConfig?.userMappings) {
|
|
301
|
+
adminConfig.userMappings = adminConfig.userMappings.map(u => ({
|
|
302
|
+
...u,
|
|
303
|
+
rcExtensionId: [u.rcExtensionId]
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
adminConfig.userMappings = [];
|
|
308
|
+
}
|
|
309
|
+
await upsertAdminSettings({
|
|
310
|
+
hashedRcAccountId,
|
|
311
|
+
adminSettings: {
|
|
312
|
+
userMappings: [...adminConfig.userMappings, ...newUserMappings]
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return userMappingResult;
|
|
317
|
+
}
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
|
|
58
321
|
exports.validateAdminRole = validateAdminRole;
|
|
59
322
|
exports.upsertAdminSettings = upsertAdminSettings;
|
|
60
323
|
exports.getAdminSettings = getAdminSettings;
|
|
324
|
+
exports.updateAdminRcTokens = updateAdminRcTokens;
|
|
61
325
|
exports.getServerLoggingSettings = getServerLoggingSettings;
|
|
62
326
|
exports.updateServerLoggingSettings = updateServerLoggingSettings;
|
|
327
|
+
exports.getAdminReport = getAdminReport;
|
|
328
|
+
exports.getUserReport = getUserReport;
|
|
329
|
+
exports.getUserMapping = getUserMapping;
|
package/handlers/auth.js
CHANGED
|
@@ -2,6 +2,8 @@ const oauth = require('../lib/oauth');
|
|
|
2
2
|
const { UserModel } = require('../models/userModel');
|
|
3
3
|
const adapterRegistry = require('../adapter/registry');
|
|
4
4
|
const Op = require('sequelize').Op;
|
|
5
|
+
const { RingCentral } = require('../lib/ringcentral');
|
|
6
|
+
const adminCore = require('./admin');
|
|
5
7
|
|
|
6
8
|
async function onOAuthCallback({ platform, hostname, tokenUrl, callbackUri, apiUrl, username, query }) {
|
|
7
9
|
const platformModule = adapterRegistry.getAdapter(platform);
|
|
@@ -36,7 +38,8 @@ async function onOAuthCallback({ platform, hostname, tokenUrl, callbackUri, apiU
|
|
|
36
38
|
hostname: platformUserInfo?.overridingHostname ? platformUserInfo.overridingHostname : hostname,
|
|
37
39
|
accessToken,
|
|
38
40
|
refreshToken,
|
|
39
|
-
tokenExpiry: expires
|
|
41
|
+
tokenExpiry: expires,
|
|
42
|
+
rcAccountId: query.rcAccountId
|
|
40
43
|
});
|
|
41
44
|
if (platformModule.postSaveUserInfo) {
|
|
42
45
|
userInfo = await platformModule.postSaveUserInfo({ userInfo, oauthApp });
|
|
@@ -81,7 +84,7 @@ async function onApiKeyLogin({ platform, hostname, apiKey, additionalInfo }) {
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken, refreshToken, tokenExpiry }) {
|
|
87
|
+
async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken, refreshToken, tokenExpiry, rcAccountId }) {
|
|
85
88
|
const id = platformUserInfo.id;
|
|
86
89
|
const name = platformUserInfo.name;
|
|
87
90
|
const existingUser = await UserModel.findByPk(id);
|
|
@@ -91,12 +94,14 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
|
|
|
91
94
|
if (existingUser) {
|
|
92
95
|
await existingUser.update(
|
|
93
96
|
{
|
|
97
|
+
platform,
|
|
94
98
|
hostname,
|
|
95
99
|
timezoneName,
|
|
96
100
|
timezoneOffset,
|
|
97
101
|
accessToken,
|
|
98
102
|
refreshToken,
|
|
99
103
|
tokenExpiry,
|
|
104
|
+
rcAccountId,
|
|
100
105
|
platformAdditionalInfo: {
|
|
101
106
|
...existingUser.platformAdditionalInfo, // keep existing platformAdditionalInfo
|
|
102
107
|
...platformAdditionalInfo,
|
|
@@ -119,6 +124,7 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
|
|
|
119
124
|
accessToken,
|
|
120
125
|
refreshToken,
|
|
121
126
|
tokenExpiry,
|
|
127
|
+
rcAccountId,
|
|
122
128
|
platformAdditionalInfo,
|
|
123
129
|
userSettings: userWithOldID.userSettings
|
|
124
130
|
});
|
|
@@ -134,6 +140,7 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
|
|
|
134
140
|
accessToken,
|
|
135
141
|
refreshToken,
|
|
136
142
|
tokenExpiry,
|
|
143
|
+
rcAccountId,
|
|
137
144
|
platformAdditionalInfo,
|
|
138
145
|
userSettings: {}
|
|
139
146
|
});
|
|
@@ -149,6 +156,7 @@ async function saveUserInfo({ platformUserInfo, platform, hostname, accessToken,
|
|
|
149
156
|
accessToken,
|
|
150
157
|
refreshToken,
|
|
151
158
|
tokenExpiry,
|
|
159
|
+
rcAccountId,
|
|
152
160
|
platformAdditionalInfo,
|
|
153
161
|
userSettings: {}
|
|
154
162
|
});
|
|
@@ -199,7 +207,25 @@ async function authValidation({ platform, userId }) {
|
|
|
199
207
|
}
|
|
200
208
|
}
|
|
201
209
|
|
|
210
|
+
// Ringcentral
|
|
211
|
+
async function onRingcentralOAuthCallback({ code, rcAccountId }) {
|
|
212
|
+
const rcSDK = new RingCentral({
|
|
213
|
+
server: process.env.RINGCENTRAL_SERVER,
|
|
214
|
+
clientId: process.env.RINGCENTRAL_CLIENT_ID,
|
|
215
|
+
clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
|
|
216
|
+
redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
|
|
217
|
+
});
|
|
218
|
+
const { access_token, refresh_token, expire_time } = await rcSDK.generateToken({ code });
|
|
219
|
+
await adminCore.updateAdminRcTokens({
|
|
220
|
+
hashedRcAccountId: rcAccountId,
|
|
221
|
+
adminAccessToken: access_token,
|
|
222
|
+
adminRefreshToken: refresh_token,
|
|
223
|
+
adminTokenExpiry: expire_time
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
202
227
|
exports.onOAuthCallback = onOAuthCallback;
|
|
203
228
|
exports.onApiKeyLogin = onApiKeyLogin;
|
|
204
229
|
exports.authValidation = authValidation;
|
|
205
|
-
exports.getLicenseStatus = getLicenseStatus;
|
|
230
|
+
exports.getLicenseStatus = getLicenseStatus;
|
|
231
|
+
exports.onRingcentralOAuthCallback = onRingcentralOAuthCallback;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const { UserModel } = require('../models/userModel');
|
|
2
|
+
const { CallDownListModel } = require('../models/callDownListModel');
|
|
3
|
+
const { Op } = require('sequelize');
|
|
4
|
+
const jwt = require('../lib/jwt');
|
|
5
|
+
|
|
6
|
+
async function schedule({ jwtToken, rcAccessToken, body }) {
|
|
7
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
8
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
9
|
+
const user = await UserModel.findByPk(unAuthData.id);
|
|
10
|
+
if (!user) throw new Error('User not found');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const recordId = crypto.randomBytes(16).toString('hex');
|
|
13
|
+
const payload = {
|
|
14
|
+
id: recordId,
|
|
15
|
+
userId: user.id,
|
|
16
|
+
contactId: body.contactId?.toString?.() ?? body.contactId,
|
|
17
|
+
contactType: body.contactType ?? 'contact',
|
|
18
|
+
contactName: body.contactName ?? '',
|
|
19
|
+
phoneNumber: body.phoneNumber ?? '',
|
|
20
|
+
status: 'scheduled',
|
|
21
|
+
scheduledAt: body.scheduledAt ? new Date(body.scheduledAt) : null,
|
|
22
|
+
lastCallAt: null
|
|
23
|
+
};
|
|
24
|
+
await CallDownListModel.create(payload);
|
|
25
|
+
return { id: recordId };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function list({ jwtToken, status }) {
|
|
29
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
30
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
31
|
+
const statusParam = (status || 'All').toString().toLowerCase();
|
|
32
|
+
const whereClause = { userId: unAuthData.id };
|
|
33
|
+
if (statusParam === 'called') whereClause.status = 'called';
|
|
34
|
+
else if (['not called', 'not_called', 'notcalled'].includes(statusParam)) whereClause.status = { [Op.ne]: 'called' };
|
|
35
|
+
const items = await CallDownListModel.findAll({ where: whereClause, order: [["scheduledAt", "ASC"]] });
|
|
36
|
+
return { items };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function remove({ jwtToken, id }) {
|
|
40
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
41
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
42
|
+
const deleted = await CallDownListModel.destroy({ where: { id, userId: unAuthData.id } });
|
|
43
|
+
if (!deleted) throw new Error('Not found');
|
|
44
|
+
return { successful: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function markCalled({ jwtToken, id, lastCallAt }) {
|
|
48
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
49
|
+
if (!unAuthData?.id) throw new Error('Unauthorized');
|
|
50
|
+
const when = lastCallAt ? new Date(lastCallAt) : new Date();
|
|
51
|
+
const [affected] = await CallDownListModel.update({ status: 'called', lastCallAt: when }, { where: { id, userId: unAuthData.id } });
|
|
52
|
+
if (!affected) throw new Error('Not found');
|
|
53
|
+
return { successful: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.schedule = schedule;
|
|
57
|
+
exports.list = list;
|
|
58
|
+
exports.remove = remove;
|
|
59
|
+
exports.markCalled = markCalled;
|
|
60
|
+
|
package/handlers/log.js
CHANGED
|
@@ -9,8 +9,9 @@ const adapterRegistry = require('../adapter/registry');
|
|
|
9
9
|
const { LOG_DETAILS_FORMAT_TYPE } = require('../lib/constants');
|
|
10
10
|
const { NoteCache } = require('../models/dynamo/noteCacheSchema');
|
|
11
11
|
const moment = require('moment');
|
|
12
|
+
const { getMediaReaderLinkByPlatformMediaLink } = require('../lib/util');
|
|
12
13
|
|
|
13
|
-
async function createCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
14
|
+
async function createCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
14
15
|
try {
|
|
15
16
|
const existingCallLog = await CallLogModel.findOne({
|
|
16
17
|
where: {
|
|
@@ -42,7 +43,7 @@ async function createCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
|
42
43
|
const callLog = incomingData.logInfo;
|
|
43
44
|
const additionalSubmission = incomingData.additionalSubmission;
|
|
44
45
|
let note = incomingData.note;
|
|
45
|
-
if (isFromSSCL) {
|
|
46
|
+
if (process.env.USE_CACHE && isFromSSCL) {
|
|
46
47
|
const noteCache = await NoteCache.get({ sessionId: incomingData.logInfo.sessionId });
|
|
47
48
|
if (noteCache) {
|
|
48
49
|
note = noteCache.note;
|
|
@@ -113,6 +114,7 @@ async function createCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
|
113
114
|
aiNote,
|
|
114
115
|
transcript,
|
|
115
116
|
composedLogDetails,
|
|
117
|
+
hashedAccountId,
|
|
116
118
|
isFromSSCL
|
|
117
119
|
});
|
|
118
120
|
if (logId) {
|
|
@@ -209,6 +211,11 @@ async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
|
|
|
209
211
|
}
|
|
210
212
|
});
|
|
211
213
|
for (const sId of sessionIdsArray) {
|
|
214
|
+
if(sId == 0)
|
|
215
|
+
{
|
|
216
|
+
logs.push({ sessionId: sId, matched: false });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
212
219
|
const callLog = callLogs.find(c => c.sessionId === sId);
|
|
213
220
|
if (!callLog) {
|
|
214
221
|
logs.push({ sessionId: sId, matched: false });
|
|
@@ -288,7 +295,7 @@ async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
|
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
|
|
291
|
-
async function updateCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
298
|
+
async function updateCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
292
299
|
try {
|
|
293
300
|
const existingCallLog = await CallLogModel.findOne({
|
|
294
301
|
where: {
|
|
@@ -347,7 +354,8 @@ async function updateCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
|
347
354
|
result: incomingData.result,
|
|
348
355
|
direction: incomingData.direction,
|
|
349
356
|
from: incomingData.from,
|
|
350
|
-
to: incomingData.to
|
|
357
|
+
to: incomingData.to,
|
|
358
|
+
legs: incomingData.legs || [],
|
|
351
359
|
},
|
|
352
360
|
contactInfo: null, // Not needed for updates
|
|
353
361
|
user,
|
|
@@ -375,9 +383,11 @@ async function updateCallLog({ platform, userId, incomingData, isFromSSCL }) {
|
|
|
375
383
|
result: incomingData.result,
|
|
376
384
|
aiNote: incomingData.aiNote,
|
|
377
385
|
transcript: incomingData.transcript,
|
|
386
|
+
legs: incomingData.legs || [],
|
|
378
387
|
additionalSubmission: incomingData.additionalSubmission,
|
|
379
388
|
composedLogDetails,
|
|
380
389
|
existingCallLogDetails, // Pass the fetched details to avoid duplicate API calls
|
|
390
|
+
hashedAccountId,
|
|
381
391
|
isFromSSCL
|
|
382
392
|
});
|
|
383
393
|
return { successful: true, logId: existingCallLog.thirdPartyLogId, updatedNote, returnMessage, extraDataTracking };
|
|
@@ -515,6 +525,18 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
515
525
|
faxDocLink = message.attachments.find(a => a.type === 'RenderedDocument').link;
|
|
516
526
|
faxDownloadLink = message.attachments.find(a => a.type === 'RenderedDocument').uri + `?access_token=${incomingData.logInfo.rcAccessToken}`
|
|
517
527
|
}
|
|
528
|
+
let imageLink = null;
|
|
529
|
+
let videoLink = null;
|
|
530
|
+
if (message.attachments && message.attachments.some(a => a.type === 'MmsAttachment')) {
|
|
531
|
+
const imageAttachment = message.attachments.find(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'));
|
|
532
|
+
if (imageAttachment) {
|
|
533
|
+
imageLink = getMediaReaderLinkByPlatformMediaLink(imageAttachment?.uri);
|
|
534
|
+
}
|
|
535
|
+
const videoAttachment = message.attachments.find(a => a.type === 'MmsAttachment' && a.contentType.startsWith('video/'));
|
|
536
|
+
if (videoAttachment) {
|
|
537
|
+
videoLink = getMediaReaderLinkByPlatformMediaLink(videoAttachment?.uri);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
518
540
|
const existingSameDateMessageLog = await MessageLogModel.findOne({
|
|
519
541
|
where: {
|
|
520
542
|
conversationLogId: incomingData.logInfo.conversationLogId
|
|
@@ -522,12 +544,12 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
522
544
|
});
|
|
523
545
|
let crmLogId = ''
|
|
524
546
|
if (existingSameDateMessageLog) {
|
|
525
|
-
const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission });
|
|
547
|
+
const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission, imageLink, videoLink });
|
|
526
548
|
crmLogId = existingSameDateMessageLog.thirdPartyLogId;
|
|
527
549
|
returnMessage = updateMessageResult?.returnMessage;
|
|
528
550
|
}
|
|
529
551
|
else {
|
|
530
|
-
const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink });
|
|
552
|
+
const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink, imageLink, videoLink });
|
|
531
553
|
crmLogId = createMessageLogResult.logId;
|
|
532
554
|
returnMessage = createMessageLogResult?.returnMessage;
|
|
533
555
|
extraDataTracking = createMessageLogResult.extraDataTracking;
|