@cocreate/crud-server 1.12.0 → 1.13.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.
@@ -0,0 +1,776 @@
1
+ // const {mongoClient} = require("./db")
2
+ const {MongoClient, ObjectId} = require('mongodb');
3
+ const {replaceArray} = require("../utils.crud.js")
4
+ const {searchData, sortData} = require("@cocreate/filter")
5
+
6
+ function mongoClient(dbUrl) {
7
+ try {
8
+ dbUrl = "mongodb+srv://cocreate-app:rolling123@cocreatedb.dnjr1.mongodb.net" || dbUrl || process.env.MONGO_URL || config.db_url;
9
+ if (!dbUrl || !dbUrl.includes('mongodb'))
10
+ console.log('CoCreate.config.js missing dbUrl')
11
+ dbClient = MongoClient.connect(dbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
12
+ return dbClient;
13
+ } catch (error) {
14
+ console.error(error)
15
+ return {
16
+ status: false
17
+ }
18
+ }
19
+ }
20
+
21
+ let dbClient;
22
+ mongoClient().then(Client => {
23
+ dbClient = Client
24
+ });
25
+
26
+ async function databaseStats(data) {
27
+ let stats = await dbClient.db(data.organization_id).stats()
28
+ return stats
29
+ }
30
+
31
+ function createDatabase(data) {
32
+ return database('createDatabase', data)
33
+ }
34
+
35
+ function readDatabase(data) {
36
+ return database('readDatabase', data)
37
+ }
38
+
39
+ function updateDatabase(data) {
40
+ return database('updateDatabase', data)
41
+ }
42
+
43
+ function deleteDatabase(data) {
44
+ return database('deleteDatabase', data)
45
+ }
46
+
47
+ function database(action, data){
48
+ return new Promise((resolve, reject) => {
49
+ const self = this;
50
+
51
+ try {
52
+ if (action == 'readDatabase') {
53
+ let db = dbClient.db().admin();
54
+
55
+ // List all the available databases
56
+ db.listDatabases(function(err, dbs) {
57
+ resolve({...data, database: dbs.databases})
58
+ })
59
+
60
+ }
61
+ if (action == 'deleteDatabase') {
62
+ const db = dbClient.db(data.database);
63
+ db.dropDatabase().then(response => {
64
+ resolve(response)
65
+ })
66
+ }
67
+ } catch(error) {
68
+ errorLog.push(error)
69
+ data['error'] = errorLog
70
+ console.log(action, 'error', error);
71
+ resolve(data);
72
+ }
73
+
74
+ }, (err) => {
75
+ errorHandler(data, err)
76
+ });
77
+ }
78
+
79
+ function createCollection(data){
80
+ return collection('createCollection', data)
81
+ }
82
+
83
+ function readCollection(data) {
84
+ return collection('readCollection', data)
85
+ }
86
+
87
+ function updateCollection(data) {
88
+ return collection('updateCollection', data)
89
+ }
90
+
91
+ function deleteCollection(data) {
92
+ return collection('deleteCollection', data)
93
+ }
94
+
95
+
96
+ function collection(action, data){
97
+ return new Promise((resolve, reject) => {
98
+
99
+ const self = this;
100
+ let type = 'collection'
101
+ let collectionArray = [];
102
+
103
+ let errorLog = [];
104
+ const errorHandler = (error) => {
105
+ if (error) {
106
+ error.db = 'mongodb'
107
+ errorLog.push(error);
108
+ }
109
+ }
110
+
111
+ try {
112
+ if (data.request)
113
+ data.collection = data.request
114
+
115
+ let databases = data.database;
116
+ if (!Array.isArray(databases))
117
+ databases = [databases]
118
+
119
+ let databasesLength = databases.length
120
+ for (let database of databases) {
121
+ const db = dbClient.db(database);
122
+ if (action == 'readCollection') {
123
+
124
+ let {query, sort} = getFilters(data);
125
+
126
+ db.listCollections(query).toArray(function(error, result) {
127
+ if (error) {
128
+ error.database = database
129
+ errorHandler(error)
130
+ }
131
+
132
+ if (result)
133
+ for (let res of result)
134
+ collectionArray.push({name: res.name, database, db: 'mongodb'})
135
+
136
+ databasesLength -= 1
137
+ if (!databasesLength) {
138
+ data = createData(data, collectionArray, type, errorLog)
139
+ resolve(data)
140
+ }
141
+ })
142
+ } else {
143
+ let collections
144
+ let value
145
+ if (action == 'updateCollection')
146
+ collections = Object.entries(data.collection)
147
+ else
148
+ collections = data.collection;
149
+
150
+ if (!Array.isArray(collections))
151
+ collections = [collections]
152
+
153
+ let collectionsLength = collections.length
154
+ for (let collection of collections) {
155
+
156
+ if (action == 'createCollection') {
157
+ db.createCollection(collection, function(error, result) {
158
+ if (error) {
159
+ error.database = database
160
+ error.collection = collection
161
+ errorHandler(error)
162
+ }
163
+ if (result)
164
+ collectionArray.push({name: collection, database, db: 'mongodb'})
165
+
166
+ collectionsLength -= 1
167
+ if (!collectionsLength)
168
+ databasesLength -= 1
169
+
170
+ if (!databasesLength && !collectionsLength) {
171
+ data = createData(data, collectionArray, type, errorLog)
172
+ resolve(data)
173
+ }
174
+ })
175
+ } else {
176
+ if (action == 'updateCollection') {
177
+ [collection, value] = collection
178
+ }
179
+
180
+ const collectionObj = db.collection(collection);
181
+
182
+ if (action == 'updateCollection') {
183
+ collectionObj.rename(value, function(error, result) {
184
+ if (error) {
185
+ error.database = database
186
+ error.collection = collection
187
+ errorHandler(error)
188
+ }
189
+
190
+ if (result)
191
+ collectionArray.push({name: value, oldName: collection, database, db: 'mongodb'})
192
+
193
+ collectionsLength -= 1
194
+ if (!collectionsLength)
195
+ databasesLength -= 1
196
+
197
+ if (!databasesLength && !collectionsLength) {
198
+ data = createData(data, collectionArray, type, errorLog)
199
+ resolve(data)
200
+ }
201
+
202
+ })
203
+ }
204
+
205
+ if (action == 'deleteCollection') {
206
+ collectionObj.drop( function(error, result) {
207
+ if (error) {
208
+ error.database = database
209
+ error.collection = collection
210
+ errorHandler(error)
211
+ }
212
+ if (result)
213
+ collectionArray.push({name: collection, database, db: 'mongodb'})
214
+
215
+ collectionsLength -= 1
216
+ if (!collectionsLength)
217
+ databasesLength -= 1
218
+
219
+ if (!databasesLength && !collectionsLength) {
220
+ data = createData(data, collectionArray, type, errorLog)
221
+ resolve(data)
222
+ }
223
+
224
+ })
225
+
226
+ }
227
+ }
228
+
229
+ }
230
+ }
231
+ }
232
+ } catch(error) {
233
+ errorLog.push(error)
234
+ data['error'] = errorLog
235
+ console.log(action, 'error', error);
236
+ resolve(data);
237
+ }
238
+ }, (err) => {
239
+ errorHandler(data, err)
240
+ });
241
+ }
242
+
243
+ function createDocument(data){
244
+ return document('createDocument', data)
245
+ }
246
+
247
+ function readDocument(data) {
248
+ return document('readDocument', data)
249
+ }
250
+
251
+ function updateDocument(data) {
252
+ return document('updateDocument', data)
253
+ }
254
+
255
+ function deleteDocument(data) {
256
+ return document('deleteDocument', data)
257
+ }
258
+
259
+ function document(action, data){
260
+ return new Promise(async (resolve, reject) => {
261
+
262
+ const self = this;
263
+
264
+ let errorLog = [];
265
+ const errorHandler = (error) => {
266
+ if (error) {
267
+ error.db = 'mongodb'
268
+ errorLog.push(error);
269
+ }
270
+ }
271
+
272
+ try {
273
+ let type = 'document'
274
+ let documents = [];
275
+
276
+ if (data.request)
277
+ data[type] = data.request
278
+
279
+
280
+ if (!data['timeStamp'])
281
+ data['timeStamp'] = new Date().toISOString()
282
+
283
+
284
+ let isFilter
285
+ if (data.filter && data.filter.query)
286
+ isFilter = true
287
+
288
+ let databases = data.database;
289
+ if (!Array.isArray(databases))
290
+ databases = [databases]
291
+
292
+ let databasesLength = databases.length
293
+ for (let database of databases) {
294
+ let collections = data.collection;
295
+ if (!Array.isArray(collections))
296
+ collections = [collections]
297
+
298
+ let collectionsLength = collections.length
299
+ for (let collection of collections) {
300
+ const db = dbClient.db(database);
301
+ const collectionObj = db.collection(collection);
302
+
303
+ let {query, sort} = getFilters(data);
304
+ if (data['organization_id']) {
305
+ query['organization_id'] = { $eq: data['organization_id'] }
306
+ }
307
+
308
+ let _ids = []
309
+ let update_ids = []
310
+ let updateData = {}
311
+
312
+ if (data[type]) {
313
+ if (!Array.isArray(data[type]))
314
+ data[type] = [data[type]]
315
+ for (let i = 0; i < data[type].length; i++) {
316
+ data[type][i] = replaceArray(data[type][i])
317
+ data[type][i]['organization_id'] = data['organization_id'];
318
+
319
+
320
+ if (action == 'createDocument') {
321
+ if (!data[type][i]._id)
322
+ data[type][i]._id = ObjectId()
323
+ else
324
+ data[type][i]._id = ObjectId(data[type][i]._id)
325
+ data[type][i]['created'] = {on: data.timeStamp, by: data.user || data.clientId}
326
+ }
327
+ if (action == 'readDocument') {
328
+ if (data[type][i]._id)
329
+ _ids.push(data[type][i]._id)
330
+ }
331
+ if (action =='updateDocument') {
332
+ if (data[type][i]._id)
333
+ update_ids.push({_id: data[type][i]._id, updateDoc: data[type][i], updateType: '_id'})
334
+
335
+ if (!data[type][i]._id)
336
+ updateData = createUpdate({document: [data[type][i]]}, type)
337
+
338
+ data[type][i]['modified'] = {on: data.timeStamp, by: data.user || data.clientId}
339
+
340
+ }
341
+ if (action =='deleteDocument') {
342
+ if (data[type][i]._id) {
343
+ _ids.push(data[type][i]._id)
344
+ documents.push({_id: data[type][i]._id, db: 'mongodb', database, collection})
345
+ }
346
+ }
347
+ }
348
+ if (_ids.length == 1)
349
+ query['_id'] = ObjectId(_ids[0])
350
+ else if (_ids.length > 0)
351
+ query['_id'] = {$in: _ids}
352
+ }
353
+
354
+
355
+ if (action == 'createDocument') {
356
+ collectionObj.insertMany(data[type], function(error, result) {
357
+ if (error) {
358
+ error.database = database
359
+ error.collection = collection
360
+ errorHandler(error)
361
+ }
362
+
363
+ for (let i = 0; i < data[type].length; i++)
364
+ documents.push({db: 'mongodb', database, collection, ...data[type][i]})
365
+
366
+ collectionsLength -= 1
367
+ if (!collectionsLength)
368
+ databasesLength -= 1
369
+
370
+ if (!databasesLength && !collectionsLength) {
371
+ data = createData(data, documents, type, errorLog)
372
+ resolve(data)
373
+ }
374
+ });
375
+ }
376
+
377
+ if (action == 'readDocument') {
378
+
379
+ collectionObj.find(query).sort(sort).toArray(function(error, result) {
380
+ if (error) {
381
+ error.database = database
382
+ error.collection = collection
383
+ errorHandler(error)
384
+ }
385
+
386
+ if (result) {
387
+ // ToDo: forEach at cursor, searchData can can be an object or an array to 1 or many docs
388
+ let searchResult = searchData(result, data.filter)
389
+ for (let doc of searchResult) {
390
+ doc.db = 'mongodb'
391
+ doc.database = database
392
+ doc.collection = collection
393
+ documents.push(doc)
394
+ }
395
+
396
+ if (data.returnDocument == false) {
397
+
398
+ for (let item of data['data']) {
399
+ let resp = {};
400
+ resp['_id'] = tmp['_id']
401
+ data[type].forEach((f) => resp[f] = item[f])
402
+ documents.push(resp);
403
+ }
404
+
405
+ data['data'] = documents
406
+ }
407
+ }
408
+
409
+ collectionsLength -= 1
410
+ if (!collectionsLength)
411
+ databasesLength -= 1
412
+
413
+ if (!databasesLength && !collectionsLength) {
414
+ data = createData(data, documents, type, errorLog)
415
+ resolve(data)
416
+ }
417
+ });
418
+ }
419
+
420
+ if (action == 'updateDocument' || action == 'deleteDocument') {
421
+ const queryDocs = () => {
422
+ return new Promise((resolve, reject) => {
423
+
424
+ collectionObj.find(query).sort(sort).toArray(function(error, result) {
425
+ if (error) {
426
+ error.database = database
427
+ error.collection = collection
428
+ errorHandler(error)
429
+ }
430
+
431
+ let searchResult = searchData(result, data.filter)
432
+ resolve(searchResult)
433
+ })
434
+ }, (err) => {
435
+ console.log(err);
436
+ });
437
+ }
438
+
439
+ let Result, $update, update, projection;
440
+
441
+ if (isFilter && data.returnDocument != false)
442
+ if (action == 'deleteDocument' || action == 'updateDocument' && updateData.update)
443
+ Result = await queryDocs()
444
+
445
+ if (Result) {
446
+ for (let doc of Result) {
447
+ if (action == 'deleteDocument')
448
+ documents.push({_id: doc._id, db: 'mongodb', database, collection})
449
+ else
450
+ doc['modified'] = {on: data.timeStamp, by: data.user || data.clientId}
451
+
452
+ _ids.push(doc._id)
453
+ }
454
+ update_ids.push({updateType: 'filter'})
455
+ }
456
+
457
+ if (action == 'updateDocument') {
458
+ let docsLength = update_ids.length
459
+ for (let {updateDoc, updateType} of update_ids) {
460
+
461
+ if (updateType == '_id') {
462
+ let update_id = updateDoc._id
463
+ query['_id'] = ObjectId(update_id)
464
+ $update = createUpdate({document: [updateDoc]}, type)
465
+ update = $update.update
466
+ projection = $update.projection
467
+ documents.push({_id: update_id, db: 'mongodb', database, collection, ...update['$set']})
468
+ }
469
+
470
+ if (updateType == 'filter') {
471
+ query['_id'] = {$in: _ids}
472
+ $update = updateData
473
+ update = $update.update
474
+ projection = $update.projection
475
+ for (let _id of _ids)
476
+ documents.push({_id, db: 'mongodb', database, collection, ...update['$set']})
477
+
478
+ }
479
+
480
+ update['$set']['organization_id'] = data.organization_id
481
+
482
+ collectionObj.updateMany(query, update, {
483
+ upsert: false,
484
+ projection
485
+ }).then((result) => {
486
+
487
+
488
+ }).catch((error) => {
489
+ errorLog.push(error)
490
+ console.log(action, 'error', error);
491
+ }).finally((error) => {
492
+ docsLength -= 1
493
+ if (!docsLength)
494
+ collectionsLength -= 1
495
+
496
+ if (!collectionsLength)
497
+ databasesLength -= 1
498
+
499
+ if (!databasesLength && !collectionsLength) {
500
+ data = createData(data, documents, type, errorLog)
501
+ resolve(data)
502
+ }
503
+ })
504
+ }
505
+
506
+ if (!update_ids.length) {
507
+ docsLength -= 1
508
+ if (!docsLength)
509
+ collectionsLength -= 1
510
+
511
+ if (!collectionsLength)
512
+ databasesLength -= 1
513
+
514
+ if (!databasesLength && !collectionsLength) {
515
+ data = createData(data, documents, type, errorLog)
516
+ resolve(data)
517
+ }
518
+ }
519
+
520
+ }
521
+
522
+ if (action == 'deleteDocument') {
523
+ if (_ids.length == 1)
524
+ query['_id'] = ObjectId(_ids[0])
525
+ else if (_ids.length > 0)
526
+ query['_id'] = {$in: _ids}
527
+ collectionObj.deleteMany(query, function(error, result) {
528
+ collectionsLength -= 1
529
+ if (!collectionsLength)
530
+ databasesLength -= 1
531
+
532
+ if (!databasesLength && !collectionsLength) {
533
+ data = createData(data, documents, type, errorLog)
534
+ resolve(data)
535
+ }
536
+
537
+ })
538
+ }
539
+
540
+ }
541
+
542
+ }
543
+ }
544
+
545
+ } catch(error) {
546
+ errorLog.push(error)
547
+ data['error'] = errorLog
548
+ console.log(action, 'error', error);
549
+ resolve(data);
550
+ }
551
+ }, (err) => {
552
+ errorHandler(data, err)
553
+ });
554
+
555
+ }
556
+
557
+ function createUpdate(data, type) {
558
+ let update = {}, projection = {};
559
+ if (data[type][0]) {
560
+ update['$set'] = valueTypes(data[type][0])
561
+ // update['$set']['organization_id'] = data['organization_id'];
562
+ if (update['$set']['_id'])
563
+ delete update['$set']['_id']
564
+ Object.keys(update['$set']).forEach(x => {
565
+ projection[x] = 1
566
+ })
567
+ }
568
+
569
+ if( data['deleteName'] ) {
570
+ update['$unset'] = replaceArray(data['deleteName']);
571
+ }
572
+
573
+ if( data['updateName'] ) {
574
+ update['$rename'] = replaceArray(data['updateName'])
575
+ for (const [key, value] of Object.entries(update['$rename'])) {
576
+ if (/\.([0-9]*)/g.test(key) || /\[([0-9]*)\]/g.test(value)) {
577
+ console.log('key is array', /\[([0-9]*)\]/g.test(value), /\.([0-9]*)/g.test(key))
578
+ } else {
579
+ let newValue = replaceArray({[value]: value})
580
+ let oldkey = key;
581
+ for (const [key] of Object.entries(newValue)) {
582
+ update['$rename'][oldkey] = key
583
+ }
584
+ }
585
+ }
586
+ }
587
+
588
+ return {update, projection}
589
+
590
+ }
591
+
592
+ function createData(data, array, type, errorLog) {
593
+ if (errorLog.length > 0)
594
+ data['error'] = errorLog
595
+
596
+ if (!data.request)
597
+ data.request = data[type] || {}
598
+
599
+ if (data.filter && data.filter.sort)
600
+ data[type] = sortData(array, data.filter.sort)
601
+ else
602
+ data[type] = array
603
+
604
+ if (data.returnLog){
605
+ if (!data.log)
606
+ data.log = []
607
+ data.log.push(...data[type])
608
+ }
609
+
610
+ return data
611
+ }
612
+
613
+ function getFilters(data) {
614
+ let query = {}, sort = {}
615
+ let filter = {
616
+ query: [],
617
+ sort: [],
618
+ search: {
619
+ value: [],
620
+ type: "or"
621
+ },
622
+ startIndex: 0,
623
+ ...data.filter
624
+ };
625
+
626
+ query = createQuery(filter.query);
627
+
628
+
629
+ if (filter.sort)
630
+ filter.sort.forEach((order) => {
631
+ sort[order.name] = order.type
632
+ });
633
+
634
+ return {query, sort}
635
+ }
636
+
637
+ // ToDo: create impved mongodb query to cover many cases
638
+ function createQuery(filters) {
639
+ let query = new Object();
640
+
641
+ filters.forEach((item) => {
642
+ if (!item.name) {
643
+ return;
644
+ }
645
+ var key = item.name;
646
+ if (!query[key]) {
647
+ query[key] = {};
648
+ }
649
+
650
+ if (item.name == "_id")
651
+ item.value = ObjectId(item.value)
652
+
653
+ switch (item.operator) {
654
+ case '$contain':
655
+ query[key]['$regex'] = item.value;
656
+ break;
657
+
658
+ case '$range':
659
+ if (item.value[0] !== null && item.value[1] !== null) {
660
+ query[key] = {$gte: item.value[0], $lte: item.value[1]};
661
+ } else if (item.value[0] !== null) {
662
+ query[key] = {$gte: item.value[0]};
663
+ } else if (item.value[1] !== null) {
664
+ query[key] = {$lte: item.value[1]};
665
+ }
666
+ break;
667
+
668
+ case '$eq':
669
+ case '$ne':
670
+ case '$lt':
671
+ case '$lte':
672
+ case '$gt':
673
+ case '$gte':
674
+ case '$regex':
675
+ query[key][item.operator] = item.value;
676
+ break;
677
+ case '$in':
678
+ var in_values = [];
679
+ item.value.forEach(function(v) {
680
+ in_values.push(new RegExp(".*" + v + ".*", "i"));
681
+ });
682
+
683
+ query[key] = {$in : in_values }
684
+ break;
685
+ case '$nin':
686
+ query[key][item.operator] = item.value;
687
+ break;
688
+ case '$geoWithin':
689
+ try {
690
+ let value = JSON.parse(item.value);
691
+ if (item.type) {
692
+ query[key]['$geoWithin'] = {
693
+ [item.type]: value
694
+ }
695
+ }
696
+ } catch(e) {
697
+ console.log('geowithin error');
698
+ }
699
+ break;
700
+ }
701
+ })
702
+
703
+ //. global search
704
+ //. we have to set indexes in text fields ex: db.chart.createIndex({ "$**": "text" })
705
+ // if (data['searchKey']) {
706
+ // query["$text"] = {$search: "\"Ni\""};
707
+ // }
708
+
709
+ return query;
710
+ }
711
+
712
+ function valueTypes(data) {
713
+ let object = {}
714
+ if( typeof data === 'object' ) {
715
+ // update['$set'] = {}
716
+ for (const [key, value] of Object.entries(data)) {
717
+ let val;
718
+ let valueType = typeof value;
719
+ switch(valueType) {
720
+ case 'string':
721
+ val = value
722
+ break;
723
+ case 'number':
724
+ val = Number(value)
725
+ break;
726
+ case 'object':
727
+ if (Array.isArray(value))
728
+ val = new Array(...value)
729
+ else
730
+ val = new Object(value)
731
+ break;
732
+ default:
733
+ val = value
734
+ }
735
+ object[key] = val
736
+ }
737
+ return object;
738
+ }
739
+ }
740
+
741
+ function errorHandler(data, error, database, collection){
742
+ if (typeof error == 'object')
743
+ error['db'] = 'mongodb'
744
+ else
745
+ error = {db: 'mongodb', message: error}
746
+
747
+ if (database)
748
+ error['database'] = database
749
+ if (collection)
750
+ error['collection'] = collection
751
+ if(data.error)
752
+ data.error.push(error)
753
+ else
754
+ data.error = [error]
755
+ }
756
+
757
+
758
+
759
+
760
+ module.exports = {
761
+ databaseStats,
762
+ createDatabase,
763
+ readDatabase,
764
+ updateDatabase,
765
+ deleteDatabase,
766
+
767
+ createCollection,
768
+ readCollection,
769
+ updateCollection,
770
+ deleteCollection,
771
+
772
+ createDocument,
773
+ readDocument,
774
+ updateDocument,
775
+ deleteDocument,
776
+ }