@itentialopensource/adapter-kafkav2 0.19.0 → 0.21.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/CHANGELOG.md +16 -0
- package/adapter.js +133 -5
- package/package.json +2 -3
- package/pronghorn.json +1 -3
- package/refs?service=git-upload-pack +0 -0
- package/utils/dbUtil.js +1603 -0
- package/utils/pre-commit.sh +1 -1
package/utils/dbUtil.js
ADDED
|
@@ -0,0 +1,1603 @@
|
|
|
1
|
+
/* Required libraries. */
|
|
2
|
+
/* global g_redis log */
|
|
3
|
+
/* eslint consistent-return:warn */
|
|
4
|
+
/* eslint no-underscore-dangle: [2, { "allow": ["_id"] }] */
|
|
5
|
+
/* eslint no-unused-vars:warn */
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const uuid = require('uuid');
|
|
9
|
+
|
|
10
|
+
/* Fetch in the other needed components for the this Adaptor */
|
|
11
|
+
const {
|
|
12
|
+
MongoClient, MongoClientOptions, CreateIndexesOptions, CountDocumentsOptions,
|
|
13
|
+
DeleteOptions, DeleteResult, ReplaceOptions, Document, UpdateResult, WithId, Filter,
|
|
14
|
+
UpdateFilter, FindOneAndUpdateOptions, Sort
|
|
15
|
+
} = require('mongodb');
|
|
16
|
+
|
|
17
|
+
// Other global variables
|
|
18
|
+
let adapterDir = '';
|
|
19
|
+
let saveFS = false;
|
|
20
|
+
let storDir = `${adapterDir}/storage`;
|
|
21
|
+
let entityDir = `${adapterDir}/entities`;
|
|
22
|
+
let id = null;
|
|
23
|
+
|
|
24
|
+
const Storage = {
|
|
25
|
+
UNDEFINED: 0,
|
|
26
|
+
DBINFO: 1,
|
|
27
|
+
ADAPTERDB: 2,
|
|
28
|
+
FILESYSTEM: 3,
|
|
29
|
+
IAPDB: 4
|
|
30
|
+
};
|
|
31
|
+
Object.freeze(Storage);
|
|
32
|
+
|
|
33
|
+
/* DB UTILS INTERNAL FUNCTIONS */
|
|
34
|
+
/** getFromJson
|
|
35
|
+
* @summary returns information from a json file on the file system
|
|
36
|
+
*/
|
|
37
|
+
function getFromJson(fileName, filter) {
|
|
38
|
+
const origin = `${id}-dbUtil-getFromJson`;
|
|
39
|
+
log.trace(origin);
|
|
40
|
+
|
|
41
|
+
// verify the required data has been provided
|
|
42
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
43
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// get the entity and the action if this is a metric
|
|
48
|
+
let ent = null;
|
|
49
|
+
let act = null;
|
|
50
|
+
let metric = false;
|
|
51
|
+
const useFilter = filter;
|
|
52
|
+
if (useFilter && useFilter.metric) {
|
|
53
|
+
metric = true;
|
|
54
|
+
if (useFilter.metric.entity) {
|
|
55
|
+
ent = useFilter.metric.entity;
|
|
56
|
+
}
|
|
57
|
+
if (useFilter.metric.action) {
|
|
58
|
+
act = useFilter.metric.action;
|
|
59
|
+
}
|
|
60
|
+
if (!ent || !act) {
|
|
61
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
delete useFilter.metric;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (fileName === 'adapter_configs') {
|
|
69
|
+
const retEntity = filter.entity;
|
|
70
|
+
if (!retEntity || !fs.existsSync(`${entityDir}`) || !fs.existsSync(`${entityDir}/${retEntity}`)) {
|
|
71
|
+
log.warn(`${origin}: Could not find adapter entity directory ${retEntity} - nothing to retrieve`);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (!fs.existsSync(`${entityDir}/${retEntity}/action.json`)) {
|
|
75
|
+
log.warn(`${origin}: Could not find action.json for entity ${retEntity} - nothing to retrieve`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// load the mockdatafiles
|
|
80
|
+
let files = fs.readdirSync(`${entityDir}/${retEntity}`);
|
|
81
|
+
const mockdatafiles = {};
|
|
82
|
+
if (files.includes('mockdatafiles') && fs.lstatSync(`${entityDir}/${retEntity}/mockdatafiles`).isDirectory()) {
|
|
83
|
+
fs.readdirSync(`${entityDir}/${retEntity}/mockdatafiles`).forEach((file) => {
|
|
84
|
+
if (file.split('.').pop() === 'json') {
|
|
85
|
+
const mockpath = `${entityDir}/${retEntity}/mockdatafiles/${file}`;
|
|
86
|
+
let data = {};
|
|
87
|
+
try {
|
|
88
|
+
data = JSON.parse(fs.readFileSync(mockpath));
|
|
89
|
+
} catch (ex) {
|
|
90
|
+
log.warn(`could not parse mockdata file ${file}, using empty object!`);
|
|
91
|
+
}
|
|
92
|
+
mockdatafiles[mockpath.split('/').pop()] = data;
|
|
93
|
+
} else if (file.split('.').pop() === 'xml') {
|
|
94
|
+
const mockpath = `${entityDir}/${retEntity}/mockdatafiles/${file}`;
|
|
95
|
+
mockdatafiles[mockpath.split('/').pop()] = fs.readFileSync(mockpath);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// load the action data
|
|
101
|
+
let actions;
|
|
102
|
+
if (files.includes('action.json')) {
|
|
103
|
+
actions = JSON.parse(fs.readFileSync(`${entityDir}/${retEntity}/action.json`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Load schema.json and other schemas in remaining json files
|
|
107
|
+
files = files.filter((f) => (f !== 'action.json') && f.endsWith('.json'));
|
|
108
|
+
const schema = [];
|
|
109
|
+
files.forEach((file) => {
|
|
110
|
+
const data = JSON.parse(fs.readFileSync(`${entityDir}/${retEntity}/${file}`));
|
|
111
|
+
schema.push({
|
|
112
|
+
name: file,
|
|
113
|
+
schema: data
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// return the data
|
|
118
|
+
return {
|
|
119
|
+
actions: actions.actions,
|
|
120
|
+
schema,
|
|
121
|
+
mockdatafiles
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// make sure what we need exists and has been provided
|
|
126
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
127
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to retrieve`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// determine if the file exists so we retrieve the data
|
|
132
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
133
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
134
|
+
const toReturn = [];
|
|
135
|
+
|
|
136
|
+
if (metric) {
|
|
137
|
+
// if metric need to match the entity and action
|
|
138
|
+
content.table.forEach((item) => {
|
|
139
|
+
if (ent === item.entity && act === item.action) {
|
|
140
|
+
toReturn.push(item);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
// if not metric must match the items in the filter
|
|
145
|
+
// if no filter, match everything
|
|
146
|
+
let key = [];
|
|
147
|
+
if (useFilter && useFilter.length > 0) {
|
|
148
|
+
key = Object.keys(useFilter);
|
|
149
|
+
}
|
|
150
|
+
content.table.forEach((item) => {
|
|
151
|
+
let push = true;
|
|
152
|
+
// check each key - if it does not match a key do not add to return data
|
|
153
|
+
key.forEach((fil) => {
|
|
154
|
+
if (useFilter[fil] !== item[fil]) push = false;
|
|
155
|
+
});
|
|
156
|
+
if (push) toReturn.push(item);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// not sure why we do this - a little different than above.
|
|
160
|
+
const filtered = content.table.filter((el) => {
|
|
161
|
+
if (useFilter) {
|
|
162
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
163
|
+
if (el[obj] !== useFilter[obj]) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
return toReturn;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// if no file, nothing to return
|
|
174
|
+
return null;
|
|
175
|
+
} catch (ex) {
|
|
176
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** countJSON
|
|
182
|
+
* @summary returns a count from a json storage file
|
|
183
|
+
*/
|
|
184
|
+
function countJSON(fileName, filter) {
|
|
185
|
+
const origin = `${id}-dbUtil-countJSON`;
|
|
186
|
+
log.trace(origin);
|
|
187
|
+
|
|
188
|
+
// verify the required data has been provided
|
|
189
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
190
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// make sure what we need exists and has been provided
|
|
196
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
197
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to count`);
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// determine if the file exists so we can count data from it
|
|
202
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
203
|
+
const data = getFromJson(fileName, filter);
|
|
204
|
+
if (data) {
|
|
205
|
+
return data.length;
|
|
206
|
+
}
|
|
207
|
+
return -1;
|
|
208
|
+
}
|
|
209
|
+
} catch (ex) {
|
|
210
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** saveAsJson
|
|
216
|
+
* @summary saves information into a json storage file
|
|
217
|
+
*/
|
|
218
|
+
function saveAsJson(fileName, data) {
|
|
219
|
+
const origin = `${id}-dbUtil-saveAsJson`;
|
|
220
|
+
log.trace(origin);
|
|
221
|
+
|
|
222
|
+
// verify the required data has been provided
|
|
223
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
224
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// get the entity and the action if this is a metric
|
|
229
|
+
let ent = null;
|
|
230
|
+
let act = null;
|
|
231
|
+
let metric = false;
|
|
232
|
+
const useData = data;
|
|
233
|
+
if (useData && useData.metric) {
|
|
234
|
+
metric = true;
|
|
235
|
+
if (useData.metric.entity) {
|
|
236
|
+
ent = useData.metric.entity;
|
|
237
|
+
}
|
|
238
|
+
if (useData.metric.action) {
|
|
239
|
+
act = useData.metric.action;
|
|
240
|
+
}
|
|
241
|
+
if (!ent || !act) {
|
|
242
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
delete useData.metric;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
// make sure what we need exists and has been provided
|
|
250
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
251
|
+
// need to make the storage directory if it does not exist
|
|
252
|
+
fs.mkdirSync(`${storDir}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// determine if the file exists so we add data to it
|
|
256
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
257
|
+
// have to read, append & save
|
|
258
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
259
|
+
let exists = false;
|
|
260
|
+
|
|
261
|
+
content.table.forEach((item) => {
|
|
262
|
+
const toPush = item;
|
|
263
|
+
if (metric) {
|
|
264
|
+
// if it is a metric and we already have entity and action need to edit the data
|
|
265
|
+
if (ent === item.entity && act === item.action) {
|
|
266
|
+
exists = true;
|
|
267
|
+
Object.keys(useData).forEach((key) => {
|
|
268
|
+
if (key === '$inc') {
|
|
269
|
+
Object.keys(useData[key]).forEach((inc) => {
|
|
270
|
+
if (!toPush[inc]) {
|
|
271
|
+
toPush[inc] = useData[key][inc];
|
|
272
|
+
}
|
|
273
|
+
toPush[inc] += useData[key][inc];
|
|
274
|
+
});
|
|
275
|
+
} else if (key === '$set') {
|
|
276
|
+
Object.keys(useData[key]).forEach((set) => {
|
|
277
|
+
toPush[set] = useData[key][set];
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
if (!exists) {
|
|
285
|
+
// push new thing to table.
|
|
286
|
+
const toPush = {};
|
|
287
|
+
const keysArray = Object.keys(useData);
|
|
288
|
+
keysArray.forEach((i) => {
|
|
289
|
+
const newKeys = Object.keys(useData[i]);
|
|
290
|
+
newKeys.forEach((j) => {
|
|
291
|
+
toPush[j] = useData[i][j];
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
content.table.push(toPush);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// now that is updated, write the file back out
|
|
298
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
299
|
+
return useData;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// if the file has not been created yet
|
|
303
|
+
const obj = { table: [] };
|
|
304
|
+
const toPush = {};
|
|
305
|
+
const keysArray = Object.keys(data);
|
|
306
|
+
keysArray.forEach((outer) => {
|
|
307
|
+
const newKeys = Object.keys(data[outer]);
|
|
308
|
+
newKeys.forEach((inner) => {
|
|
309
|
+
toPush[inner] = data[outer][inner];
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
obj.table.push(toPush);
|
|
313
|
+
|
|
314
|
+
// write the file out to the file system
|
|
315
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(obj, null, 2));
|
|
316
|
+
return data;
|
|
317
|
+
} catch (ex) {
|
|
318
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** removeFromJSON
|
|
324
|
+
* @summary removes information from a json storage file
|
|
325
|
+
*/
|
|
326
|
+
function removeFromJSON(fileName, filter, multiple) {
|
|
327
|
+
const origin = `${id}-dbUtil-removeFromJSON`;
|
|
328
|
+
log.trace(origin);
|
|
329
|
+
|
|
330
|
+
// verify the required data has been provided
|
|
331
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
332
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// get the entity and the action if this is a metric
|
|
337
|
+
let ent = null;
|
|
338
|
+
let act = null;
|
|
339
|
+
let metric = false;
|
|
340
|
+
const useFilter = filter;
|
|
341
|
+
if (useFilter && useFilter.metric) {
|
|
342
|
+
metric = true;
|
|
343
|
+
if (useFilter.metric.entity) {
|
|
344
|
+
ent = useFilter.metric.entity;
|
|
345
|
+
}
|
|
346
|
+
if (useFilter.metric.action) {
|
|
347
|
+
act = useFilter.metric.action;
|
|
348
|
+
}
|
|
349
|
+
if (!ent && !act) {
|
|
350
|
+
log.warn(`${origin}: metric provided with one of entity/action, require both`);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
delete useFilter.metric;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
// make sure what we need exists and has been provided
|
|
358
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
359
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to remove`);
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// determine if the file exists so we remove data from it
|
|
364
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
365
|
+
const content = JSON.parse(fs.readFileSync(`${storDir}/${fileName}.json`, 'utf-8'));
|
|
366
|
+
const toReturn = [];
|
|
367
|
+
|
|
368
|
+
// if this is a metric make sure the entity and action match
|
|
369
|
+
if (metric) {
|
|
370
|
+
content.table = content.table.filter((item) => {
|
|
371
|
+
if (ent === item.entity && act === item.action) {
|
|
372
|
+
toReturn.push(item);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
});
|
|
377
|
+
} else {
|
|
378
|
+
// go through content to determine if item matches the filter
|
|
379
|
+
let ctr = 0;
|
|
380
|
+
// create the contents that are being removed
|
|
381
|
+
const removed = content.table.filter((el) => {
|
|
382
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
383
|
+
if (el[obj] !== useFilter[obj]) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
ctr += 1;
|
|
388
|
+
if (!multiple && ctr > 1) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
});
|
|
393
|
+
let ctr1 = 0;
|
|
394
|
+
// remove the items from the contents
|
|
395
|
+
content.table = content.table.filter((el, i) => {
|
|
396
|
+
Object.keys(useFilter).forEach((obj) => {
|
|
397
|
+
if (el[obj] !== useFilter[obj]) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
ctr1 += 1;
|
|
402
|
+
if (!multiple && ctr1 > 1) {
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// write the contents back to the file
|
|
409
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
410
|
+
return removed;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// write the contents back to the file
|
|
414
|
+
fs.writeFileSync(`${storDir}/${fileName}.json`, JSON.stringify(content, null, 2));
|
|
415
|
+
return toReturn;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
log.error(`${origin}: Collection ${fileName} does not exist`);
|
|
419
|
+
return null;
|
|
420
|
+
} catch (ex) {
|
|
421
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** deleteJSON
|
|
427
|
+
* @summary deletes a json storage file
|
|
428
|
+
*/
|
|
429
|
+
function deleteJSON(fileName) {
|
|
430
|
+
const origin = `${id}-dbUtil-deleteJSON`;
|
|
431
|
+
log.trace(origin);
|
|
432
|
+
|
|
433
|
+
// verify the required data has been provided
|
|
434
|
+
if (fileName === undefined || fileName === null || fileName === '') {
|
|
435
|
+
log.warn(`${origin}: Must provide a file name`);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
// make sure what we need exists and has been provided
|
|
441
|
+
if (!fs.existsSync(`${storDir}`)) {
|
|
442
|
+
log.warn(`${origin}: Could not find adapter storage directory - nothing to delete`);
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// determine if the file exists so we remove - also assume we add a .json suffix
|
|
447
|
+
if (fs.existsSync(`${storDir}/${fileName}.json`)) {
|
|
448
|
+
fs.remove(`${storDir}/${fileName}.json`).catch((some) => {
|
|
449
|
+
log.info(`${origin}: ${some}`);
|
|
450
|
+
fs.rmdirSync(`${storDir}/${fileName}.json`);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// successful -- return the fileName
|
|
455
|
+
return fileName;
|
|
456
|
+
} catch (ex) {
|
|
457
|
+
log.warn(`${origin}: Caught Exception ${ex}`);
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
class DBUtil {
|
|
463
|
+
/**
|
|
464
|
+
* These are database utilities that can be used by the adapter to interact with a
|
|
465
|
+
* mongo database
|
|
466
|
+
* @constructor
|
|
467
|
+
*/
|
|
468
|
+
constructor(prongId, properties, directory) {
|
|
469
|
+
this.myid = prongId;
|
|
470
|
+
id = prongId;
|
|
471
|
+
this.baseDir = directory;
|
|
472
|
+
adapterDir = this.baseDir;
|
|
473
|
+
storDir = `${adapterDir}/storage`;
|
|
474
|
+
entityDir = `${adapterDir}/entities`;
|
|
475
|
+
this.props = properties;
|
|
476
|
+
this.adapterMongoClient = null;
|
|
477
|
+
|
|
478
|
+
// set up the properties I care about
|
|
479
|
+
this.refreshProperties(properties);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* refreshProperties is used to set up all of the properties for the db utils.
|
|
484
|
+
* It allows properties to be changed later by simply calling refreshProperties rather
|
|
485
|
+
* than having to restart the db utils.
|
|
486
|
+
*
|
|
487
|
+
* @function refreshProperties
|
|
488
|
+
* @param {Object} properties - an object containing all of the properties
|
|
489
|
+
*/
|
|
490
|
+
refreshProperties(properties) {
|
|
491
|
+
const origin = `${this.myid}-dbUtil-refreshProperties`;
|
|
492
|
+
log.trace(origin);
|
|
493
|
+
this.dburl = null;
|
|
494
|
+
this.dboptions = {};
|
|
495
|
+
this.database = this.myid;
|
|
496
|
+
|
|
497
|
+
// verify the necessary information was received
|
|
498
|
+
if (!properties) {
|
|
499
|
+
log.error(`${origin}: DB Utils received no properties!`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (!properties.mongo || !properties.mongo.host) {
|
|
503
|
+
log.info(`${origin}: No default adapter database configured!`);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
this.dboptions = properties.mongo.dboptions || {};
|
|
507
|
+
|
|
508
|
+
// set the database port
|
|
509
|
+
let port = 27017;
|
|
510
|
+
if (properties.mongo.port) {
|
|
511
|
+
port = properties.mongo.port;
|
|
512
|
+
}
|
|
513
|
+
// set the database
|
|
514
|
+
if (properties.mongo.database) {
|
|
515
|
+
this.database = properties.mongo.database;
|
|
516
|
+
}
|
|
517
|
+
// set the user
|
|
518
|
+
let username = null;
|
|
519
|
+
if (properties.mongo.username) {
|
|
520
|
+
username = properties.mongo.username;
|
|
521
|
+
}
|
|
522
|
+
// set the password
|
|
523
|
+
let password = null;
|
|
524
|
+
if (properties.mongo.password) {
|
|
525
|
+
password = properties.mongo.password;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// format the database url
|
|
529
|
+
this.dburl = 'mongodb://';
|
|
530
|
+
log.info(`${origin}: Default adapter database at: ${properties.mongo.host}`);
|
|
531
|
+
|
|
532
|
+
if (username) {
|
|
533
|
+
this.dburl += `${encodeURIComponent(username)}:${encodeURIComponent(password)}@`;
|
|
534
|
+
log.info(`${origin}: Default adapter database will use authentication.`);
|
|
535
|
+
}
|
|
536
|
+
this.dburl += `${encodeURIComponent(properties.mongo.host)}:${encodeURIComponent(port)}/${encodeURIComponent(this.database)}`;
|
|
537
|
+
|
|
538
|
+
// are we using a replication set need to add it to the url
|
|
539
|
+
if (properties.mongo.replSet) {
|
|
540
|
+
this.dburl += `?${properties.mongo.replSet}`;
|
|
541
|
+
log.info(`${origin}: Default adapter database will use replica set.`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Do we need SSL to connect to the database
|
|
545
|
+
if (properties.mongo.db_ssl && properties.mongo.db_ssl.enabled === true) {
|
|
546
|
+
log.info(`${origin}: Default adapter database will use SSL.`);
|
|
547
|
+
this.dboptions.ssl = true;
|
|
548
|
+
|
|
549
|
+
// validate the server's certificate against a known certificate authority?
|
|
550
|
+
if (properties.mongo.db_ssl.accept_invalid_cert === false) {
|
|
551
|
+
this.dboptions.sslValidate = true;
|
|
552
|
+
log.info(`${origin}: Default adapter database will use Certificate based SSL.`);
|
|
553
|
+
// if validation is enabled, we need to read the CA file
|
|
554
|
+
if (properties.mongo.db_ssl.ca_file) {
|
|
555
|
+
try {
|
|
556
|
+
this.dboptions.sslCA = [fs.readFileSync(properties.mongo.db_ssl.ca_file)];
|
|
557
|
+
} catch (err) {
|
|
558
|
+
log.error(`${origin}: Error: unable to load Mongo CA file: ${err}`);
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
log.error(`${origin}: Error: Certificate validation is enabled but a CA is not specified.`);
|
|
562
|
+
}
|
|
563
|
+
if (properties.mongo.db_ssl.key_file) {
|
|
564
|
+
try {
|
|
565
|
+
this.dboptions.sslKey = [fs.readFileSync(properties.mongo.db_ssl.key_file)];
|
|
566
|
+
} catch (err) {
|
|
567
|
+
log.error(`${origin}: Error: Unable to load Mongo Key file: ${err}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (properties.mongo.db_ssl.cert_file) {
|
|
571
|
+
try {
|
|
572
|
+
this.dboptions.sslCert = [fs.readFileSync(properties.mongo.db_ssl.cert_file)];
|
|
573
|
+
} catch (err) {
|
|
574
|
+
log.error(`${origin}: Error: Unable to load Mongo Certificate file: ${err}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} else {
|
|
578
|
+
this.dboptions.sslValidate = false;
|
|
579
|
+
log.info(`${origin}: Default adapter database not using Certificate based SSL.`);
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
log.info(`${origin}: Default adapter database not using SSL.`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// if we were provided with a path to save metrics - instead of just a boolean
|
|
586
|
+
// will assume this is a place where all adapter stuff can go!
|
|
587
|
+
saveFS = this.props.save_metric || false;
|
|
588
|
+
if (saveFS && typeof saveFS === 'string' && saveFS !== '') {
|
|
589
|
+
storDir = saveFS;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* @callback determineStorageCallback
|
|
595
|
+
* @param {string} err - the error message
|
|
596
|
+
* @param {int} storageType - the storage type
|
|
597
|
+
* @param {MongoClient} mongoClient - the mongo client
|
|
598
|
+
* @param {string} database - the database
|
|
599
|
+
*/
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* @typedef DBInfo
|
|
603
|
+
* @property {string} dburl
|
|
604
|
+
* @property {MongoClientOptions} dboptions
|
|
605
|
+
* @property {string} database
|
|
606
|
+
*/
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* @typedef StorageInfo
|
|
610
|
+
* @property {int} storageType
|
|
611
|
+
* @property {MongoClient} mongoClient
|
|
612
|
+
* @property {string} database
|
|
613
|
+
*/
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Call to determine the storage target. If the storage is a Mongo database, then
|
|
617
|
+
* the client connection and database is returned.
|
|
618
|
+
*
|
|
619
|
+
* @function determineStorage
|
|
620
|
+
* @param {DBInfo | null} dbInfo - the url for the database to connect to (dburl, dboptions, database). If null, tries to connect to adapter database, then the filesystem
|
|
621
|
+
* @param {determineStorageCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
622
|
+
*
|
|
623
|
+
* @returns {Promise<StorageInfo>} - {storageType, mongoClient, database}. storageType is an enum
|
|
624
|
+
* @throws {string} - the error message
|
|
625
|
+
*/
|
|
626
|
+
async determineStorage(dbInfo, callback = undefined) {
|
|
627
|
+
const origin = `${this.myid}-dbUtil-determineStorage`;
|
|
628
|
+
const useCallback = typeof callback === 'function';
|
|
629
|
+
|
|
630
|
+
const createReturnObj = (storageType, mongoClient, database) => ({
|
|
631
|
+
storageType,
|
|
632
|
+
mongoClient,
|
|
633
|
+
database
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
let mongoClient = null;
|
|
637
|
+
let storageType = null;
|
|
638
|
+
let database = null;
|
|
639
|
+
|
|
640
|
+
if (dbInfo) {
|
|
641
|
+
// priority 1 - use the dbInfo passed in
|
|
642
|
+
if (dbInfo.dburl && dbInfo.database) {
|
|
643
|
+
try {
|
|
644
|
+
mongoClient = await MongoClient.connect(dbInfo.dburl, dbInfo.dboptions);
|
|
645
|
+
log.debug('Using dbInfo');
|
|
646
|
+
storageType = Storage.DBINFO;
|
|
647
|
+
database = dbInfo.database;
|
|
648
|
+
if (useCallback) return callback(null, storageType, mongoClient, database);
|
|
649
|
+
return createReturnObj(storageType, mongoClient, database);
|
|
650
|
+
} catch (err) {
|
|
651
|
+
const msg = `${origin}: Error! Failed to connect to database: ${err}`;
|
|
652
|
+
log.error(msg);
|
|
653
|
+
if (useCallback) return callback(msg, null, null, null);
|
|
654
|
+
throw msg;
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
const msg = 'Error! Malformed dbInfo';
|
|
658
|
+
if (useCallback) return callback(msg, null, null, Storage.DBINFO); // Unknown why callback ends with Storage.DBINFO here, kept for legacy purposes
|
|
659
|
+
throw msg;
|
|
660
|
+
}
|
|
661
|
+
} else if (this.adapterMongoClient) {
|
|
662
|
+
// priority 2 - use the adapter database
|
|
663
|
+
log.debug('Using adapter db');
|
|
664
|
+
storageType = Storage.ADAPTERDB;
|
|
665
|
+
mongoClient = this.adapterMongoClient;
|
|
666
|
+
database = this.database;
|
|
667
|
+
if (useCallback) return callback(null, storageType, mongoClient, database);
|
|
668
|
+
return createReturnObj(storageType, mongoClient, database);
|
|
669
|
+
} else if (this.dburl === null && dbInfo === null) {
|
|
670
|
+
// priority 3 - use the filesystem
|
|
671
|
+
log.debug('Using filesystem');
|
|
672
|
+
storageType = Storage.FILESYSTEM;
|
|
673
|
+
if (useCallback) return callback(null, storageType, null, null);
|
|
674
|
+
return createReturnObj(storageType, mongoClient, database);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @callback connectCallback
|
|
680
|
+
* @param {boolean} isAlive
|
|
681
|
+
* @param {MongoClient} mongoClient
|
|
682
|
+
*/
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* @typedef ConnectionInfo
|
|
686
|
+
* @property {boolean} isAlive
|
|
687
|
+
* @property {MongoClient} mongoClient
|
|
688
|
+
*/
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Call to connect and authenticate to the adapter database
|
|
692
|
+
*
|
|
693
|
+
* @function connect
|
|
694
|
+
* @param {connectCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
695
|
+
*
|
|
696
|
+
* @returns {Promise<ConnectionInfo>}
|
|
697
|
+
*/
|
|
698
|
+
async connect(callback = undefined) {
|
|
699
|
+
const origin = `${this.myid}-dbUtil-connect`;
|
|
700
|
+
const useCallback = typeof callback === 'function';
|
|
701
|
+
log.trace(origin);
|
|
702
|
+
|
|
703
|
+
let mongoClient = null;
|
|
704
|
+
|
|
705
|
+
// const options = (replSetEnabled === true) ? { replSet: opts } : { server: opts };
|
|
706
|
+
log.debug(`${origin}: Connecting to MongoDB with options ${JSON.stringify(this.dboptions)}`);
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
// Now we will start the process of connecting to mongo db
|
|
710
|
+
mongoClient = await MongoClient.connect(this.dburl, this.dboptions);
|
|
711
|
+
mongoClient.on('close', () => {
|
|
712
|
+
this.alive = false;
|
|
713
|
+
log.error(`${origin}: MONGO CONNECTION LOST...`);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
mongoClient.on('reconnect', async () => {
|
|
717
|
+
// we still need to check if we are properly authenticated
|
|
718
|
+
// so we just list collections to test it.
|
|
719
|
+
try {
|
|
720
|
+
await this.clientDB.listCollections().toArray();
|
|
721
|
+
log.info(`${origin}: MONGO CONNECTION BACK...`);
|
|
722
|
+
this.alive = true;
|
|
723
|
+
this.adapterMongoClient = mongoClient;
|
|
724
|
+
} catch (error) {
|
|
725
|
+
log.error(`${origin}: ${error}`);
|
|
726
|
+
this.alive = false;
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
log.info(`${origin}: mongo running @${this.dburl}/${this.database}`);
|
|
731
|
+
this.clientDB = mongoClient.db(this.database);
|
|
732
|
+
this.adapterMongoClient = mongoClient;
|
|
733
|
+
|
|
734
|
+
// we don't have authentication defined but we still need to check if Mongo does not
|
|
735
|
+
// require one, so we just list collections to test if it's doable.
|
|
736
|
+
try {
|
|
737
|
+
await this.clientDB.listCollections().toArray();
|
|
738
|
+
log.info(`${origin}: MongoDB connection has been established`);
|
|
739
|
+
this.alive = true;
|
|
740
|
+
this.adapterMongoClient = mongoClient;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
log.error(`${origin}: ${error}`);
|
|
743
|
+
this.alive = false;
|
|
744
|
+
}
|
|
745
|
+
if (useCallback) return callback(this.alive, mongoClient);
|
|
746
|
+
return { isAlive: this.alive, mongoClient };
|
|
747
|
+
} catch (err) {
|
|
748
|
+
log.error(`${origin}: Error! Exiting... Must start MongoDB first ${err}`);
|
|
749
|
+
this.alive = false;
|
|
750
|
+
if (useCallback) return callback(this.alive, mongoClient);
|
|
751
|
+
return { isAlive: this.alive, mongoClient };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Call to disconnect from the adapter database
|
|
757
|
+
*
|
|
758
|
+
* @function disconnect
|
|
759
|
+
*/
|
|
760
|
+
async disconnect() {
|
|
761
|
+
if (this.adapterMongoClient) {
|
|
762
|
+
await this.adapterMongoClient.close();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @callback collectionCallback
|
|
768
|
+
* @param {string} err - the error message
|
|
769
|
+
* @param {string} collectionName
|
|
770
|
+
*/
|
|
771
|
+
/**
|
|
772
|
+
* createCollection creates the provided collection in the file system or database.
|
|
773
|
+
*
|
|
774
|
+
* @function createCollection
|
|
775
|
+
* @param {string} collectionName - the name of the collection to create
|
|
776
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
777
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
778
|
+
* @param {collectionCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
779
|
+
*
|
|
780
|
+
* @returns {Promise<string>} - the name of the collection
|
|
781
|
+
* @throws {string} - the error message
|
|
782
|
+
*/
|
|
783
|
+
async createCollection(collectionName, dbInfo, fsWrite, callback = undefined) {
|
|
784
|
+
const origin = `${this.myid}-dbUtil-createCollection`;
|
|
785
|
+
const useCallback = typeof callback === 'function';
|
|
786
|
+
log.trace(origin);
|
|
787
|
+
|
|
788
|
+
// verify the required data has been provided
|
|
789
|
+
if (!collectionName || (typeof collectionName !== 'string')) {
|
|
790
|
+
const msg = `${origin}: Missing Collection Name or not string`;
|
|
791
|
+
log.warn(msg);
|
|
792
|
+
if (useCallback) return callback(msg, null);
|
|
793
|
+
throw msg;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
let res;
|
|
797
|
+
try {
|
|
798
|
+
res = await this.determineStorage(dbInfo);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
const msg = `${origin}: ${err}`;
|
|
801
|
+
if (useCallback) return callback(msg, null);
|
|
802
|
+
throw msg;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// if using file storage
|
|
806
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
807
|
+
// if there is no adapter directory - can not do anything so error
|
|
808
|
+
if (!fs.existsSync(`${this.baseDir}`)) {
|
|
809
|
+
const msg = `${origin}: Not able to create storage - missing base directory!`;
|
|
810
|
+
log.warn(msg);
|
|
811
|
+
if (useCallback) return callback(msg, null);
|
|
812
|
+
throw msg;
|
|
813
|
+
}
|
|
814
|
+
// if there is no storage directory - create it
|
|
815
|
+
if (!fs.existsSync(`${this.baseDir}/storage`)) {
|
|
816
|
+
fs.mkdirSync(`${this.baseDir}/storage`);
|
|
817
|
+
}
|
|
818
|
+
// if the collection already exists - no need to create it
|
|
819
|
+
if (fs.existsSync(`${this.baseDir}/storage/${collectionName}`)) {
|
|
820
|
+
log.debug(`${origin}: storage file collection already exists`);
|
|
821
|
+
return collectionName;
|
|
822
|
+
}
|
|
823
|
+
// create the new collection on the file system
|
|
824
|
+
fs.mkdirSync(`${this.baseDir}/storage/${collectionName}`);
|
|
825
|
+
log.debug(`${origin}: storage file collection ${collectionName} created`);
|
|
826
|
+
return collectionName;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// if using MongoDB storage
|
|
830
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
831
|
+
let collection;
|
|
832
|
+
try {
|
|
833
|
+
collection = await res.mongoClient.db(res.database).createCollection(collectionName);
|
|
834
|
+
} catch (err) {
|
|
835
|
+
// error we get back if the collection already existed - not a true error
|
|
836
|
+
if (err.codeName === 'NamespaceExists') {
|
|
837
|
+
log.debug(`${origin}: database collection already exists`);
|
|
838
|
+
if (useCallback) return callback(null, collectionName);
|
|
839
|
+
return collectionName;
|
|
840
|
+
}
|
|
841
|
+
const msg = `${origin}: Error creating collection ${err}`;
|
|
842
|
+
log.warn(msg);
|
|
843
|
+
if (useCallback) return callback(msg, null);
|
|
844
|
+
throw msg;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
log.spam(`${origin}: db response ${res}`);
|
|
848
|
+
log.debug(`${origin}: database collection ${collectionName} created`);
|
|
849
|
+
if (useCallback) return callback(null, collectionName);
|
|
850
|
+
return collectionName;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* removeCollection removes the provided collection from the file system or database.
|
|
858
|
+
*
|
|
859
|
+
* @function removeCollection
|
|
860
|
+
* @param {string} collectionName - the name of the collection to remove
|
|
861
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
862
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
863
|
+
* @param {collectionCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
864
|
+
*
|
|
865
|
+
* @returns {Promise<string>} - the name of the collection
|
|
866
|
+
* @throws {string} - the error message
|
|
867
|
+
*/
|
|
868
|
+
async removeCollection(collectionName, dbInfo, fsWrite, callback = undefined) {
|
|
869
|
+
const origin = `${this.myid}-dbUtil-removeCollection`;
|
|
870
|
+
const useCallback = typeof callback === 'function';
|
|
871
|
+
log.trace(origin);
|
|
872
|
+
|
|
873
|
+
// verify the required data has been provided
|
|
874
|
+
if (!collectionName || (typeof collectionName !== 'string')) {
|
|
875
|
+
const msg = `${origin}: Missing Collection Name or not string`;
|
|
876
|
+
log.warn(msg);
|
|
877
|
+
if (useCallback) return callback(msg, null);
|
|
878
|
+
throw msg;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
let res;
|
|
882
|
+
try {
|
|
883
|
+
res = await this.determineStorage(dbInfo);
|
|
884
|
+
} catch (err) {
|
|
885
|
+
const msg = `${origin}: ${err}`;
|
|
886
|
+
if (useCallback) return callback(msg, null);
|
|
887
|
+
throw msg;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// if using file storage
|
|
891
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
892
|
+
const deld = deleteJSON(collectionName);
|
|
893
|
+
if (deld) {
|
|
894
|
+
log.debug(`${origin}: storage file collection ${collectionName} removed`);
|
|
895
|
+
if (useCallback) return callback(null, deld);
|
|
896
|
+
return deld;
|
|
897
|
+
}
|
|
898
|
+
const msg = `${origin}: could not remove storage file collection`;
|
|
899
|
+
log.debug(msg);
|
|
900
|
+
if (useCallback) return callback(msg, null);
|
|
901
|
+
throw msg;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// if using MongoDB storage
|
|
905
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
906
|
+
// get the list of collections from the database - can we just drop?
|
|
907
|
+
let collections;
|
|
908
|
+
try {
|
|
909
|
+
collections = await res.mongoClient.db(res.database).listCollections().toArray();
|
|
910
|
+
} catch (err) {
|
|
911
|
+
const msg = `${origin}: Failed to get collections ${err}`;
|
|
912
|
+
log.warn(msg);
|
|
913
|
+
if (useCallback) return callback(msg, null);
|
|
914
|
+
throw msg;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// go through the collections to get the correct one for removal
|
|
918
|
+
const collectionToRemove = collections.find((collection) => collection.name === collectionName);
|
|
919
|
+
// If it doesn't exist, drop is implicit and successful
|
|
920
|
+
if (!collectionToRemove) {
|
|
921
|
+
if (useCallback) return callback(null, collectionName);
|
|
922
|
+
return collectionName;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// now that we found it, remove it
|
|
926
|
+
try {
|
|
927
|
+
const dropRes = await res.mongoClient.db(res.database).collection(collectionName).drop({});
|
|
928
|
+
log.spam(`${origin}: db response ${dropRes}`);
|
|
929
|
+
log.debug(`${origin}: database collection ${collectionName} removed`);
|
|
930
|
+
if (useCallback) return callback(null, collectionName);
|
|
931
|
+
return collectionName;
|
|
932
|
+
} catch (err) {
|
|
933
|
+
const msg = `${origin}: Failed to remove collection ${err}`;
|
|
934
|
+
if (useCallback) return callback(msg, null);
|
|
935
|
+
throw msg;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* @callback createCallback
|
|
944
|
+
* @param {string} err - the error message
|
|
945
|
+
* @param {string} data - the modification made
|
|
946
|
+
*/
|
|
947
|
+
/**
|
|
948
|
+
* Call to create an item in the database
|
|
949
|
+
*
|
|
950
|
+
* @function create
|
|
951
|
+
* @param {string} collectionName - the collection to save the item in. (required)
|
|
952
|
+
* @param {string} data - the modification to make. (required)
|
|
953
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
954
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
955
|
+
* @param {createCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
956
|
+
*
|
|
957
|
+
* @returns {Promise<string>} - the modification made
|
|
958
|
+
* @throws {string} - the error message
|
|
959
|
+
*/
|
|
960
|
+
async create(collectionName, data, dbInfo, fsWrite, callback = undefined) {
|
|
961
|
+
const origin = `${this.myid}-dbUtil-create`;
|
|
962
|
+
const useCallback = typeof callback === 'function';
|
|
963
|
+
log.trace(origin);
|
|
964
|
+
|
|
965
|
+
// verify the required data has been provided
|
|
966
|
+
if (!collectionName) {
|
|
967
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
968
|
+
log.warn(msg);
|
|
969
|
+
if (useCallback) return callback(msg, null);
|
|
970
|
+
throw msg;
|
|
971
|
+
} else if (!data) {
|
|
972
|
+
const msg = `${origin}: Missing data to add`;
|
|
973
|
+
log.warn(msg);
|
|
974
|
+
if (useCallback) return callback(msg, null);
|
|
975
|
+
throw msg;
|
|
976
|
+
} else if ((data.entity && !data.action) || (!data.entity && data.action)) {
|
|
977
|
+
const msg = `${origin}: Inconsistent entity/action set`;
|
|
978
|
+
log.warn(msg);
|
|
979
|
+
if (useCallback) return callback(msg, null);
|
|
980
|
+
throw msg;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// create the unique identifier (should we let mongo do this?)
|
|
984
|
+
const dataInfo = data;
|
|
985
|
+
if (!{}.hasOwnProperty.call(dataInfo, '_id')) {
|
|
986
|
+
dataInfo._id = uuid.v4();
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
let res;
|
|
990
|
+
try {
|
|
991
|
+
res = await this.determineStorage(dbInfo);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
const msg = `${origin}: ${err}`;
|
|
994
|
+
if (useCallback) return callback(msg, null);
|
|
995
|
+
throw msg;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// if using file storage
|
|
999
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1000
|
+
// save it to file in the adapter storage directory
|
|
1001
|
+
const saved = saveAsJson(collectionName, data);
|
|
1002
|
+
if (!saved) {
|
|
1003
|
+
const msg = `${origin}: Data has not been saved to file storage`;
|
|
1004
|
+
log.warn(msg);
|
|
1005
|
+
if (useCallback) return callback(msg, null);
|
|
1006
|
+
throw msg;
|
|
1007
|
+
}
|
|
1008
|
+
log.debug(`${origin}: Data saved in file storage`);
|
|
1009
|
+
if (useCallback) return callback(null, saved);
|
|
1010
|
+
return saved;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// if using MongoDB storage
|
|
1014
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1015
|
+
// Add the data to the database
|
|
1016
|
+
// insertOne has only 2 parameters: the data to be added & callback. Not an identifier.
|
|
1017
|
+
try {
|
|
1018
|
+
const result = await res.mongoClient.db(res.database).collection(collectionName).insertOne(dataInfo);
|
|
1019
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1020
|
+
log.spam(`${origin}: db response ${result}`);
|
|
1021
|
+
log.debug(`${origin}: Data saved in database`);
|
|
1022
|
+
if (useCallback) return callback(null, data);
|
|
1023
|
+
return data;
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1026
|
+
const msg = `${origin}: Failed to insert data in collection ${err}`;
|
|
1027
|
+
log.warn(msg);
|
|
1028
|
+
if (useCallback) return callback(msg, null);
|
|
1029
|
+
throw msg;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Call to create an index in the database
|
|
1038
|
+
*
|
|
1039
|
+
* @function createIndex
|
|
1040
|
+
* @param {string} collectionName - the collection to index. (required)
|
|
1041
|
+
* @param {string} fieldOrSpec - what to index. (required)
|
|
1042
|
+
* @param {CreateIndexesOptions | undefined} options - the creation options
|
|
1043
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1044
|
+
* @param {createCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1045
|
+
*
|
|
1046
|
+
* @returns {Promise<string>} - the created index
|
|
1047
|
+
* @throws {string} - the error message
|
|
1048
|
+
*/
|
|
1049
|
+
async createIndex(collectionName, fieldOrSpec, options, dbInfo, callback = undefined) {
|
|
1050
|
+
const origin = `${this.myid}-dbUtil-createIndex`;
|
|
1051
|
+
const useCallback = typeof callback === 'function';
|
|
1052
|
+
log.trace(origin);
|
|
1053
|
+
|
|
1054
|
+
// verify the required data has been provided
|
|
1055
|
+
if (!collectionName) {
|
|
1056
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1057
|
+
log.warn(msg);
|
|
1058
|
+
if (useCallback) return callback(msg, null);
|
|
1059
|
+
throw msg;
|
|
1060
|
+
}
|
|
1061
|
+
if (!fieldOrSpec) {
|
|
1062
|
+
const msg = `${origin}: Missing Specs`;
|
|
1063
|
+
log.warn(msg);
|
|
1064
|
+
if (useCallback) return callback(msg, null);
|
|
1065
|
+
throw msg;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
let res;
|
|
1069
|
+
try {
|
|
1070
|
+
res = await this.determineStorage(dbInfo);
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
const msg = `${origin}: ${err}`;
|
|
1073
|
+
if (useCallback) callback(msg, null);
|
|
1074
|
+
throw msg;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// if using file storage
|
|
1078
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1079
|
+
// no database - no index
|
|
1080
|
+
const msg = `${origin}: No database - no index`;
|
|
1081
|
+
log.warn(msg);
|
|
1082
|
+
if (useCallback) return callback(msg, null);
|
|
1083
|
+
throw msg;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// if using MongoDB storage
|
|
1087
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1088
|
+
// create the index on the collection
|
|
1089
|
+
let indexRes;
|
|
1090
|
+
try {
|
|
1091
|
+
indexRes = await res.mongoClient.db(res.database).collection(collectionName).createIndex(fieldOrSpec, options || {});
|
|
1092
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1093
|
+
log.debug(`${origin}: Data in collection indexed`);
|
|
1094
|
+
if (useCallback) return callback(null, indexRes);
|
|
1095
|
+
return indexRes;
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1098
|
+
const msg = `${origin}: Failed to index data in collection ${err}`;
|
|
1099
|
+
log.warn(msg);
|
|
1100
|
+
if (useCallback) return callback(msg, null);
|
|
1101
|
+
throw msg;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* @callback countCallback
|
|
1110
|
+
* @param {string} err - the error message
|
|
1111
|
+
* @param {int} data - the count
|
|
1112
|
+
*/
|
|
1113
|
+
/**
|
|
1114
|
+
* Call to count the documents in a collection
|
|
1115
|
+
*
|
|
1116
|
+
* @function countDocuments
|
|
1117
|
+
* @param {string} collectionName - the collection to count documents in. (required)
|
|
1118
|
+
* @param {object} query - the query to minimize documents you count. (required)
|
|
1119
|
+
* @param {CountDocumentsOptions | undefined} options
|
|
1120
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1121
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1122
|
+
* @param {countCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1123
|
+
*
|
|
1124
|
+
* @returns {Promise<number>} - the count of the documents
|
|
1125
|
+
* @throws {string} - the error message
|
|
1126
|
+
*/
|
|
1127
|
+
async countDocuments(collectionName, query, options, dbInfo, fsWrite, callback = undefined) {
|
|
1128
|
+
const origin = `${this.myid}-dbUtil-countDocuments`;
|
|
1129
|
+
const useCallback = typeof callback === 'function';
|
|
1130
|
+
log.trace(origin);
|
|
1131
|
+
|
|
1132
|
+
// verify the required data has been provided
|
|
1133
|
+
if (!collectionName) {
|
|
1134
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1135
|
+
log.warn(msg);
|
|
1136
|
+
if (useCallback) return callback(msg, null);
|
|
1137
|
+
throw msg;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
let res;
|
|
1141
|
+
try {
|
|
1142
|
+
res = await this.determineStorage(dbInfo);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
const msg = `${origin}: ${err}`;
|
|
1145
|
+
if (useCallback) callback(msg, null);
|
|
1146
|
+
throw msg;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// if using file storage
|
|
1150
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1151
|
+
// get a count from the JSON
|
|
1152
|
+
const data = countJSON(collectionName, query);
|
|
1153
|
+
if (!data || data === -1) {
|
|
1154
|
+
const msg = `${origin}: Could not count data from file storage`;
|
|
1155
|
+
log.warn(msg);
|
|
1156
|
+
if (useCallback) return callback(msg, null);
|
|
1157
|
+
throw msg;
|
|
1158
|
+
}
|
|
1159
|
+
log.debug(`${origin}: Count from file storage ${data}`);
|
|
1160
|
+
if (useCallback) return callback(null, data);
|
|
1161
|
+
return data;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// if using MongoDB storage
|
|
1165
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1166
|
+
// get the count from mongo
|
|
1167
|
+
let count;
|
|
1168
|
+
try {
|
|
1169
|
+
count = await res.mongoClient.db(res.database).collection(collectionName).countDocuments(query, options || {});
|
|
1170
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1171
|
+
log.debug(`${origin}: Count from database ${res}`);
|
|
1172
|
+
if (useCallback) return callback(null, count);
|
|
1173
|
+
return count;
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1176
|
+
const msg = `${origin}: Failed to count collection ${err}`;
|
|
1177
|
+
log.warn(msg);
|
|
1178
|
+
if (useCallback) return callback(msg, null);
|
|
1179
|
+
throw msg;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Delete items from a collection
|
|
1188
|
+
*
|
|
1189
|
+
* @function delete
|
|
1190
|
+
* @param {string} collectionName - the collection to remove document from. (required)
|
|
1191
|
+
* @param {object} filter - the filter for the document(s) to remove. (required)
|
|
1192
|
+
* @param {DeleteOptions} options
|
|
1193
|
+
* @param {boolean} multiple
|
|
1194
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1195
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1196
|
+
* @param {createCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1197
|
+
*
|
|
1198
|
+
* @returns {Promise<DeleteResult>}
|
|
1199
|
+
* @throws {string} - the error message
|
|
1200
|
+
*/
|
|
1201
|
+
async delete(collectionName, filter, options, multiple, dbInfo, fsWrite, callback = undefined) {
|
|
1202
|
+
const origin = `${this.myid}-dbUtil-delete`;
|
|
1203
|
+
const useCallback = typeof callback === 'function';
|
|
1204
|
+
log.trace(origin);
|
|
1205
|
+
|
|
1206
|
+
// verify the required data has been provided
|
|
1207
|
+
if (!collectionName) {
|
|
1208
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1209
|
+
log.warn(msg);
|
|
1210
|
+
if (useCallback) return callback(msg, null);
|
|
1211
|
+
throw msg;
|
|
1212
|
+
}
|
|
1213
|
+
if (!filter) {
|
|
1214
|
+
const msg = `${origin}: Missing Filter`;
|
|
1215
|
+
log.warn(msg);
|
|
1216
|
+
if (useCallback) return callback(msg, null);
|
|
1217
|
+
throw msg;
|
|
1218
|
+
}
|
|
1219
|
+
if (multiple === undefined || multiple === null) {
|
|
1220
|
+
const msg = `${origin}: Missing Multiple flag`;
|
|
1221
|
+
log.warn(msg);
|
|
1222
|
+
if (useCallback) return callback(msg, null);
|
|
1223
|
+
throw msg;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
let res;
|
|
1227
|
+
try {
|
|
1228
|
+
res = await this.determineStorage(dbInfo);
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
const msg = `${origin}: ${err}`;
|
|
1231
|
+
if (useCallback) callback(msg, null);
|
|
1232
|
+
throw msg;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// if using file storage
|
|
1236
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1237
|
+
// verify the collection exists
|
|
1238
|
+
if (!fs.existsSync(`${adapterDir}/storage/${collectionName}.json`)) {
|
|
1239
|
+
const msg = `${origin}: Collection ${collectionName} does not exist`;
|
|
1240
|
+
log.warn(msg);
|
|
1241
|
+
if (useCallback) return callback(msg, null);
|
|
1242
|
+
throw msg;
|
|
1243
|
+
}
|
|
1244
|
+
// remove the item from the collection
|
|
1245
|
+
const deld = removeFromJSON(collectionName, filter, multiple);
|
|
1246
|
+
if (!deld) {
|
|
1247
|
+
const msg = `${origin}: Data has not been deleted from file storage`;
|
|
1248
|
+
log.warn(msg);
|
|
1249
|
+
if (useCallback) return callback(msg, null);
|
|
1250
|
+
throw msg;
|
|
1251
|
+
}
|
|
1252
|
+
log.debug(`${origin}: Data has been deleted from file storage`);
|
|
1253
|
+
if (useCallback) return callback(null, deld);
|
|
1254
|
+
return deld;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// if using MongoDB storage
|
|
1258
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1259
|
+
// Calls either 'collection.deleteOne' or 'collection.deleteMany'. Both methods take the same parameters
|
|
1260
|
+
const methodName = multiple ? 'deleteMany' : 'deleteOne';
|
|
1261
|
+
let delRes;
|
|
1262
|
+
try {
|
|
1263
|
+
delRes = await res.mongoClient.db(res.database).collection(collectionName)[methodName](filter, options);
|
|
1264
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1265
|
+
log.debug(`${origin}: Data has been deleted from database`);
|
|
1266
|
+
if (useCallback) return callback(null, delRes);
|
|
1267
|
+
return delRes;
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1270
|
+
const msg = `${origin}: Failed to delete data from database ${err}`;
|
|
1271
|
+
log.warn(msg);
|
|
1272
|
+
if (useCallback) return callback(msg, null);
|
|
1273
|
+
throw msg;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* @callback replaceCallback
|
|
1282
|
+
* @param {string} err - the error message
|
|
1283
|
+
* @param {Document | UpdateResult<Document>} data - the update result
|
|
1284
|
+
*/
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Replace an item in a collection
|
|
1288
|
+
*
|
|
1289
|
+
* @function replaceOne
|
|
1290
|
+
* @param {string} collectionName - the collection to replace document in. (required)
|
|
1291
|
+
* @param {object} filter - the filter for the document to replace. (required)
|
|
1292
|
+
* @param {object} doc - the document to insert. (required)
|
|
1293
|
+
* @param {ReplaceOptions} options
|
|
1294
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1295
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1296
|
+
* @param {replaceCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1297
|
+
*
|
|
1298
|
+
* @returns {Promise<Document | UpdateResult<Document>>}
|
|
1299
|
+
* @throws {string} - the error message
|
|
1300
|
+
*/
|
|
1301
|
+
async replaceOne(collectionName, filter, doc, options, dbInfo, fsWrite, callback = undefined) {
|
|
1302
|
+
const origin = `${this.myid}-dbUtil-replaceOne`;
|
|
1303
|
+
const useCallback = typeof callback === 'function';
|
|
1304
|
+
log.trace(origin);
|
|
1305
|
+
|
|
1306
|
+
// verify the required data has been provided
|
|
1307
|
+
if (!collectionName) {
|
|
1308
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1309
|
+
log.warn(msg);
|
|
1310
|
+
if (useCallback) return callback(msg, null);
|
|
1311
|
+
throw msg;
|
|
1312
|
+
}
|
|
1313
|
+
if (!filter) {
|
|
1314
|
+
const msg = `${origin}: Missing Filter`;
|
|
1315
|
+
log.warn(msg);
|
|
1316
|
+
if (useCallback) return callback(msg, null);
|
|
1317
|
+
throw msg;
|
|
1318
|
+
}
|
|
1319
|
+
if (!doc) {
|
|
1320
|
+
const msg = `${origin}: Missing Document`;
|
|
1321
|
+
log.warn(msg);
|
|
1322
|
+
if (useCallback) return callback(msg, null);
|
|
1323
|
+
throw msg;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
let res;
|
|
1327
|
+
try {
|
|
1328
|
+
res = await this.determineStorage(dbInfo);
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
const msg = `${origin}: ${err}`;
|
|
1331
|
+
if (useCallback) callback(msg, null);
|
|
1332
|
+
throw msg;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// if using file storage
|
|
1336
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1337
|
+
// remove the data from the collection
|
|
1338
|
+
const rem = removeFromJSON(collectionName, filter, false);
|
|
1339
|
+
if (rem) {
|
|
1340
|
+
// add the data into the collection
|
|
1341
|
+
const sav = saveAsJson(collectionName, doc);
|
|
1342
|
+
if (sav) {
|
|
1343
|
+
log.debug(`${origin}: Data replaced in file storage`);
|
|
1344
|
+
if (useCallback) return callback(null, sav);
|
|
1345
|
+
return sav;
|
|
1346
|
+
}
|
|
1347
|
+
const msg = `${origin}: Could not save doc into file storage`;
|
|
1348
|
+
log.warn(msg);
|
|
1349
|
+
if (useCallback) return callback(msg, null);
|
|
1350
|
+
throw msg;
|
|
1351
|
+
}
|
|
1352
|
+
const msg = `${origin}: Could not delete from file storage`;
|
|
1353
|
+
log.warn(msg);
|
|
1354
|
+
if (useCallback) return callback(msg, null);
|
|
1355
|
+
throw msg;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// if using MongoDB storage
|
|
1359
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1360
|
+
// replace an items in mongo
|
|
1361
|
+
let replaceRes;
|
|
1362
|
+
try {
|
|
1363
|
+
replaceRes = await res.mongoClient.db(res.database).collection(collectionName).replaceOne(filter, doc, options);
|
|
1364
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1365
|
+
log.debug(`${origin}: Data replaced in file storage`);
|
|
1366
|
+
if (useCallback) return callback(null, replaceRes);
|
|
1367
|
+
return replaceRes;
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1370
|
+
const msg = `${origin}: Failed to replace data in database ${err}`;
|
|
1371
|
+
log.warn(msg);
|
|
1372
|
+
if (useCallback) return callback(msg, null);
|
|
1373
|
+
throw msg;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* @callback findCallback
|
|
1382
|
+
* @param {string} err - the error message
|
|
1383
|
+
* @param {Document[]} data - the found data
|
|
1384
|
+
*/
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* @typedef FindOptsParam
|
|
1388
|
+
* @property {Filter<Document>} filter
|
|
1389
|
+
* @property {Sort} sort
|
|
1390
|
+
* @property {number} start - start position
|
|
1391
|
+
* @property {number} limit - max number of results
|
|
1392
|
+
*/
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Call to find items in the database
|
|
1396
|
+
*
|
|
1397
|
+
* @function find
|
|
1398
|
+
* @param {string} collectionName - the collection name to search in. (required)
|
|
1399
|
+
* @param {FindOptsParam} options - the options to use to find data.
|
|
1400
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database)
|
|
1401
|
+
* @param {boolean} fsWrite - turn on write to the file system
|
|
1402
|
+
* @param {findCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1403
|
+
*
|
|
1404
|
+
* @returns {Promise<Document[]>}
|
|
1405
|
+
* @throws {string} - the error message
|
|
1406
|
+
*/
|
|
1407
|
+
async find(collectionName, options, dbInfo, fsWrite, callback = undefined) {
|
|
1408
|
+
const origin = `${this.myid}-dbUtil-find`;
|
|
1409
|
+
const useCallback = typeof callback === 'function';
|
|
1410
|
+
log.trace(origin);
|
|
1411
|
+
|
|
1412
|
+
// verify the required data has been provided
|
|
1413
|
+
if (!collectionName) {
|
|
1414
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1415
|
+
log.warn(msg);
|
|
1416
|
+
if (useCallback) return callback(msg, null);
|
|
1417
|
+
throw msg;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// get the collection so we can run the remove on the collection
|
|
1421
|
+
let filter = {};
|
|
1422
|
+
let start = 0;
|
|
1423
|
+
let sort = {};
|
|
1424
|
+
let limit = 10;
|
|
1425
|
+
if (options) {
|
|
1426
|
+
filter = options.filter || {};
|
|
1427
|
+
start = options.start || 0;
|
|
1428
|
+
sort = options.sort || {};
|
|
1429
|
+
if (Object.hasOwnProperty.call(options, 'limit')) {
|
|
1430
|
+
({ limit } = options);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// If limit is not specified, default to 10.
|
|
1435
|
+
// Note: limit may be 0, which is equivalent to setting no limit.
|
|
1436
|
+
|
|
1437
|
+
// Replace filter with regex to allow for substring lookup
|
|
1438
|
+
// TODO: Need to create a new filter object instead of mutating the exsisting one
|
|
1439
|
+
const filterKeys = Object.keys(filter).filter((key) => (key[0] !== '$' && typeof filter[key] === 'string'));
|
|
1440
|
+
filterKeys.map((key) => {
|
|
1441
|
+
try {
|
|
1442
|
+
const escapedFilter = filter[key].replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
|
|
1443
|
+
const regexedFilter = new RegExp(`.*${escapedFilter}.*`, 'i');
|
|
1444
|
+
filter[key] = {
|
|
1445
|
+
$regex: regexedFilter
|
|
1446
|
+
};
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
delete filter[key];
|
|
1449
|
+
}
|
|
1450
|
+
return key;
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
let res;
|
|
1454
|
+
try {
|
|
1455
|
+
res = await this.determineStorage(dbInfo);
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
const msg = `${origin}: ${err}`;
|
|
1458
|
+
if (useCallback) callback(msg, null);
|
|
1459
|
+
throw msg;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// if using file storage
|
|
1463
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1464
|
+
// Find it from file in the adapter
|
|
1465
|
+
let toReturn = getFromJson(collectionName, options);
|
|
1466
|
+
if (collectionName === 'adapter_configs') {
|
|
1467
|
+
log.debug(`${origin}: Data retrieved from file storage`);
|
|
1468
|
+
if (useCallback) return callback(null, [toReturn]);
|
|
1469
|
+
return [toReturn];
|
|
1470
|
+
}
|
|
1471
|
+
if (toReturn && toReturn.length > limit) {
|
|
1472
|
+
let curEnd = start + limit;
|
|
1473
|
+
if (curEnd < toReturn.length) {
|
|
1474
|
+
curEnd = toReturn.length;
|
|
1475
|
+
}
|
|
1476
|
+
toReturn = toReturn.slice(start, curEnd);
|
|
1477
|
+
}
|
|
1478
|
+
log.debug(`${origin}: Data retrieved from file storage`);
|
|
1479
|
+
if (useCallback) return callback(null, toReturn);
|
|
1480
|
+
return toReturn;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// if using MongoDB storage
|
|
1484
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1485
|
+
// Find the data in the database
|
|
1486
|
+
let findRes;
|
|
1487
|
+
try {
|
|
1488
|
+
findRes = await res.mongoClient.db(res.database).collection(collectionName).find(filter)
|
|
1489
|
+
.sort(sort)
|
|
1490
|
+
.skip(start)
|
|
1491
|
+
.limit(limit)
|
|
1492
|
+
.toArray();
|
|
1493
|
+
log.debug(`${origin}: Data retrieved from database`);
|
|
1494
|
+
if (useCallback) return callback(null, findRes);
|
|
1495
|
+
return findRes;
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
const msg = `${origin}: Data could not be retrieved from database`;
|
|
1498
|
+
log.warn(msg);
|
|
1499
|
+
if (useCallback) return callback(msg, null);
|
|
1500
|
+
throw msg;
|
|
1501
|
+
} finally {
|
|
1502
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* @callback findAndModifyCallback
|
|
1511
|
+
* @param {string} err - the error message
|
|
1512
|
+
* @param {WithId<Document>} data - the response
|
|
1513
|
+
*/
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* Call to find an item in a collection and modify it.
|
|
1517
|
+
*
|
|
1518
|
+
* @function findAndModify
|
|
1519
|
+
* @param {string} collectionName - the collection to find things from. (required)
|
|
1520
|
+
* @param {Filter<Document>} filter - the filter used to find objects. (optional)
|
|
1521
|
+
* @param {Array} sort - how to sort the items (first one in order will be modified). (optional)
|
|
1522
|
+
* @param {UpdateFilter<Document>} data - the modification to make. (required)
|
|
1523
|
+
* @param {boolean} upsert - option for the whether to insert new objects. (optional)
|
|
1524
|
+
* @param {DBInfo} dbInfo - the url for the database to connect to (dburl, dboptions, database) (optional)
|
|
1525
|
+
* @param {boolean} fsWrite - turn on write to the file system (optional)
|
|
1526
|
+
* @param {findAndModifyCallback | undefined} callback - the callback to invoke. If undefined, the function acts like a promise. If defined, the function returns void.
|
|
1527
|
+
*
|
|
1528
|
+
* @return {Promise<WithId<Document>>}
|
|
1529
|
+
* @throws {string} - the error message
|
|
1530
|
+
*/
|
|
1531
|
+
async findAndModify(collectionName, filter, sort, data, upsert, dbInfo, fsWrite, callback = undefined) {
|
|
1532
|
+
const origin = `${this.myid}-dbUtil-findAndModify`;
|
|
1533
|
+
const useCallback = typeof callback === 'function';
|
|
1534
|
+
log.trace(origin);
|
|
1535
|
+
|
|
1536
|
+
// verify the required data has been provided
|
|
1537
|
+
if (collectionName === undefined || collectionName === null || collectionName === '') {
|
|
1538
|
+
const msg = `${origin}: Missing Collection Name`;
|
|
1539
|
+
log.warn(msg);
|
|
1540
|
+
if (useCallback) return callback(msg, null);
|
|
1541
|
+
throw msg;
|
|
1542
|
+
}
|
|
1543
|
+
if (data === undefined || data === null || typeof data !== 'object' || Object.keys(data).length === 0) {
|
|
1544
|
+
const msg = `${origin}: Missing data for modification`;
|
|
1545
|
+
log.warn(msg);
|
|
1546
|
+
if (useCallback) return callback(msg, null);
|
|
1547
|
+
throw msg;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const options = {
|
|
1551
|
+
sort,
|
|
1552
|
+
upsert,
|
|
1553
|
+
returnOriginal: false
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
let res;
|
|
1557
|
+
try {
|
|
1558
|
+
res = await this.determineStorage(dbInfo);
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
const msg = `${origin}: ${err}`;
|
|
1561
|
+
if (useCallback) callback(msg, null);
|
|
1562
|
+
throw msg;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// if using file storage
|
|
1566
|
+
if (res.storageType === Storage.FILESYSTEM) {
|
|
1567
|
+
// save it to file in the adapter storage directory
|
|
1568
|
+
const saved = saveAsJson(collectionName, data);
|
|
1569
|
+
if (!saved) {
|
|
1570
|
+
const msg = `${origin}: Data has not been saved`;
|
|
1571
|
+
log.error(msg);
|
|
1572
|
+
if (useCallback) return callback(msg, null);
|
|
1573
|
+
throw msg;
|
|
1574
|
+
}
|
|
1575
|
+
log.debug(`${origin}: Data modified in file storage`);
|
|
1576
|
+
if (useCallback) return callback(null, saved);
|
|
1577
|
+
return saved;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// if using MongoDB storage
|
|
1581
|
+
if (res.storageType === Storage.DBINFO || res.storageType === Storage.ADAPTERDB) {
|
|
1582
|
+
// find and modify the data in the database
|
|
1583
|
+
let findRes;
|
|
1584
|
+
try {
|
|
1585
|
+
findRes = await res.mongoClient.db(res.database).collection(collectionName).findOneAndUpdate((filter || {}), data, options);
|
|
1586
|
+
if (findRes) log.debug(`${origin}: Data modified in database`);
|
|
1587
|
+
if (useCallback) return callback(null, findRes);
|
|
1588
|
+
return findRes;
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
const msg = `${origin}: Failed to modified data in database ${err}`;
|
|
1591
|
+
log.warn(msg);
|
|
1592
|
+
if (useCallback) return callback(msg, null);
|
|
1593
|
+
throw msg;
|
|
1594
|
+
} finally {
|
|
1595
|
+
if (res.storageType === Storage.DBINFO && res.mongoClient) res.mongoClient.close();
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
log.warn(`${origin}: Unexpected end of function reached`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
module.exports = DBUtil;
|