@meltwater/conversations-api-services 1.0.0
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/.drone.yml +13 -0
- package/.github/workflows/release.yml +42 -0
- package/.nvmrc +1 -0
- package/README.md +91 -0
- package/catalog-info.yaml +12 -0
- package/docs/index.md +28 -0
- package/mkdocs.yml +8 -0
- package/package.json +45 -0
- package/src/data-access/http/InstagramVideoClient.js +37 -0
- package/src/data-access/http/WarpZoneApi.client.js +39 -0
- package/src/data-access/http/amazonS3.js +41 -0
- package/src/data-access/http/asset-manager-tvm.client.js +32 -0
- package/src/data-access/http/companiesApi.client.js +35 -0
- package/src/data-access/http/credentialsApi.client.js +137 -0
- package/src/data-access/http/entitlementsApi.client.js +41 -0
- package/src/data-access/http/facebookApi.client.js +701 -0
- package/src/data-access/http/featureToggleApi.client.js +35 -0
- package/src/data-access/http/identityServices.client.js +117 -0
- package/src/data-access/http/instagramApi.client.js +496 -0
- package/src/data-access/http/ir.client.js +309 -0
- package/src/data-access/http/linkedInApi.client.js +624 -0
- package/src/data-access/http/masf.client.js +93 -0
- package/src/data-access/http/tiktokApi.client.js +477 -0
- package/src/data-access/index.js +33 -0
- package/src/lib/applicationTags.helpers.js +22 -0
- package/src/lib/configuration.js +10 -0
- package/src/lib/document-action-events.js +6 -0
- package/src/lib/externalId.helpers.js +15 -0
- package/src/lib/hiddenComment.helper.js +100 -0
- package/src/lib/logger.js +14 -0
- package/src/lib/metrics.helper.js +107 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import superagent from 'superagent';
|
|
3
|
+
import logger from '../../lib/logger.js';
|
|
4
|
+
import configuration from '../../lib/configuration.js';
|
|
5
|
+
import { metricN } from '../../lib/metrics.helper.js';
|
|
6
|
+
|
|
7
|
+
export class IRClient {
|
|
8
|
+
RETRY_COUNT = 50;
|
|
9
|
+
constructor(context) {
|
|
10
|
+
const { company, user, traceId, metrics } = context;
|
|
11
|
+
this.company = company;
|
|
12
|
+
this.user = user;
|
|
13
|
+
this.traceId = traceId;
|
|
14
|
+
this.metrics = metrics;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
let irApiKey = configuration.get('IR_API_KEY');
|
|
19
|
+
|
|
20
|
+
this.searchServicesUrl = configuration.get('SEARCH_SERVICE_URL');
|
|
21
|
+
this.searchServicesKey = irApiKey;
|
|
22
|
+
|
|
23
|
+
this.documentRevisionsServiceUrl = configuration.get(
|
|
24
|
+
'DOCUMENT_REVISIONS_SERVICE_URL'
|
|
25
|
+
);
|
|
26
|
+
this.documentServiceUrl = configuration.get('DOCUMENT_SERVICE_URL');
|
|
27
|
+
this.documentServiceKey = irApiKey;
|
|
28
|
+
|
|
29
|
+
this.exportApiUrl = configuration.get('EXPORT_API_URL');
|
|
30
|
+
this.exportApiKey = irApiKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async addRevisionsToDocuments(operations) {
|
|
34
|
+
await this.init();
|
|
35
|
+
const wait = 100;
|
|
36
|
+
const url = `${this.documentRevisionsServiceUrl}/${this.company._id}/`;
|
|
37
|
+
|
|
38
|
+
let result;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
result = await superagent
|
|
42
|
+
.patch(url)
|
|
43
|
+
.query({
|
|
44
|
+
wait,
|
|
45
|
+
apikey: this.documentServiceKey,
|
|
46
|
+
})
|
|
47
|
+
.set('x-client-name', 'engage-conversations')
|
|
48
|
+
.send(operations)
|
|
49
|
+
.then((result) => result.body);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error(
|
|
52
|
+
`Failed adding a revisions with operation:${JSON.stringify(
|
|
53
|
+
operations
|
|
54
|
+
)}`,
|
|
55
|
+
{ error, url, companyId: this.company._id, operations }
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
logger.info(`Finished adding revision operations`, { operations });
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async modifyDocuments(operations, retryAttempt = 0) {
|
|
66
|
+
await this.init();
|
|
67
|
+
const wait = 100;
|
|
68
|
+
const url = `${this.documentServiceUrl}/`;
|
|
69
|
+
|
|
70
|
+
let result;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
result = await superagent
|
|
74
|
+
.patch(url)
|
|
75
|
+
.query({
|
|
76
|
+
wait,
|
|
77
|
+
apikey: this.documentServiceKey,
|
|
78
|
+
})
|
|
79
|
+
.set('x-client-name', 'engage-conversations')
|
|
80
|
+
.send(operations)
|
|
81
|
+
.then((result) => result.body);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
retryAttempt++;
|
|
84
|
+
if (retryAttempt > this.RETRY_COUNT) {
|
|
85
|
+
logger.error(
|
|
86
|
+
`Maximum Retrys hit for method modifyDocuments ${err.status} ${err.message}`,
|
|
87
|
+
{ err, companyId: this.company._id }
|
|
88
|
+
);
|
|
89
|
+
} else if (
|
|
90
|
+
await this.retryBecauseRateLimiting(err, 'modifyDocuments')
|
|
91
|
+
) {
|
|
92
|
+
return await this.modifyDocuments(operations, retryAttempt);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
logger.info(`Finished modifying documents operations`, { operations });
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getDocumentById(id, retryAttempt = 0) {
|
|
102
|
+
await this.init();
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const uri = `${this.documentServiceUrl}/${id}/revisions/${this.company._id}?apikey=${this.documentServiceKey}`;
|
|
106
|
+
const metric = this.metrics.count(metricNames.irGetByDocumentId);
|
|
107
|
+
const result = await superagent.get(uri);
|
|
108
|
+
this.metrics.finishOperation(metricNames.irGetByDocumentId, metric);
|
|
109
|
+
return result.body;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
retryAttempt++;
|
|
112
|
+
if (retryAttempt > this.RETRY_COUNT) {
|
|
113
|
+
logger.error(
|
|
114
|
+
`Maximum Retrys hit for method getDocumentById ${err.status} ${err.message}`,
|
|
115
|
+
{ err, companyId: this.company._id }
|
|
116
|
+
);
|
|
117
|
+
} else if (
|
|
118
|
+
await this.retryBecauseRateLimiting(err, 'getDocumentById')
|
|
119
|
+
) {
|
|
120
|
+
return await this.getDocumentById(id, retryAttempt);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async retryBecauseRateLimiting(err, name) {
|
|
126
|
+
// rate limit error
|
|
127
|
+
if (err.status === 429) {
|
|
128
|
+
this.metrics.count(metricNames.irRatelimited);
|
|
129
|
+
logger.error(
|
|
130
|
+
`Error Accessing ir.client method because rate limit: trying again ${name}`,
|
|
131
|
+
{ err, companyId: this.company._id }
|
|
132
|
+
);
|
|
133
|
+
// should not be hitting rate limit in production
|
|
134
|
+
// this is a patch for staging testing
|
|
135
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (err.status == 404) {
|
|
139
|
+
logger.info(
|
|
140
|
+
`Document Not Found ir.client method ${name} ${err.status} ${err.message}`,
|
|
141
|
+
{ err, companyId: this.company._id }
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
logger.info(
|
|
145
|
+
`Could not access ir.client method ${name} ${err.status} ${err.message}`,
|
|
146
|
+
{ err, companyId: this.company._id }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async export(rune) {
|
|
153
|
+
await this.init();
|
|
154
|
+
const traceId = this.generateTraceId();
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
let uri = `${this.exportApiUrl}?apikey=${this.exportApiKey}`;
|
|
158
|
+
|
|
159
|
+
logger.info(`Starting call to : ${this.exportApiUrl}`, {
|
|
160
|
+
rune,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.addCompanyToRuneForHorace(rune);
|
|
164
|
+
|
|
165
|
+
let result = await superagent
|
|
166
|
+
.post(uri)
|
|
167
|
+
.query({
|
|
168
|
+
traceId,
|
|
169
|
+
})
|
|
170
|
+
.send(rune);
|
|
171
|
+
|
|
172
|
+
logger.info(
|
|
173
|
+
`Finished calling Export API: ${result.body.exportKey}`,
|
|
174
|
+
{
|
|
175
|
+
irTraceId: traceId,
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return result.body.exportKey;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
logger.error('Error executing search against Export API', {
|
|
182
|
+
err,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async search(rune) {
|
|
188
|
+
await this.init();
|
|
189
|
+
const traceId = this.generateTraceId();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
let uri = `${this.searchServicesUrl}?apikey=${this.searchServicesKey}`;
|
|
193
|
+
|
|
194
|
+
// todo: remove changing rune searchResults size
|
|
195
|
+
// todo: remove reduce ( dedupes and trims to original rune limit )
|
|
196
|
+
// rune limit is a bandaid, hopefully better solution can be worked out with Horace
|
|
197
|
+
let limitActual = rune.viewRequests.searchResults.size;
|
|
198
|
+
rune.viewRequests.searchResults.size = limitActual * 2;
|
|
199
|
+
|
|
200
|
+
this.addCompanyToRuneForHorace(rune);
|
|
201
|
+
|
|
202
|
+
const metric = this.metrics.count(metricNames.irSearch);
|
|
203
|
+
let result = await superagent
|
|
204
|
+
.post(uri)
|
|
205
|
+
.query({
|
|
206
|
+
traceId,
|
|
207
|
+
})
|
|
208
|
+
.send(rune);
|
|
209
|
+
this.metrics.finishOperation(metricNames.irSearch, metric);
|
|
210
|
+
|
|
211
|
+
rune.viewRequests.searchResults.size = limitActual;
|
|
212
|
+
|
|
213
|
+
const finalResults = result.body.views.searchResults.results.reduce(
|
|
214
|
+
(acc, document, index) => {
|
|
215
|
+
if (
|
|
216
|
+
!index ||
|
|
217
|
+
(acc.length <= limitActual &&
|
|
218
|
+
acc[acc.length - 1].documentId !==
|
|
219
|
+
document.quiddity.id)
|
|
220
|
+
) {
|
|
221
|
+
acc.push(document.quiddity);
|
|
222
|
+
} else if (
|
|
223
|
+
index &&
|
|
224
|
+
acc.length <= limitActual &&
|
|
225
|
+
acc[acc.length - 1].documentId === document.quiddity.id
|
|
226
|
+
) {
|
|
227
|
+
logger.info(
|
|
228
|
+
`Duplicates in IR Search Results for DocumentId ` +
|
|
229
|
+
document.quiddity.id,
|
|
230
|
+
{ document, irTraceId: traceId }
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return acc;
|
|
234
|
+
},
|
|
235
|
+
[]
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
logger.info(
|
|
239
|
+
`Final Results Calculated, ${
|
|
240
|
+
finalResults ? finalResults.length : 'no results'
|
|
241
|
+
}`,
|
|
242
|
+
{ irTraceId: traceId, rune }
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return finalResults;
|
|
246
|
+
// original return function
|
|
247
|
+
// return result.body.views.searchResults.results.map(document => {
|
|
248
|
+
// return document.quiddity;
|
|
249
|
+
// })
|
|
250
|
+
} catch (err) {
|
|
251
|
+
logger.error('Error executing search against Search Service', {
|
|
252
|
+
err,
|
|
253
|
+
irTraceId: traceId,
|
|
254
|
+
rune,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async latest(rune, retryAttempt = 0) {
|
|
260
|
+
try {
|
|
261
|
+
const result = await this.executeSearch(rune);
|
|
262
|
+
return (
|
|
263
|
+
(result.body.views.searchResults.results.length && [
|
|
264
|
+
result.body.views.searchResults.results[0].quiddity,
|
|
265
|
+
]) ||
|
|
266
|
+
[]
|
|
267
|
+
);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
retryAttempt++;
|
|
270
|
+
if (retryAttempt > this.RETRY_COUNT) {
|
|
271
|
+
logger.error(
|
|
272
|
+
`Maximum Retrys hit for method latest ${err.status} ${err.message}`,
|
|
273
|
+
{ err, companyId: this.company._id }
|
|
274
|
+
);
|
|
275
|
+
} else if (await this.retryBecauseRateLimiting(err, 'latest')) {
|
|
276
|
+
return await this.latest(rune, retryAttempt);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async count(rune) {
|
|
282
|
+
try {
|
|
283
|
+
const result = await this.executeSearch(rune);
|
|
284
|
+
return result.body.views.documentCount.totalCount;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
logger.error('Error getting count from Search Service', err);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async executeSearch(rune) {
|
|
291
|
+
await this.init();
|
|
292
|
+
this.addCompanyToRuneForHorace(rune);
|
|
293
|
+
const uri = `${this.searchServicesUrl}?apikey=${this.searchServicesKey}`;
|
|
294
|
+
return await superagent.post(uri).send(rune);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
addCompanyToRuneForHorace(rune) {
|
|
298
|
+
if (!rune.metaData) {
|
|
299
|
+
rune.metaData = {};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Horace requires this for tracking purposes
|
|
303
|
+
rune.metaData.companyId = this.company._id;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
generateTraceId() {
|
|
307
|
+
return `engage_${this.company._id}_${this.traceId}_${v4()}`;
|
|
308
|
+
}
|
|
309
|
+
}
|