monastery 1.32.4 → 1.34.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 +21 -0
- package/docs/readme.md +15 -0
- package/lib/index.js +3 -0
- package/lib/model-crud.js +83 -165
- package/lib/model.js +7 -52
- package/lib/rules.js +8 -7
- package/package.json +1 -1
- package/plugins/images/index.js +2 -2
- package/test/blacklisting.js +194 -73
- package/test/crud.js +5 -1
- package/test/model.js +0 -165
- package/test/util.js +7 -0
- package/test/validate.js +41 -5
package/changelog.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.34.0](https://github.com/boycce/monastery/compare/1.33.0...1.34.0) (2022-04-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* added db.arraySchema / db.arrayWithSchema ([4210dc3](https://github.com/boycce/monastery/commit/4210dc33486de757a22d0973e661522e19230158))
|
|
11
|
+
|
|
12
|
+
## [1.33.0](https://github.com/boycce/monastery/compare/1.32.5...1.33.0) (2022-04-05)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* whitelisting a parent will remove any previously blacklisted children ([d335c2e](https://github.com/boycce/monastery/commit/d335c2e2e9e691b2c0b406f06cb9c2ed14f8bb25))
|
|
18
|
+
|
|
19
|
+
### [1.32.5](https://github.com/boycce/monastery/compare/1.32.4...1.32.5) (2022-03-21)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* dates not enforcing numbers ([d5a2a53](https://github.com/boycce/monastery/commit/d5a2a533f497b3c9e50cbeb66407cf9bc08dd2e4))
|
|
25
|
+
|
|
5
26
|
### [1.32.4](https://github.com/boycce/monastery/compare/1.32.3...1.32.4) (2022-03-07)
|
|
6
27
|
|
|
7
28
|
### [1.32.3](https://github.com/boycce/monastery/compare/1.32.2...1.32.3) (2022-03-07)
|
package/docs/readme.md
CHANGED
|
@@ -81,6 +81,21 @@ npm run dev -- -t 'Model indexes'
|
|
|
81
81
|
|
|
82
82
|
Coming soon...
|
|
83
83
|
|
|
84
|
+
## Roadmap
|
|
85
|
+
|
|
86
|
+
- Add FindOneAndUpdate
|
|
87
|
+
- Add before/afterInsertUpdate
|
|
88
|
+
- Bug: Setting an object literal on an ID field ('model') saves successfully
|
|
89
|
+
- Population within array items
|
|
90
|
+
- ~~Blackislisitng which aggregates from the order of appearance~~
|
|
91
|
+
- ~~Whitelisting a parent will remove any previously blacklisted children~~
|
|
92
|
+
- Automatic subdocument ids
|
|
93
|
+
- Remove ACL default 'public read'
|
|
94
|
+
- Public db.arrayWithSchema method
|
|
95
|
+
- Global after/before hooks
|
|
96
|
+
- Split away from Monk (unless updated)
|
|
97
|
+
- docs: Make the implicit ID query conversion more apparent
|
|
98
|
+
|
|
84
99
|
## Special Thanks
|
|
85
100
|
|
|
86
101
|
[Jerome Gravel-Niquet](https://github.com/jeromegn)
|
package/lib/index.js
CHANGED
|
@@ -53,6 +53,9 @@ module.exports = function(uri, opts, fn) {
|
|
|
53
53
|
manager.timestamps = timestamps
|
|
54
54
|
manager.useMilliseconds = useMilliseconds
|
|
55
55
|
manager.beforeModel = []
|
|
56
|
+
manager.arrayWithSchema = manager.arraySchema = (array, schema) => {
|
|
57
|
+
array.schema = schema; return array
|
|
58
|
+
}
|
|
56
59
|
|
|
57
60
|
// Depreciation warnings
|
|
58
61
|
if (depreciationWarningDefaultField) {
|
package/lib/model-crud.js
CHANGED
|
@@ -70,9 +70,8 @@ module.exports = {
|
|
|
70
70
|
let options = util.omit(opts, ['blacklist', 'one', 'populate', 'project', 'query', 'respond'])
|
|
71
71
|
options.addFields = options.addFields || {}
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Using project (can be an inclusion/exclusion)
|
|
74
74
|
if (opts.project) {
|
|
75
|
-
// Can be an inclusion or exclusion projection
|
|
76
75
|
if (util.isString(opts.project)) {
|
|
77
76
|
opts.project = opts.project.trim().split(/\s+/)
|
|
78
77
|
}
|
|
@@ -82,11 +81,9 @@ module.exports = {
|
|
|
82
81
|
return o
|
|
83
82
|
}, {})
|
|
84
83
|
}
|
|
84
|
+
// Or blacklisting
|
|
85
85
|
} else {
|
|
86
|
-
|
|
87
|
-
let blacklistProjection = { ...this.findBLProject }
|
|
88
|
-
blacklistProjection = this._addDeepBlacklists(blacklistProjection, opts.populate)
|
|
89
|
-
options.projection = this._addBlacklist(blacklistProjection, opts.blacklist)
|
|
86
|
+
options.projection = this._getBlacklistProjection('find', opts.blacklist)
|
|
90
87
|
}
|
|
91
88
|
// Has text search?
|
|
92
89
|
// if (opts.query.$text) {
|
|
@@ -146,9 +143,6 @@ module.exports = {
|
|
|
146
143
|
}
|
|
147
144
|
|
|
148
145
|
if (opts.one && util.isArray(response)) response = response[0]
|
|
149
|
-
// (Not using) Project works with lookup.
|
|
150
|
-
// Remove blacklisted properties from joined models, because subpiplines with 'project' are slower
|
|
151
|
-
// if (opts.populate) this._depreciated_removeBlacklisted(data, opts.populate, options.projection)
|
|
152
146
|
response = await this._processAfterFind(response, options.projection, opts)
|
|
153
147
|
|
|
154
148
|
// Success
|
|
@@ -270,6 +264,80 @@ module.exports = {
|
|
|
270
264
|
}
|
|
271
265
|
},
|
|
272
266
|
|
|
267
|
+
_getBlacklistProjection: function(type, customBlacklist) {
|
|
268
|
+
/**
|
|
269
|
+
* Returns an exclusion projection
|
|
270
|
+
*
|
|
271
|
+
* @param {string} type - find, insert, or update
|
|
272
|
+
* @param {array} customBlacklist - normally passed through options
|
|
273
|
+
* @return {array} exclusion projection {'pets.name': 0}
|
|
274
|
+
* @this model
|
|
275
|
+
*
|
|
276
|
+
* 1. collate deep-blacklists
|
|
277
|
+
* 2. concatenate the model's blacklist and any custom blacklist
|
|
278
|
+
* 3. create an exclusion projection object from the blacklist, overriding from left to right
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
let list = []
|
|
282
|
+
let manager = this.manager
|
|
283
|
+
let projection = {}
|
|
284
|
+
|
|
285
|
+
// Concat deep blacklists
|
|
286
|
+
util.forEach(this.fieldsFlattened, (schema, path) => {
|
|
287
|
+
if (!schema.model) return
|
|
288
|
+
let deepBL = manager.model[schema.model][`${type}BL`] || []
|
|
289
|
+
let pathWithoutArrays = path.replace(/\.0(\.|$)/, '$1')
|
|
290
|
+
list = list.concat(deepBL.map(o => {
|
|
291
|
+
return `${o.charAt(0) == '-'? '-' : ''}${pathWithoutArrays}.${o.replace(/^-/, '')}`
|
|
292
|
+
}))
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Concat model, and custom blacklists
|
|
296
|
+
list = list.concat([...this[`${type}BL`]]).concat(customBlacklist || [])
|
|
297
|
+
|
|
298
|
+
// Loop blacklists
|
|
299
|
+
for (let _key of list) {
|
|
300
|
+
let key = _key.replace(/^-/, '')
|
|
301
|
+
let whitelisted = _key.match(/^-/)
|
|
302
|
+
|
|
303
|
+
// Remove any child fields. E.g remove { user.token: 0 } = key2 if iterating { user: 0 } = key
|
|
304
|
+
for (let key2 in projection) {
|
|
305
|
+
if (key2.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) { // test that scoped to the \.
|
|
306
|
+
delete projection[key2]
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Whitelist
|
|
311
|
+
if (whitelisted) {
|
|
312
|
+
projection[key] = 1
|
|
313
|
+
// Whitelisting a child of a blacklisted field (blacklist expansion)
|
|
314
|
+
// let parent = '' // highest blacklisted parent
|
|
315
|
+
// for (let key2 in projection) {
|
|
316
|
+
// if (key2.length > parent.length && key.match(new RegExp('^' + key2.replace(/\./g, '\\.')))) {
|
|
317
|
+
// parent = key2
|
|
318
|
+
// }
|
|
319
|
+
// }
|
|
320
|
+
|
|
321
|
+
// Blacklist (only if there isn't a parent blacklisted)
|
|
322
|
+
} else {
|
|
323
|
+
let parent
|
|
324
|
+
for (let key2 in projection) { // E.g. [address = key2, addresses.country = key]
|
|
325
|
+
if (projection[key2] == 0 && key.match(new RegExp('^' + key2.replace(/\./g, '\\.') + '\\.'))) {
|
|
326
|
+
parent = key2
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!parent) projection[key] = 0
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Remove whitelist projections
|
|
334
|
+
for (let key in projection) {
|
|
335
|
+
if (projection[key]) delete projection[key]
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return projection
|
|
339
|
+
},
|
|
340
|
+
|
|
273
341
|
_queryObject: async function(opts, type, one) {
|
|
274
342
|
/**
|
|
275
343
|
* Normalise options
|
|
@@ -347,17 +415,18 @@ module.exports = {
|
|
|
347
415
|
|
|
348
416
|
// Loop found model/deep-model data objects, and populate missing default-fields and call afterFind on each
|
|
349
417
|
for (let item of modelData) {
|
|
350
|
-
//
|
|
418
|
+
// Populate missing default fields if data !== null
|
|
351
419
|
// NOTE: maybe only call functions if default is being set.. fine for now
|
|
352
420
|
if (item.dataRef) {
|
|
353
|
-
util.forEach(model[item.modelName].
|
|
421
|
+
util.forEach(model[item.modelName].fieldsFlattened, (schema, path) => {
|
|
422
|
+
if (!util.isDefined(schema.default) || path.match(/^\.?(createdAt|updatedAt)$/)) return
|
|
354
423
|
let parentPath = item.fieldName? item.fieldName + '.' : ''
|
|
355
424
|
let pathWithoutArrays = (parentPath + path).replace(/\.0(\.|$)/, '$1')
|
|
356
425
|
// Ignore default fields that are excluded in a blacklist/parent-blacklist
|
|
357
426
|
if (!this._pathInProjection(pathWithoutArrays, projection, true)) return
|
|
358
|
-
//
|
|
427
|
+
// console.log(pathWithoutArrays, path, projection)
|
|
428
|
+
// Set value
|
|
359
429
|
let value = util.isFunction(schema.default)? schema.default(this.manager) : schema.default
|
|
360
|
-
// console.log(item.dataRef)
|
|
361
430
|
util.setDeepValue(item.dataRef, path.replace(/\.0(\.|$)/g, '.$$$1'), value, true, false, true)
|
|
362
431
|
})
|
|
363
432
|
}
|
|
@@ -369,157 +438,6 @@ module.exports = {
|
|
|
369
438
|
return util.runSeries(callbackSeries).then(() => data)
|
|
370
439
|
},
|
|
371
440
|
|
|
372
|
-
_addDeepBlacklists: function(blacklistProjection, populate) {
|
|
373
|
-
/**
|
|
374
|
-
* Include deep-model blacklists into the projection
|
|
375
|
-
* @param {object} blacklistProject
|
|
376
|
-
* @param {array} populate - find populate array
|
|
377
|
-
* @return {object} exclusion blacklist
|
|
378
|
-
* @this model
|
|
379
|
-
*/
|
|
380
|
-
let model = this
|
|
381
|
-
let manager = this.manager
|
|
382
|
-
let paths = (populate||[]).map(o => o && o.as? o.as : o)
|
|
383
|
-
|
|
384
|
-
if (!paths.length) return blacklistProjection
|
|
385
|
-
this._recurseFields(model.fields, '', function(path, field) {
|
|
386
|
-
// Remove array indexes from the path e.g. '0.'
|
|
387
|
-
path = path.replace(/(\.[0-9]+)(\.|$)/, '$2')
|
|
388
|
-
if (!field.model || !paths.includes(path)) return
|
|
389
|
-
loop: for (let prop of manager.model[field.model].findBL) {
|
|
390
|
-
// Don't include any deep model projection keys that already have a parent specified. E.g if
|
|
391
|
-
// both { users.secrets: 1 } and { users.secrets.token: 1 } exist, remove the later
|
|
392
|
-
for (let key in blacklistProjection) {
|
|
393
|
-
if ((path + '.' + prop).match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) {
|
|
394
|
-
continue loop
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
// Add model property to blacklist
|
|
398
|
-
blacklistProjection[path + '.' + prop] = 0
|
|
399
|
-
}
|
|
400
|
-
})
|
|
401
|
-
return blacklistProjection
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
_addBlacklist: function(blacklistProjection, blacklist) {
|
|
405
|
-
/**
|
|
406
|
-
* Merge blacklist in
|
|
407
|
-
* @param {object} blacklistProjection
|
|
408
|
-
* @param {array} paths - e.g. ['password', '-email'] - email will be whitelisted / removed from
|
|
409
|
-
* exlcusion projection
|
|
410
|
-
* @return {object} exclusion blacklist
|
|
411
|
-
* @this model
|
|
412
|
-
*/
|
|
413
|
-
if (blacklist === false) {
|
|
414
|
-
return {}
|
|
415
|
-
} else if (!blacklist) {
|
|
416
|
-
return blacklistProjection
|
|
417
|
-
} else {
|
|
418
|
-
// Loop blacklist
|
|
419
|
-
for (let _key of blacklist) {
|
|
420
|
-
let key = _key.replace(/^-/, '')
|
|
421
|
-
let negated = _key.match(/^-/)
|
|
422
|
-
// Remove any deep projection keys that already have a parent specified. E.g if
|
|
423
|
-
// both { users.secrets: 0 } and { users.secrets.token: 0 } exist, remove the later
|
|
424
|
-
for (let key2 in blacklistProjection) {
|
|
425
|
-
if (key2.match(new RegExp('^' + key.replace(/\./g, '\\.') + '(\\.|$)'))) {
|
|
426
|
-
delete blacklistProjection[key2]
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (negated) {
|
|
430
|
-
if (blacklistProjection.hasOwnProperty(key)) delete blacklistProjection[key]
|
|
431
|
-
} else {
|
|
432
|
-
blacklistProjection[key] = 0
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
return blacklistProjection
|
|
436
|
-
}
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
_depreciated_removeBlacklisted: function(data, populate, whitelist) {
|
|
440
|
-
/**
|
|
441
|
-
* Remove blacklisted fields, takes foreign model blacklists into account
|
|
442
|
-
* @param {object|array} data
|
|
443
|
-
* @param {array} <populate> find populate list
|
|
444
|
-
* @param {array} <whitelist>
|
|
445
|
-
*/
|
|
446
|
-
let findBL = this.findBL
|
|
447
|
-
let findWL = [ '_id', ...this.findWL ]
|
|
448
|
-
let model = this.manager.model
|
|
449
|
-
|
|
450
|
-
this._recurseFields(this.fields, '', function(path, field) {
|
|
451
|
-
// Remove array indexes from the path e.g. '0.'
|
|
452
|
-
path = path.replace(/(\.[0-9]+)(\.|$)/, '$2')
|
|
453
|
-
//if (field.type == 'any') findWL.push(path) //exclude.push(path)
|
|
454
|
-
|
|
455
|
-
// Below: Merge in model whitelists (we should cache the results)
|
|
456
|
-
if (!field.model) return
|
|
457
|
-
else if (!model[field.model]) return
|
|
458
|
-
|
|
459
|
-
// Has this path been blacklisted already?
|
|
460
|
-
for (let blacklisted of findBL) {
|
|
461
|
-
if (blacklisted === path || path.includes(blacklisted + '.', 0)) {
|
|
462
|
-
return
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Populating?
|
|
467
|
-
if ((populate||[]).includes(path)) {
|
|
468
|
-
// Remove the model-field from the whitelist if populating
|
|
469
|
-
let parentIndex = findWL.indexOf(path)
|
|
470
|
-
if (parentIndex !== -1) findWL.splice(parentIndex, 1)
|
|
471
|
-
|
|
472
|
-
// Okay, merge in the model's whitelist
|
|
473
|
-
findWL.push(path + '.' + '_id')
|
|
474
|
-
for (let prop of model[field.model].findWL) {
|
|
475
|
-
// Dont add model properties that are already blacklisted
|
|
476
|
-
if (findBL.includes(path + '.' + prop)) continue
|
|
477
|
-
// Add model prop to whitelist
|
|
478
|
-
findWL.push(path + '.' + prop)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
// Merge in the passed in whitelist
|
|
484
|
-
findWL = findWL.concat(whitelist || [])
|
|
485
|
-
|
|
486
|
-
// Fill in missing parents e.g. pets.dog.name, pets.dog doesn't exist
|
|
487
|
-
// Doesn't matter what order these are added in
|
|
488
|
-
// for (let whitelisted of findWL) {
|
|
489
|
-
// let parents = whitelisted.split('.')
|
|
490
|
-
// let target = ''
|
|
491
|
-
// for (let parent of parents) {
|
|
492
|
-
// if (!findWL.includes(target + parent)) findWL.push(target + parent)
|
|
493
|
-
// target = target + parent + '.'
|
|
494
|
-
// }
|
|
495
|
-
// }
|
|
496
|
-
|
|
497
|
-
console.log(1, findWL)
|
|
498
|
-
console.log(2, data)
|
|
499
|
-
|
|
500
|
-
// "data.cat.colour" needs to add "data.cat"
|
|
501
|
-
// "data.cat" needs to everything
|
|
502
|
-
|
|
503
|
-
function recurseAndDeleteData(data, path) {
|
|
504
|
-
// Remove array indexes from the path e.g. '0.'
|
|
505
|
-
let newpath = path.replace(/(\.[0-9]+)(\.|$)/, '$2')
|
|
506
|
-
util.forEach(data, function(field, fieldName) {
|
|
507
|
-
if ((util.isArray(field) || util.isObjectAndNotID(field))/* && !exclude.includes(newpath + fieldName)*/) {
|
|
508
|
-
if (findWL.includes(newpath + fieldName)) return
|
|
509
|
-
recurseAndDeleteData(field, newpath + fieldName + '.')
|
|
510
|
-
} else if (!findWL.includes(newpath + fieldName) && !util.isNumber(fieldName)) {
|
|
511
|
-
console.log(3, fieldName)
|
|
512
|
-
delete data[fieldName]
|
|
513
|
-
}
|
|
514
|
-
})
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
for (let doc of util.toArray(data)) {
|
|
518
|
-
recurseAndDeleteData(doc, '')
|
|
519
|
-
}
|
|
520
|
-
return data
|
|
521
|
-
},
|
|
522
|
-
|
|
523
441
|
_pathInProjection: function(path, projection, matchParentPaths) {
|
|
524
442
|
/**
|
|
525
443
|
* Checks if the path is valid within a inclusion/exclusion projection
|
|
@@ -630,6 +548,6 @@ module.exports = {
|
|
|
630
548
|
cb(path + fieldName, field)
|
|
631
549
|
}
|
|
632
550
|
}, this)
|
|
633
|
-
}
|
|
551
|
+
},
|
|
634
552
|
|
|
635
553
|
}
|
package/lib/model.js
CHANGED
|
@@ -69,12 +69,8 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
69
69
|
}, this)
|
|
70
70
|
|
|
71
71
|
// Extend default fields with passed in fields and check for invalid fields
|
|
72
|
-
this.fields = Object.assign({}, this._timestampFields, this.fields)
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
// console.log(0, this.fieldlist)
|
|
76
|
-
// console.log(0, this.findBL)
|
|
77
|
-
// console.log(0, this.findBLProject)
|
|
72
|
+
this._setupFields(this.fields = Object.assign({}, this._timestampFields, this.fields))
|
|
73
|
+
this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
|
|
78
74
|
|
|
79
75
|
// Extend model with monk collection actions
|
|
80
76
|
this._collection = manager.get? manager.get(name, { castIds: false }) : null
|
|
@@ -107,52 +103,24 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
107
103
|
else this._setupIndexes().catch(errHandler) // returns this
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
Model.prototype._getFieldlist = function(fields, path) {
|
|
111
|
-
/**
|
|
112
|
-
* Get all field paths (without array indices, handy for blacklisting)
|
|
113
|
-
* @param {object|array} fields - subdocument or array
|
|
114
|
-
* @param {string} path
|
|
115
|
-
* @return {array} e.g. ['name', 'pets.dog']
|
|
116
|
-
*/
|
|
117
|
-
let list = []
|
|
118
|
-
util.forEach(fields, function(field, fieldName) {
|
|
119
|
-
// Don't append array indexes to the new path e.g. '0.'
|
|
120
|
-
let newPath = util.isArray(fields)? path : path + fieldName + '.'
|
|
121
|
-
if (fieldName == 'schema') {
|
|
122
|
-
return
|
|
123
|
-
/*} else if (this.findBL.includes(newPath.replace(/\.$/, ''))) {
|
|
124
|
-
return*/
|
|
125
|
-
} else if (util.isArray(field)) {
|
|
126
|
-
list = list.concat(this._getFieldlist(field, newPath))
|
|
127
|
-
} else if (util.isSubdocument(field)) {
|
|
128
|
-
list = list.concat(this._getFieldlist(field, newPath))
|
|
129
|
-
} else {
|
|
130
|
-
list.push(newPath.replace(/\.$/, ''))
|
|
131
|
-
}
|
|
132
|
-
}, this)
|
|
133
|
-
return list
|
|
134
|
-
}
|
|
135
|
-
|
|
136
106
|
Model.prototype._getFieldsFlattened = function(fields, path) {
|
|
137
107
|
/**
|
|
138
108
|
* Flatten fields
|
|
139
|
-
* @param {object|array} fields - subdocument or array
|
|
109
|
+
* @param {object|array} fields - can be a nested subdocument or array
|
|
140
110
|
* @param {string} path
|
|
141
|
-
* @return {object} e.g.
|
|
111
|
+
* @return {object} e.g. {'name': Schema, 'pets.dog': Schema}
|
|
142
112
|
*/
|
|
143
113
|
let obj = {}
|
|
144
114
|
util.forEach(fields, function(field, fieldName) {
|
|
145
|
-
let newPath = path + fieldName + '.'
|
|
115
|
+
let newPath = /*util.isArray(fields)? path : */path + fieldName + '.'
|
|
146
116
|
if (fieldName == 'schema') {
|
|
147
117
|
return
|
|
148
118
|
} else if (util.isArray(field)) {
|
|
149
119
|
Object.assign(obj, this._getFieldsFlattened(field, newPath))
|
|
150
120
|
} else if (util.isSubdocument(field)) {
|
|
151
121
|
Object.assign(obj, this._getFieldsFlattened(field, newPath))
|
|
152
|
-
} else
|
|
153
|
-
|
|
154
|
-
obj[newPath.replace(/\.$/, '')] = field
|
|
155
|
-
}
|
|
122
|
+
} else {
|
|
123
|
+
obj[newPath.replace(/\.$/, '')] = field
|
|
156
124
|
}
|
|
157
125
|
}, this)
|
|
158
126
|
return obj
|
|
@@ -234,19 +202,6 @@ Model.prototype._setupFields = function(fields) {
|
|
|
234
202
|
}, this)
|
|
235
203
|
},
|
|
236
204
|
|
|
237
|
-
Model.prototype._setupFieldsAndWhitelists = function(fields, path) {
|
|
238
|
-
/**
|
|
239
|
-
* Setup fields and retrieve a handy schema field list. This can be called mulitple times.
|
|
240
|
-
* Note: the project fields are only required when finding with projections.
|
|
241
|
-
* @param {object} fields - can be a nested subdocument or array
|
|
242
|
-
* @param {string} <path>
|
|
243
|
-
*/
|
|
244
|
-
this._setupFields(fields)
|
|
245
|
-
this.fieldlist = this._getFieldlist(fields, path || '')
|
|
246
|
-
this.defaultFieldsFlattened = this._getFieldsFlattened(fields, path || '') // test output?
|
|
247
|
-
this.findBLProject = this.findBL.reduce((o, v) => { (o[v] = 0); return o }, {})
|
|
248
|
-
},
|
|
249
|
-
|
|
250
205
|
Model.prototype._setupIndexes = function(fields, opts={}) {
|
|
251
206
|
/**
|
|
252
207
|
* Creates indexes for the model (multikey, and sub-document supported)
|
package/lib/rules.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Todo: remove stringnums in date/number/integer rules
|
|
1
2
|
let ObjectId = require('mongodb').ObjectId
|
|
2
3
|
let util = require('./util')
|
|
3
4
|
let validator = require('validator')
|
|
@@ -48,12 +49,12 @@ module.exports = {
|
|
|
48
49
|
message: 'Value was not a unix timestamp.',
|
|
49
50
|
tryParse: function(x) {
|
|
50
51
|
if (x === '') return null
|
|
51
|
-
if (util.isString(x) && x.match(/^[+-]
|
|
52
|
-
return isNaN(
|
|
52
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
|
|
53
|
+
return isNaN(Number(x)) || (!x && x!==0) || x === true? x : Number(x)
|
|
53
54
|
},
|
|
54
55
|
fn: function(x) {
|
|
55
|
-
if (util.isString(x) && x.match(/^[+-]
|
|
56
|
-
return typeof x === 'number'
|
|
56
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
57
|
+
return typeof x === 'number'
|
|
57
58
|
}
|
|
58
59
|
},
|
|
59
60
|
isImageObject: {
|
|
@@ -78,10 +79,10 @@ module.exports = {
|
|
|
78
79
|
tryParse: function(x) {
|
|
79
80
|
if (x === '') return null
|
|
80
81
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
|
|
81
|
-
return isNaN(parseInt(x)) || (!x && x!==0) || x===true? x : parseInt(x)
|
|
82
|
+
return isNaN(parseInt(x)) || (!x && x!==0) || x === true? x : parseInt(x)
|
|
82
83
|
},
|
|
83
84
|
fn: function(x) {
|
|
84
|
-
if (util.isString(x) && x.match(/^[+-]
|
|
85
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
85
86
|
return typeof x === 'number' && (parseInt(x) === x)
|
|
86
87
|
}
|
|
87
88
|
},
|
|
@@ -91,7 +92,7 @@ module.exports = {
|
|
|
91
92
|
tryParse: function(x) {
|
|
92
93
|
if (x === '') return null
|
|
93
94
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
|
|
94
|
-
return isNaN(Number(x)) || (!x && x!==0) || x===true? x : Number(x)
|
|
95
|
+
return isNaN(Number(x)) || (!x && x!==0) || x === true? x : Number(x)
|
|
95
96
|
},
|
|
96
97
|
fn: function(x) {
|
|
97
98
|
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "monastery",
|
|
3
3
|
"description": "⛪ A straight forward MongoDB ODM built around Monk",
|
|
4
4
|
"author": "Ricky Boyce",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.34.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/plugins/images/index.js
CHANGED
|
@@ -56,8 +56,8 @@ let plugin = module.exports = {
|
|
|
56
56
|
model.imageFields = plugin._findAndTransformImageFields(model.fields, '')
|
|
57
57
|
|
|
58
58
|
if (model.imageFields.length) {
|
|
59
|
-
// Todo?: Update image fields
|
|
60
|
-
// model.
|
|
59
|
+
// Todo?: Update image fields / blacklists with the new object schema
|
|
60
|
+
// model._setupFields(model.fields)/model._getFieldsFlattened(model.fields)
|
|
61
61
|
model.beforeValidate.push(function(data, n) {
|
|
62
62
|
plugin.keepImagePlacement(this, data).then(() => n(null, data)).catch(e => n(e))
|
|
63
63
|
})
|
package/test/blacklisting.js
CHANGED
|
@@ -93,8 +93,6 @@ module.exports = function(monastery, opendb) {
|
|
|
93
93
|
}
|
|
94
94
|
}})
|
|
95
95
|
|
|
96
|
-
// Note: testing mongodb projections
|
|
97
|
-
|
|
98
96
|
// Test initial blacklist
|
|
99
97
|
let find1 = await user.findOne({
|
|
100
98
|
query: user1._id
|
|
@@ -122,37 +120,184 @@ module.exports = function(monastery, opendb) {
|
|
|
122
120
|
animals: { dog: 'Max', cat: 'Ginger' }
|
|
123
121
|
})
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
db.close()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('find blacklisting population', async () => {
|
|
127
|
+
// inprogresss
|
|
128
|
+
// Setup
|
|
129
|
+
let db = monastery('localhost/monastery', {
|
|
130
|
+
timestamps: false,
|
|
131
|
+
serverSelectionTimeoutMS: 2000,
|
|
129
132
|
})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
let bird = db.model('bird', {
|
|
134
|
+
fields: {
|
|
135
|
+
color: { type: 'string', default: 'red' },
|
|
136
|
+
height: { type: 'number' },
|
|
137
|
+
name: { type: 'string' },
|
|
138
|
+
sub: {
|
|
139
|
+
color: { type: 'string', default: 'red' },
|
|
140
|
+
},
|
|
141
|
+
subs: [{
|
|
142
|
+
color: { type: 'string', default: 'red'},
|
|
143
|
+
}],
|
|
144
|
+
wing: {
|
|
145
|
+
size: { type: 'number' },
|
|
146
|
+
sizes: {
|
|
147
|
+
one: { type: 'number' },
|
|
148
|
+
two: { type: 'number' },
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
findBL: ['wing']
|
|
135
153
|
})
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
'
|
|
154
|
+
let user = db.model('user', {
|
|
155
|
+
fields: {
|
|
156
|
+
dog: { type: 'string' },
|
|
157
|
+
bird1: { model: 'bird' },
|
|
158
|
+
bird2: { model: 'bird' },
|
|
159
|
+
bird3: { model: 'bird' },
|
|
160
|
+
bird4: { model: 'bird' },
|
|
161
|
+
bird5: { model: 'bird' },
|
|
162
|
+
},
|
|
163
|
+
findBL: [
|
|
164
|
+
'bird1.name', // bird1.name & bird1.wing blacklisted
|
|
165
|
+
'-bird2', 'bird2.name', // bird2.name blacklisted
|
|
166
|
+
'bird3.name', '-bird3', 'bird3.height', // bird3.height blacklisted
|
|
167
|
+
'-bird4.wing.sizes.one', '-bird4.wing.size', // ignored
|
|
168
|
+
// bird4.wing.sizes.two blacklisted (expand in future verion)
|
|
169
|
+
'-bird5.wing.sizes.one', // bird5.wing.sizes.one ignored, wing blacklisted
|
|
170
|
+
// bird5.wing.sizes.two, wing.size blacklisted (expand in future verion)
|
|
143
171
|
]
|
|
144
172
|
})
|
|
145
|
-
|
|
173
|
+
let bird1 = await bird.insert({
|
|
174
|
+
data: {
|
|
175
|
+
name: 'ponyo',
|
|
176
|
+
height: 40,
|
|
177
|
+
sub: {},
|
|
178
|
+
wing: { size: 1, sizes: { one: 1, two: 1 }}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
let userData = {
|
|
182
|
+
dog: 'Bruce',
|
|
183
|
+
bird1: bird1._id,
|
|
184
|
+
bird2: bird1._id,
|
|
185
|
+
bird3: bird1._id,
|
|
186
|
+
bird4: bird1._id,
|
|
187
|
+
bird5: bird1._id
|
|
188
|
+
}
|
|
189
|
+
let user1 = await user.insert({ data: userData })
|
|
190
|
+
let bird1Base = { _id: bird1._id, color: 'red', sub: { color: 'red' }}
|
|
191
|
+
|
|
192
|
+
// Test bird1
|
|
193
|
+
expect(await user.findOne({ query: user1._id, populate: ['bird1'] })).toEqual({
|
|
194
|
+
...userData,
|
|
146
195
|
_id: user1._id,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
196
|
+
bird1: { ...bird1Base, height: 40 },
|
|
197
|
+
})
|
|
198
|
+
// Test bird2
|
|
199
|
+
expect(await user.findOne({ query: user1._id, populate: ['bird2'] })).toEqual({
|
|
200
|
+
...userData,
|
|
201
|
+
_id: user1._id,
|
|
202
|
+
bird2: { ...bird1Base, height: 40, wing: { size: 1, sizes: { one: 1, two: 1 }} },
|
|
203
|
+
})
|
|
204
|
+
// Test bird3
|
|
205
|
+
expect(await user.findOne({ query: user1._id, populate: ['bird3'] })).toEqual({
|
|
206
|
+
...userData,
|
|
207
|
+
_id: user1._id,
|
|
208
|
+
bird3: { ...bird1Base, name: 'ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
|
|
209
|
+
})
|
|
210
|
+
// Test bird4
|
|
211
|
+
expect(await user.findOne({ query: user1._id, populate: ['bird4'] })).toEqual({
|
|
212
|
+
...userData,
|
|
213
|
+
_id: user1._id,
|
|
214
|
+
bird4: { ...bird1Base, name: 'ponyo', height: 40 },
|
|
215
|
+
})
|
|
216
|
+
// Test bird5
|
|
217
|
+
expect(await user.findOne({ query: user1._id, populate: ['bird5'] })).toEqual({
|
|
218
|
+
...userData,
|
|
219
|
+
_id: user1._id,
|
|
220
|
+
bird5: { ...bird1Base, name: 'ponyo', height: 40 },
|
|
150
221
|
})
|
|
151
222
|
|
|
152
223
|
db.close()
|
|
153
224
|
})
|
|
154
225
|
|
|
155
|
-
test('find blacklisting
|
|
226
|
+
test('find blacklisting getProjection', async () => {
|
|
227
|
+
let db = (await opendb(null)).db
|
|
228
|
+
// Setup
|
|
229
|
+
db.model('bird', {
|
|
230
|
+
fields: {
|
|
231
|
+
age: { type: 'number' },
|
|
232
|
+
name: { type: 'string' },
|
|
233
|
+
wing: {
|
|
234
|
+
size: { type: 'number' },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
findBL: ['age', 'wing']
|
|
238
|
+
})
|
|
239
|
+
db.model('user', {
|
|
240
|
+
fields: {
|
|
241
|
+
name: { type: 'string' },
|
|
242
|
+
bird1: { model: 'bird' },
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
// default
|
|
246
|
+
expect(db.user._getBlacklistProjection('find')).toEqual({
|
|
247
|
+
'bird1.wing': 0,
|
|
248
|
+
'bird1.age': 0,
|
|
249
|
+
'password': 0,
|
|
250
|
+
})
|
|
251
|
+
// blacklist /w invalid field (which goes through)
|
|
252
|
+
expect(db.user._getBlacklistProjection('find', ['name', 'invalidfield'])).toEqual({
|
|
253
|
+
'bird1.wing': 0,
|
|
254
|
+
'bird1.age': 0,
|
|
255
|
+
'invalidfield': 0,
|
|
256
|
+
'name': 0,
|
|
257
|
+
'password': 0,
|
|
258
|
+
})
|
|
259
|
+
// whitelist
|
|
260
|
+
expect(db.user._getBlacklistProjection('find', ['-password', '-bird1.age'])).toEqual({
|
|
261
|
+
'bird1.wing': 0,
|
|
262
|
+
})
|
|
263
|
+
// whitelist parent
|
|
264
|
+
expect(db.user._getBlacklistProjection('find', ['-bird1'])).toEqual({
|
|
265
|
+
'password': 0,
|
|
266
|
+
})
|
|
267
|
+
// whitelist parent, then blacklist child
|
|
268
|
+
expect(db.user._getBlacklistProjection('find', ['-bird1', 'bird1.name'])).toEqual({
|
|
269
|
+
'password': 0,
|
|
270
|
+
'bird1.name': 0,
|
|
271
|
+
})
|
|
272
|
+
// the model's blacklists are applied after deep model's
|
|
273
|
+
db.user.findBL = ['-bird1.age']
|
|
274
|
+
expect(db.user._getBlacklistProjection('find')).toEqual({
|
|
275
|
+
'bird1.wing': 0,
|
|
276
|
+
})
|
|
277
|
+
// custom blacklists are applied after the model's, which are after deep model's
|
|
278
|
+
db.user.findBL = ['-bird1.age']
|
|
279
|
+
expect(db.user._getBlacklistProjection('find', ['bird1'])).toEqual({
|
|
280
|
+
'bird1': 0,
|
|
281
|
+
})
|
|
282
|
+
// blacklisted parent with a blacklisted child
|
|
283
|
+
expect(db.user._getBlacklistProjection('find', ['bird1', 'bird1.wing'])).toEqual({
|
|
284
|
+
'bird1': 0,
|
|
285
|
+
})
|
|
286
|
+
// A mess of things
|
|
287
|
+
expect(db.user._getBlacklistProjection('find', ['-bird1', 'bird1.wing', '-bird1.wing','bird1.wing.size'])).toEqual({
|
|
288
|
+
'bird1.wing.size': 0,
|
|
289
|
+
})
|
|
290
|
+
// blacklisted parent with a whitelisted child (expect blacklist expansion in future version?)
|
|
291
|
+
// expect(db.user._getBlacklistProjection('find', ['bird1', '-bird1.wing'])).toEqual({
|
|
292
|
+
// 'bird1.age': 0,
|
|
293
|
+
// 'bird1.name': 0,
|
|
294
|
+
// })
|
|
295
|
+
|
|
296
|
+
db.close()
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('find project', async () => {
|
|
300
|
+
// Test mongodb native project option
|
|
156
301
|
// Setup
|
|
157
302
|
let db = (await opendb(null)).db
|
|
158
303
|
let user = db.model('user', {
|
|
@@ -184,7 +329,6 @@ module.exports = function(monastery, opendb) {
|
|
|
184
329
|
let find1 = await user.findOne({
|
|
185
330
|
query: user1._id,
|
|
186
331
|
project: ['animal.name', 'animals.name']
|
|
187
|
-
//blacklist: ['animal.color', 'animals']
|
|
188
332
|
})
|
|
189
333
|
expect(find1).toEqual({
|
|
190
334
|
_id: user1._id,
|
|
@@ -209,97 +353,74 @@ module.exports = function(monastery, opendb) {
|
|
|
209
353
|
]
|
|
210
354
|
})
|
|
211
355
|
|
|
212
|
-
// Test exclusion blacklist
|
|
213
|
-
let find3 = await user.findOne({
|
|
214
|
-
query: user1._id,
|
|
215
|
-
blacklist: ['animal.color', 'animals', 'color']
|
|
216
|
-
})
|
|
217
|
-
expect(find3).toEqual({
|
|
218
|
-
_id: user1._id,
|
|
219
|
-
name: 'Bruce',
|
|
220
|
-
animal: { name: 'max' }
|
|
221
|
-
})
|
|
222
|
-
|
|
223
356
|
db.close()
|
|
224
357
|
})
|
|
225
358
|
|
|
226
|
-
test('find
|
|
359
|
+
test('find project population', async () => {
|
|
360
|
+
// Test mongodb native project option
|
|
227
361
|
// Setup
|
|
228
362
|
let db = (await opendb(null)).db
|
|
229
363
|
let bird = db.model('bird', {
|
|
230
364
|
fields: {
|
|
231
365
|
name: { type: 'string' },
|
|
232
366
|
age: { type: 'number' },
|
|
367
|
+
height: { type: 'number' },
|
|
233
368
|
color: { type: 'string', default: 'red' },
|
|
234
369
|
sub: {
|
|
235
370
|
color: { type: 'string', default: 'red' },
|
|
236
371
|
}
|
|
237
|
-
}
|
|
372
|
+
},
|
|
373
|
+
findBL: ['age']
|
|
238
374
|
})
|
|
239
375
|
let user = db.model('user', {
|
|
240
376
|
fields: {
|
|
241
377
|
dog: { type: 'string' },
|
|
242
|
-
|
|
243
|
-
|
|
378
|
+
bird: { model: 'bird' },
|
|
379
|
+
bird2: { model: 'bird' },
|
|
380
|
+
bird3: { model: 'bird' }
|
|
244
381
|
},
|
|
382
|
+
findBL: [
|
|
383
|
+
// allll these should be ignored.....?/////
|
|
384
|
+
'bird.name', // bird.name & bird.age blacklisted
|
|
385
|
+
'-bird2', 'bird2.name', // bird2.name blacklisted
|
|
386
|
+
'bird3.name', '-bird3', 'bird3.height', // bird3.height blacklisted
|
|
387
|
+
]
|
|
245
388
|
})
|
|
246
389
|
let bird1 = await bird.insert({ data: {
|
|
247
390
|
name: 'ponyo',
|
|
248
391
|
age: 3,
|
|
392
|
+
height: 40,
|
|
249
393
|
sub: {}
|
|
250
394
|
}})
|
|
251
395
|
let user1 = await user.insert({ data: {
|
|
252
396
|
dog: 'Bruce',
|
|
253
|
-
|
|
254
|
-
|
|
397
|
+
bird: bird1._id,
|
|
398
|
+
bird2: bird1._id,
|
|
399
|
+
bird3: bird1._id
|
|
255
400
|
}})
|
|
256
401
|
|
|
257
402
|
// Test project
|
|
258
403
|
let find1 = await user.findOne({
|
|
259
404
|
query: user1._id,
|
|
260
|
-
populate: ['
|
|
261
|
-
project: ['
|
|
405
|
+
populate: ['bird', 'bird2'],
|
|
406
|
+
project: ['bird.age', 'bird2']
|
|
262
407
|
})
|
|
263
408
|
expect(find1).toEqual({
|
|
264
409
|
_id: user1._id,
|
|
265
|
-
|
|
266
|
-
|
|
410
|
+
bird: { age: 3 },
|
|
411
|
+
bird2: { _id: bird1._id, age: 3, name: 'ponyo', height: 40, color: 'red', sub: { color: 'red' }}
|
|
267
412
|
})
|
|
268
413
|
|
|
269
414
|
// Test project (different project details)
|
|
270
415
|
let find2 = await user.findOne({
|
|
271
416
|
query: user1._id,
|
|
272
|
-
populate: ['
|
|
273
|
-
project: ['
|
|
417
|
+
populate: ['bird', 'bird2'],
|
|
418
|
+
project: ['bird', 'bird2.height']
|
|
274
419
|
})
|
|
275
420
|
expect(find2).toEqual({
|
|
276
421
|
_id: user1._id,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
// Test blacklisting
|
|
282
|
-
let find22 = await user.findOne({
|
|
283
|
-
query: user1._id,
|
|
284
|
-
populate: ['myBird', 'myBird2'],
|
|
285
|
-
blacklist: ['dog', 'myBird2.name', 'myBird2._id']
|
|
286
|
-
})
|
|
287
|
-
expect(find22).toEqual({
|
|
288
|
-
_id: user1._id,
|
|
289
|
-
myBird: { _id: bird1._id, age: 3, name: 'ponyo', color: 'red', sub: { color: 'red' }},
|
|
290
|
-
myBird2: { age: 3, color: 'red', sub: { color: 'red' }}
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
// Test blacklisting overrides
|
|
294
|
-
let find3 = await user.findOne({
|
|
295
|
-
query: user1._id,
|
|
296
|
-
populate: ['myBird', 'myBird2'],
|
|
297
|
-
blacklist: ['dog', 'myBird2.name', 'myBird2._id', '-myBird2']
|
|
298
|
-
})
|
|
299
|
-
expect(find3).toEqual({
|
|
300
|
-
_id: user1._id,
|
|
301
|
-
myBird: { _id: bird1._id, age: 3, name: 'ponyo', color: 'red', sub: { color: 'red' }},
|
|
302
|
-
myBird2: { _id: bird1._id, age: 3, name: 'ponyo', color: 'red', sub: { color: 'red' }}
|
|
422
|
+
bird: { _id: bird1._id, age: 3, name: 'ponyo', height: 40, color: 'red', sub: { color: 'red' }},
|
|
423
|
+
bird2: { height: 40 },
|
|
303
424
|
})
|
|
304
425
|
|
|
305
426
|
db.close()
|
package/test/crud.js
CHANGED
|
@@ -391,7 +391,10 @@ module.exports = function(monastery, opendb) {
|
|
|
391
391
|
expect(find1).toEqual({
|
|
392
392
|
_id: inserted2._id,
|
|
393
393
|
name: 'Martin Luther',
|
|
394
|
-
addresses: [
|
|
394
|
+
addresses: [
|
|
395
|
+
{ city: 'Frankfurt', country: 'Germany' },
|
|
396
|
+
{ city: 'Christchurch', country: 'New Zealand' }
|
|
397
|
+
],
|
|
395
398
|
address: { country: 'Germany' },
|
|
396
399
|
pet: { dog: { _id: inserted._id, name: 'Scruff', user: inserted2._id }},
|
|
397
400
|
dogs: [{ _id: inserted._id, name: 'Scruff', user: inserted2._id }]
|
|
@@ -407,6 +410,7 @@ module.exports = function(monastery, opendb) {
|
|
|
407
410
|
as: 'dogs'
|
|
408
411
|
}],
|
|
409
412
|
blacklist: ['address', 'addresses.country', 'dogs.name']
|
|
413
|
+
// ^ great test (address should cancel addresses if not stopping at the .)
|
|
410
414
|
})
|
|
411
415
|
expect(find2).toEqual({
|
|
412
416
|
_id: inserted2._id,
|
package/test/model.js
CHANGED
|
@@ -411,169 +411,4 @@ module.exports = function(monastery, opendb) {
|
|
|
411
411
|
db.close()
|
|
412
412
|
})
|
|
413
413
|
|
|
414
|
-
test('model findBL findBLProject', async () => {
|
|
415
|
-
let db = (await opendb(null)).db
|
|
416
|
-
db.model('bird', { fields: {
|
|
417
|
-
name: { type: 'string' }
|
|
418
|
-
}})
|
|
419
|
-
let user = db.model('user', {
|
|
420
|
-
findBL: [
|
|
421
|
-
'pets.hiddenAge',
|
|
422
|
-
'animals.hiddenCat',
|
|
423
|
-
'hiddenDog',
|
|
424
|
-
'hiddenPets',
|
|
425
|
-
'hiddenList',
|
|
426
|
-
'deep.deep2.hiddenDeep3',
|
|
427
|
-
'hiddenDeeper',
|
|
428
|
-
'hiddenDeepModel',
|
|
429
|
-
'hiddenDeepModels'
|
|
430
|
-
],
|
|
431
|
-
fields: {
|
|
432
|
-
list: [{ type: 'number' }],
|
|
433
|
-
pet: { type: 'string' },
|
|
434
|
-
anyPet: { type: 'any' },
|
|
435
|
-
pets: [{ name: { type: 'string'}, hiddenAge: { type: 'number'} }],
|
|
436
|
-
animals: {
|
|
437
|
-
dog: { type: 'string' },
|
|
438
|
-
hiddenCat: { type: 'string' }
|
|
439
|
-
},
|
|
440
|
-
deep: {
|
|
441
|
-
deep2: {
|
|
442
|
-
hiddenDeep3: {
|
|
443
|
-
deep4: { type: 'string' }
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
deepModel: {
|
|
448
|
-
myBird: { model: 'bird' }
|
|
449
|
-
},
|
|
450
|
-
deepModel2: {
|
|
451
|
-
myBird: { model: 'bird' }
|
|
452
|
-
},
|
|
453
|
-
hiddenDog: { type: 'string' },
|
|
454
|
-
hiddenPets: [{
|
|
455
|
-
name: { type: 'string'}
|
|
456
|
-
}],
|
|
457
|
-
hiddenList: [{ type: 'number'}],
|
|
458
|
-
hiddenDeeper: {
|
|
459
|
-
deeper2: {
|
|
460
|
-
deeper3: {
|
|
461
|
-
deeper4: { type: 'string' }
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
hiddenDeepModel: {
|
|
466
|
-
myBird: { model: 'bird' }
|
|
467
|
-
},
|
|
468
|
-
hiddenDeepModels: [{ model: 'bird' }]
|
|
469
|
-
}
|
|
470
|
-
})
|
|
471
|
-
|
|
472
|
-
// Test find whitelist
|
|
473
|
-
// expect(user.fieldlist.filter(o => !user.findBL.includes(o))).toEqual([
|
|
474
|
-
// 'list',
|
|
475
|
-
// 'pet',
|
|
476
|
-
// 'anyPet',
|
|
477
|
-
// 'pets.name',
|
|
478
|
-
// 'animals.dog',
|
|
479
|
-
// 'deepModel.myBird',
|
|
480
|
-
// 'deepModel2.myBird'
|
|
481
|
-
// ])
|
|
482
|
-
|
|
483
|
-
// Test findBLProject
|
|
484
|
-
expect(user.findBLProject).toEqual({
|
|
485
|
-
'pets.hiddenAge': 0,
|
|
486
|
-
'animals.hiddenCat': 0,
|
|
487
|
-
'hiddenDog': 0,
|
|
488
|
-
'hiddenPets': 0,
|
|
489
|
-
'hiddenList': 0,
|
|
490
|
-
'deep.deep2.hiddenDeep3': 0,
|
|
491
|
-
'hiddenDeeper': 0,
|
|
492
|
-
'hiddenDeepModel': 0,
|
|
493
|
-
'hiddenDeepModels': 0
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
// Test inclusion of deep model blacklists
|
|
497
|
-
var findBLProject = user._addDeepBlacklists(user.findBLProject, [
|
|
498
|
-
'deepModel.myBird',
|
|
499
|
-
'hiddenDeepModel.myBird',
|
|
500
|
-
'hiddenDeepModels',
|
|
501
|
-
{
|
|
502
|
-
// raw $lookup object
|
|
503
|
-
as: 'deepModel2.myBird'
|
|
504
|
-
}
|
|
505
|
-
])
|
|
506
|
-
expect(findBLProject).toEqual({
|
|
507
|
-
'pets.hiddenAge': 0,
|
|
508
|
-
'animals.hiddenCat': 0,
|
|
509
|
-
'hiddenDog': 0,
|
|
510
|
-
'hiddenPets': 0,
|
|
511
|
-
'hiddenList': 0,
|
|
512
|
-
'deep.deep2.hiddenDeep3': 0,
|
|
513
|
-
'hiddenDeeper': 0,
|
|
514
|
-
'hiddenDeepModel': 0,
|
|
515
|
-
'hiddenDeepModels': 0,
|
|
516
|
-
// Deep model blacklists
|
|
517
|
-
'deepModel.myBird.password': 0,
|
|
518
|
-
'deepModel2.myBird.password': 0
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
// Test whitelisting
|
|
522
|
-
expect(user._addBlacklist(findBLProject, ['-pets', '-deep.deep2.hiddenDeep3'])).toEqual({
|
|
523
|
-
// 'pets.hiddenAge': 0,
|
|
524
|
-
// 'deep.deep2.hiddenDeep3': 0,
|
|
525
|
-
'animals.hiddenCat': 0,
|
|
526
|
-
'hiddenDog': 0,
|
|
527
|
-
'hiddenPets': 0,
|
|
528
|
-
'hiddenList': 0,
|
|
529
|
-
'hiddenDeeper': 0,
|
|
530
|
-
'hiddenDeepModel': 0,
|
|
531
|
-
'hiddenDeepModels': 0,
|
|
532
|
-
// Deep model blacklists
|
|
533
|
-
'deepModel.myBird.password': 0,
|
|
534
|
-
'deepModel2.myBird.password': 0
|
|
535
|
-
})
|
|
536
|
-
|
|
537
|
-
// Test aggregate with projection exclusions. This is mainly to test how $lookup reacts
|
|
538
|
-
let bird1 = await db.bird.insert({ data: { name: 'bird1' } })
|
|
539
|
-
let bird2 = await db.bird.insert({ data: { name: 'bird2' } })
|
|
540
|
-
let user1 = await db.user.insert({ data: {
|
|
541
|
-
pet: 'carla',
|
|
542
|
-
pets: [{ name: 'carla', hiddenAge: 12 }, { name: 'sparky', hiddenAge: 14 }],
|
|
543
|
-
anyPet: { hi: 1234 },
|
|
544
|
-
hiddenDeepModel: { myBird: bird1._id },
|
|
545
|
-
hiddenDeepModels: [bird1._id, bird2._id]
|
|
546
|
-
}})
|
|
547
|
-
let res = await db.user._aggregate([
|
|
548
|
-
{ $match: { _id: user1._id } },
|
|
549
|
-
{ $lookup: {
|
|
550
|
-
from: 'bird',
|
|
551
|
-
localField: 'hiddenDeepModel.myBird',
|
|
552
|
-
foreignField: '_id',
|
|
553
|
-
as: 'hiddenDeepModel.myBird'
|
|
554
|
-
}},
|
|
555
|
-
{ $lookup: {
|
|
556
|
-
from: 'bird',
|
|
557
|
-
localField: 'hiddenDeepModels',
|
|
558
|
-
foreignField: '_id',
|
|
559
|
-
as: 'hiddenDeepModels'
|
|
560
|
-
}},
|
|
561
|
-
{ $project: {
|
|
562
|
-
'anyPet': 0,
|
|
563
|
-
'pets.hiddenAge': 0,
|
|
564
|
-
'hiddenDeepModel.myBird': 0,
|
|
565
|
-
'hiddenDeepModels.name': 0,
|
|
566
|
-
}}
|
|
567
|
-
])
|
|
568
|
-
expect(res[0]).toEqual({
|
|
569
|
-
_id: user1._id,
|
|
570
|
-
pet: 'carla',
|
|
571
|
-
pets: [ { name: 'carla' }, { name: 'sparky' } ],
|
|
572
|
-
hiddenDeepModel: {},
|
|
573
|
-
hiddenDeepModels: [ { _id: bird1._id }, { _id: bird2._id } ]
|
|
574
|
-
})
|
|
575
|
-
|
|
576
|
-
db.close()
|
|
577
|
-
})
|
|
578
|
-
|
|
579
414
|
}
|
package/test/util.js
CHANGED
|
@@ -39,4 +39,11 @@ module.exports = function(monastery, opendb) {
|
|
|
39
39
|
expect(db.isId('5ff50fe955da2c00170de734')).toEqual(true)
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
+
test('utilities arrayWithSchema', async () => {
|
|
43
|
+
let db = (await opendb(false)).db
|
|
44
|
+
let res = db.arrayWithSchema([{ name: { type: 'string' }}], { minLength: 1 })
|
|
45
|
+
expect(res).toContainEqual({ name: { type: 'string' }})
|
|
46
|
+
expect(res.schema).toEqual({ minLength: 1 })
|
|
47
|
+
})
|
|
48
|
+
|
|
42
49
|
}
|
package/test/validate.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Todo: split out basic 'type' tests
|
|
2
|
+
|
|
1
3
|
let validate = require('../lib/model-validate')
|
|
2
4
|
|
|
3
5
|
module.exports = function(monastery, opendb) {
|
|
@@ -51,21 +53,55 @@ module.exports = function(monastery, opendb) {
|
|
|
51
53
|
meta: { rule: 'isString', model: 'user', field: 'name' }
|
|
52
54
|
})
|
|
53
55
|
|
|
56
|
+
|
|
54
57
|
// Type error (date)
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
let userdate = db.model('userdate', { fields: { amount: { type: 'date', required: true }}})
|
|
59
|
+
let userdate2 = db.model('userdate2', { fields: { amount: { type: 'date' }}})
|
|
60
|
+
await expect(userdate.validate({ amount: 0 })).resolves.toEqual({ amount: 0 })
|
|
61
|
+
await expect(userdate.validate({ amount: '0' })).resolves.toEqual({ amount: 0 })
|
|
62
|
+
await expect(userdate.validate({ amount: '1646778655000' })).resolves.toEqual({ amount: 1646778655000 })
|
|
63
|
+
await expect(userdate2.validate({ amount: '' })).resolves.toEqual({ amount: null })
|
|
64
|
+
await expect(userdate2.validate({ amount: null })).resolves.toEqual({ amount: null })
|
|
65
|
+
await expect(userdate.validate({ amount: 'badnum' })).rejects.toEqual([{
|
|
57
66
|
status: '400',
|
|
58
|
-
title: '
|
|
67
|
+
title: 'amount',
|
|
59
68
|
detail: 'Value was not a unix timestamp.',
|
|
60
|
-
meta: { rule: 'isDate', model: '
|
|
61
|
-
})
|
|
69
|
+
meta: { rule: 'isDate', model: 'userdate', field: 'amount' }
|
|
70
|
+
}])
|
|
71
|
+
await expect(userdate.validate({ amount: false })).rejects.toEqual([{
|
|
72
|
+
status: '400',
|
|
73
|
+
title: 'amount',
|
|
74
|
+
detail: 'Value was not a unix timestamp.',
|
|
75
|
+
meta: { rule: 'isDate', model: 'userdate', field: 'amount' }
|
|
76
|
+
}])
|
|
77
|
+
await expect(userdate.validate({ amount: undefined })).rejects.toEqual([{
|
|
78
|
+
status: '400',
|
|
79
|
+
title: 'amount',
|
|
80
|
+
detail: 'This field is required.',
|
|
81
|
+
meta: { rule: 'required', model: 'userdate', field: 'amount' },
|
|
82
|
+
}])
|
|
83
|
+
await expect(userdate.validate({ amount: null })).rejects.toEqual([{
|
|
84
|
+
status: '400',
|
|
85
|
+
title: 'amount',
|
|
86
|
+
detail: 'This field is required.',
|
|
87
|
+
meta: { rule: 'required', model: 'userdate', field: 'amount' },
|
|
88
|
+
}])
|
|
89
|
+
|
|
62
90
|
|
|
63
91
|
// Type error (number)
|
|
64
92
|
let usernum = db.model('usernum', { fields: { amount: { type: 'number', required: true }}})
|
|
65
93
|
let usernum2 = db.model('usernum2', { fields: { amount: { type: 'number' }}})
|
|
66
94
|
await expect(usernum.validate({ amount: 0 })).resolves.toEqual({ amount: 0 })
|
|
67
95
|
await expect(usernum.validate({ amount: '0' })).resolves.toEqual({ amount: 0 })
|
|
96
|
+
await expect(usernum.validate({ amount: '1646778655000' })).resolves.toEqual({ amount: 1646778655000 })
|
|
68
97
|
await expect(usernum2.validate({ amount: '' })).resolves.toEqual({ amount: null })
|
|
98
|
+
await expect(usernum2.validate({ amount: null })).resolves.toEqual({ amount: null })
|
|
99
|
+
await expect(usernum.validate({ amount: 'badnum' })).rejects.toEqual([{
|
|
100
|
+
status: '400',
|
|
101
|
+
title: 'amount',
|
|
102
|
+
detail: 'Value was not a number.',
|
|
103
|
+
meta: { rule: 'isNumber', model: 'usernum', field: 'amount' }
|
|
104
|
+
}])
|
|
69
105
|
await expect(usernum.validate({ amount: false })).rejects.toEqual([{
|
|
70
106
|
status: '400',
|
|
71
107
|
title: 'amount',
|