monastery 1.42.2 → 2.1.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 +4 -0
- package/docs/definition/index.md +2 -2
- package/docs/image-plugin.md +1 -0
- package/docs/readme.md +1 -0
- package/lib/model-validate.js +8 -19
- package/lib/model.js +37 -1
- package/package.json +15 -9
- package/plugins/images/index.js +27 -15
- package/test/model.js +128 -0
- package/test/plugin-images.js +1 -0
- package/test/validate.js +59 -49
package/changelog.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
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
|
+
## [2.1.0](https://github.com/boycce/monastery/compare/1.42.2...2.1.0) (2024-04-05)
|
|
6
|
+
|
|
7
|
+
## [2.0.0](https://github.com/boycce/monastery/compare/1.42.2...2.0.0) (2023-12-06)
|
|
8
|
+
|
|
5
9
|
### [1.42.2](https://github.com/boycce/monastery/compare/1.42.1...1.42.2) (2023-10-31)
|
|
6
10
|
|
|
7
11
|
### [1.42.1](https://github.com/boycce/monastery/compare/1.42.0...1.42.1) (2023-10-09)
|
package/docs/definition/index.md
CHANGED
|
@@ -283,12 +283,12 @@ You are able to define custom error messages for each field rule.
|
|
|
283
283
|
}
|
|
284
284
|
},
|
|
285
285
|
// Assign custom error messages for arrays
|
|
286
|
+
// e.g. pets = [{ name: { type: 'string' }}]
|
|
286
287
|
'pets': {
|
|
287
288
|
minLength: `Please add at least one pet pet group.`
|
|
288
289
|
},
|
|
289
290
|
// You can assign custom error messages for all fields on embedded documents in an array
|
|
290
|
-
|
|
291
|
-
'pets.name': {
|
|
291
|
+
'pets.$.name': {
|
|
292
292
|
required: `Your pet's name needs to be a string.`
|
|
293
293
|
},
|
|
294
294
|
// To target a specific array item
|
package/docs/image-plugin.md
CHANGED
|
@@ -12,6 +12,7 @@ To use the default image plugin shipped with monastery, you need to use the opti
|
|
|
12
12
|
imagePlugin: {
|
|
13
13
|
awsAcl: 'public-read', // default
|
|
14
14
|
awsBucket: 'your-bucket-name',
|
|
15
|
+
awsRegion: undefined, // required when using getSignedUrl (e.g. 's3-ap-southeast-2')
|
|
15
16
|
awsAccessKeyId: 'your-key-here',
|
|
16
17
|
awsSecretAccessKey: 'your-key-here',
|
|
17
18
|
filesize: undefined, // default (max filesize in bytes)
|
package/docs/readme.md
CHANGED
|
@@ -105,6 +105,7 @@ Coming soon...
|
|
|
105
105
|
- Remove leading forward slashes from custom image paths (AWS adds this as a seperate folder)
|
|
106
106
|
- double check await db.model.remove({ query: idfromparam }) doesnt cause issues for null, undefined or '', but continue to allow {}
|
|
107
107
|
- ~~can't insert/update model id (maybe we can allow this and add _id to default insert/update blacklists)~~
|
|
108
|
+
- timstamps are blacklisted by default (instead of the `timestamps` opt), and can be switched off via blacklisting
|
|
108
109
|
|
|
109
110
|
## Versions
|
|
110
111
|
|
package/lib/model-validate.js
CHANGED
|
@@ -65,29 +65,18 @@ module.exports = {
|
|
|
65
65
|
_getMostSpecificKeyMatchingPath: function(object, path) {
|
|
66
66
|
/**
|
|
67
67
|
* Get all possible array variation matches from the object, and return the most specifc key
|
|
68
|
-
* @param {object} object - e.g. { 'pets.1.name', 'pets.$.name', 'pets.name', .. }
|
|
68
|
+
* @param {object} object - messages, e.g. { 'pets.1.name', 'pets.$.name', 'pets.name', .. }
|
|
69
69
|
* @path {string} path - must be a specifc path, e.g. 'pets.1.name'
|
|
70
70
|
* @return most specific key in object
|
|
71
|
-
*
|
|
72
|
-
* 1. Get all viable messages keys, e.g. (key)dogs.$ == (path)dogs.1
|
|
73
|
-
* 2. Order array key list by scoring, i.e. [0-9]=2, $=1, ''=0
|
|
74
|
-
* 3. Return first
|
|
75
71
|
*/
|
|
76
|
-
let
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (key.match(pathreg)) {
|
|
82
|
-
let score = (key.match(/\.[0-9]+/g)||[]).length * 1001
|
|
83
|
-
score += (key.match(/\.\$/g)||[]).length * 1000
|
|
84
|
-
keys.push({ score: score, key: key })
|
|
72
|
+
let key
|
|
73
|
+
for (let k in object) {
|
|
74
|
+
if (path.match(object[k].regex)) {
|
|
75
|
+
key = k
|
|
76
|
+
break
|
|
85
77
|
}
|
|
86
78
|
}
|
|
87
|
-
|
|
88
|
-
if (!keys.length) return
|
|
89
|
-
else if (keys.length == 1) return keys[0].key
|
|
90
|
-
return keys.sort((a, b) => a.score - b.score).reverse()[0].key // descending
|
|
79
|
+
return key
|
|
91
80
|
},
|
|
92
81
|
|
|
93
82
|
_validateFields: function(dataRoot, fields, data, opts, path) {
|
|
@@ -236,7 +225,7 @@ module.exports = {
|
|
|
236
225
|
let rule = this.rules[ruleName] || rules[ruleName]
|
|
237
226
|
let fieldName = path.match(/[^.]+$/)[0]
|
|
238
227
|
let isDeepProp = path.match(/\./) // todo: not dot-notation
|
|
239
|
-
let ruleMessageKey = this._getMostSpecificKeyMatchingPath(this.messages, path)
|
|
228
|
+
let ruleMessageKey = this.messagesLen && this._getMostSpecificKeyMatchingPath(this.messages, path)
|
|
240
229
|
let ruleMessage = ruleMessageKey && this.messages[ruleMessageKey][ruleName]
|
|
241
230
|
let validateUndefined = util.isDefined(opts.validateUndefined) ? opts.validateUndefined : opts.insert || isDeepProp
|
|
242
231
|
if (!ruleMessage) ruleMessage = rule.message
|
package/lib/model.js
CHANGED
|
@@ -43,14 +43,50 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
43
43
|
? !opts.insertBL.includes('_id') && !opts.insertBL.includes('-_id') ? ['_id'].concat(opts.insertBL) : opts.insertBL
|
|
44
44
|
: ['_id'],
|
|
45
45
|
fields: { ...(util.deepCopy(opts.fields) || {}) },
|
|
46
|
-
findBL: opts.findBL || ['password'],
|
|
46
|
+
findBL: opts.findBL || ['password'], // todo: password should be removed
|
|
47
47
|
manager: manager,
|
|
48
48
|
messages: opts.messages || {},
|
|
49
|
+
messagesLen: Object.keys(opts.messages || {}).length > 0,
|
|
49
50
|
name: name,
|
|
50
51
|
rules: { ...(opts.rules || {}) },
|
|
51
52
|
updateBL: opts.updateBL || [],
|
|
52
53
|
})
|
|
53
54
|
|
|
55
|
+
// Sort messages by specifity first, then we can just return the first match
|
|
56
|
+
this.messages = Object
|
|
57
|
+
.keys(this.messages)
|
|
58
|
+
.sort((a, b) => {
|
|
59
|
+
function getScore(key) {
|
|
60
|
+
// Make sure the keys are sorted by specifity, e.g. the most specific keys are at the top
|
|
61
|
+
// That means the variable indexes need to be sorted last,
|
|
62
|
+
// e.g. 'gulls.1.name' is more specific than 'gulls.$.name'
|
|
63
|
+
// e.g. 'gulls.1.name' is more specific than 'gulls.1.$'
|
|
64
|
+
// e.g. 'gulls.1.$' is more specific than 'gulls.$.1'
|
|
65
|
+
// e.g. 'gulls.1.1.$' is more specific than 'gulls.$.1.1'
|
|
66
|
+
if (!key.match(/\.\$/)) return 0
|
|
67
|
+
let score = 0
|
|
68
|
+
let parts = key.split('.')
|
|
69
|
+
for (let i = 0; i < parts.length; i++) {
|
|
70
|
+
if (parts[i] == '$') score += 100 * (100 - i) // higher score is less specific
|
|
71
|
+
}
|
|
72
|
+
return score
|
|
73
|
+
}
|
|
74
|
+
const scoreA = getScore(a)
|
|
75
|
+
const scoreB = getScore(b)
|
|
76
|
+
// this.messages[a].score = scoreA
|
|
77
|
+
// this.messages[b].score = scoreB
|
|
78
|
+
return scoreA > scoreB ? 1 : (scoreA < scoreB ? -1 : 0)
|
|
79
|
+
})
|
|
80
|
+
.reduce((acc, key) => {
|
|
81
|
+
// Now covert the path to a regex
|
|
82
|
+
// e.g. pets.$.names.4.first => pets\.[0-9]+\.names\.4\.first
|
|
83
|
+
this.messages[key].regex = new RegExp(`^${key.replace(/\./g, '\\.').replace(/\.\$/g, '.[0-9]+')}$`)
|
|
84
|
+
// this.messages[key].regex = new RegExp(`^${key.replace(/\.\$/g, '(.[0-9]+|.)').replace(/\./g, '\\.')}$`)
|
|
85
|
+
// return an ordered object
|
|
86
|
+
acc[key] = this.messages[key]
|
|
87
|
+
return acc
|
|
88
|
+
}, {})
|
|
89
|
+
|
|
54
90
|
// Run before model hooks
|
|
55
91
|
for (let hook of this.manager.beforeModel) {
|
|
56
92
|
hook(this)
|
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": "2.1.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
|
@@ -21,30 +21,36 @@
|
|
|
21
21
|
"docs": "cd docs && bundle exec jekyll serve --livereload --livereload-port 4001",
|
|
22
22
|
"lint": "eslint ./lib ./plugins ./test",
|
|
23
23
|
"mong": "nodemon resources/mong.js",
|
|
24
|
+
"major": "standard-version --release-as major && npm publish",
|
|
24
25
|
"minor": "standard-version --release-as minor && npm publish",
|
|
25
26
|
"patch": "standard-version --release-as patch && npm publish",
|
|
26
27
|
"release": "standard-version && npm publish && git push --tags",
|
|
27
28
|
"test": "npm run lint && jest",
|
|
28
|
-
"test-one-example": "jest -t images"
|
|
29
|
+
"test-one-example": "jest -t \"images addImages\" --watchAll"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"aws-sdk": "
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"@aws-sdk/client-s3": "^3.549.0",
|
|
33
|
+
"@aws-sdk/s3-request-presigner": "^3.549.0",
|
|
34
|
+
"debug": "^4.3.4",
|
|
35
|
+
"file-type": "^16.5.4",
|
|
34
36
|
"monk": "7.3.4",
|
|
35
37
|
"nanoid": "3.2.0",
|
|
36
38
|
"validator": "13.7.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
|
-
"body-parser": "1.
|
|
41
|
+
"body-parser": "^1.20.2",
|
|
40
42
|
"eslint": "8.7.0",
|
|
41
|
-
"express": "4.
|
|
42
|
-
"express-fileupload": "1.
|
|
43
|
+
"express": "^4.19.2",
|
|
44
|
+
"express-fileupload": "^1.5.0",
|
|
43
45
|
"jest": "27.4.7",
|
|
44
|
-
"nodemon": "
|
|
46
|
+
"nodemon": "^3.1.0",
|
|
45
47
|
"standard-version": "9.3.2",
|
|
46
48
|
"supertest": "4.0.2"
|
|
47
49
|
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=14",
|
|
52
|
+
"npm": ">=6"
|
|
53
|
+
},
|
|
48
54
|
"standard-version": {
|
|
49
55
|
"infile": "changelog.md",
|
|
50
56
|
"releaseCommitMessageFormat": "{{currentTag}}",
|
package/plugins/images/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// requiring: nanoid, file-type, aws-sdk/clients/s3
|
|
2
|
-
|
|
2
|
+
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
|
|
3
|
+
const util = require('../../lib/util')
|
|
3
4
|
|
|
4
5
|
let plugin = module.exports = {
|
|
5
6
|
|
|
@@ -26,6 +27,7 @@ let plugin = module.exports = {
|
|
|
26
27
|
this.awsBucket = options.awsBucket
|
|
27
28
|
this.awsAccessKeyId = options.awsAccessKeyId
|
|
28
29
|
this.awsSecretAccessKey = options.awsSecretAccessKey
|
|
30
|
+
this.awsRegion = options.awsRegion
|
|
29
31
|
this.bucketDir = options.bucketDir || 'full' // depreciated > 1.36.2
|
|
30
32
|
this.filesize = options.filesize
|
|
31
33
|
this.formats = options.formats || ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff']
|
|
@@ -42,10 +44,14 @@ let plugin = module.exports = {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// Create s3 'service' instance (defer require since it takes 120ms to load)
|
|
45
|
-
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
|
|
47
|
+
// v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
|
|
48
|
+
// v3: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/
|
|
49
|
+
// v3 examples: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples.html
|
|
46
50
|
manager._getSignedUrl = this._getSignedUrl
|
|
47
|
-
this.
|
|
48
|
-
|
|
51
|
+
this.getS3Client = () => {
|
|
52
|
+
const { S3 } = require('@aws-sdk/client-s3')
|
|
53
|
+
return this._s3Client || (this._s3Client = new S3({
|
|
54
|
+
region: this.awsRegion,
|
|
49
55
|
credentials: {
|
|
50
56
|
accessKeyId: this.awsAccessKeyId,
|
|
51
57
|
secretAccessKey: this.awsSecretAccessKey
|
|
@@ -168,7 +174,7 @@ let plugin = module.exports = {
|
|
|
168
174
|
plugin._addImageObjectsToData(filesArr.inputPath, data, image)
|
|
169
175
|
resolve(s3Options)
|
|
170
176
|
} else {
|
|
171
|
-
plugin.
|
|
177
|
+
plugin.getS3Client().upload(s3Options, (err, response) => {
|
|
172
178
|
if (err) return reject(err)
|
|
173
179
|
plugin._addImageObjectsToData(filesArr.inputPath, data, image)
|
|
174
180
|
resolve(s3Options)
|
|
@@ -203,7 +209,7 @@ let plugin = module.exports = {
|
|
|
203
209
|
* Get signed urls for all image objects in data
|
|
204
210
|
* @param {object} options - monastery operation options {model, query, files, ..}
|
|
205
211
|
* @param {object} data
|
|
206
|
-
* @return promise(data
|
|
212
|
+
* @return promise() - mutates data
|
|
207
213
|
* @this model
|
|
208
214
|
*/
|
|
209
215
|
// Not wanting signed urls for this operation?
|
|
@@ -215,8 +221,9 @@ let plugin = module.exports = {
|
|
|
215
221
|
if (options.getSignedUrls
|
|
216
222
|
|| (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : plugin.getSignedUrl)) {
|
|
217
223
|
let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
|
|
224
|
+
// todo: we could do this in parallel
|
|
218
225
|
for (let image of images) {
|
|
219
|
-
image.image.signedUrl = plugin._getSignedUrl(image.image.path, 3600, imageField.awsBucket)
|
|
226
|
+
image.image.signedUrl = await plugin._getSignedUrl(image.image.path, 3600, imageField.awsBucket)
|
|
220
227
|
}
|
|
221
228
|
}
|
|
222
229
|
}
|
|
@@ -365,7 +372,7 @@ let plugin = module.exports = {
|
|
|
365
372
|
// the file doesnt get deleted, we only delete from plugin.awsBucket.
|
|
366
373
|
if (!unused.length) return
|
|
367
374
|
await new Promise((resolve, reject) => {
|
|
368
|
-
plugin.
|
|
375
|
+
plugin.getS3Client().deleteObjects({
|
|
369
376
|
Bucket: plugin.awsBucket,
|
|
370
377
|
Delete: { Objects: unused }
|
|
371
378
|
}, (err, data) => {
|
|
@@ -571,18 +578,23 @@ let plugin = module.exports = {
|
|
|
571
578
|
return list
|
|
572
579
|
},
|
|
573
580
|
|
|
574
|
-
_getSignedUrl: (path, expires=3600, bucket) => {
|
|
581
|
+
_getSignedUrl: async (path, expires=3600, bucket) => {
|
|
575
582
|
/**
|
|
576
583
|
* @param {string} path - aws file path
|
|
577
584
|
* @param {number} <expires> - seconds
|
|
578
585
|
* @param {number} <bucket>
|
|
579
|
-
* @
|
|
586
|
+
* @return {promise} signedUrl
|
|
587
|
+
* @see v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
|
|
588
|
+
* @see v3: https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-presigned-url
|
|
580
589
|
*/
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
590
|
+
if (!plugin.awsRegion) {
|
|
591
|
+
throw 'Monastery requires config.awsRegion to be defined when using getSignedUrl\'s'
|
|
592
|
+
}
|
|
593
|
+
const { GetObjectCommand } = require('@aws-sdk/client-s3')
|
|
594
|
+
const params = { Bucket: bucket || plugin.awsBucket, Key: path }
|
|
595
|
+
const command = new GetObjectCommand(params)
|
|
596
|
+
let signedUrl = await getSignedUrl(plugin.getS3Client(), command, { expiresIn: expires })
|
|
597
|
+
// console.log(signedUrl)
|
|
586
598
|
return signedUrl
|
|
587
599
|
},
|
|
588
600
|
|
package/test/model.js
CHANGED
|
@@ -170,6 +170,134 @@ module.exports = function(monastery, opendb) {
|
|
|
170
170
|
})
|
|
171
171
|
})
|
|
172
172
|
|
|
173
|
+
test('model setup with messages', async () => {
|
|
174
|
+
// Setup
|
|
175
|
+
let db = (await opendb(false)).db
|
|
176
|
+
let user = db.model('user', {
|
|
177
|
+
fields: {
|
|
178
|
+
name: { type: 'string' },
|
|
179
|
+
},
|
|
180
|
+
messages: {
|
|
181
|
+
// these are sorted when trhe model's initialised
|
|
182
|
+
'cats.name': {},
|
|
183
|
+
|
|
184
|
+
'dogs.name': {},
|
|
185
|
+
'dogs.$.name': {},
|
|
186
|
+
'dogs.1.name': {},
|
|
187
|
+
'dogs.$': {},
|
|
188
|
+
'dogs.1': {},
|
|
189
|
+
|
|
190
|
+
'pigs.name': {},
|
|
191
|
+
'pigs.$.name': {},
|
|
192
|
+
'pigs.1.name': {},
|
|
193
|
+
'pigs.2.name': {},
|
|
194
|
+
|
|
195
|
+
'gulls.$.1.$': {},
|
|
196
|
+
'gulls.1.$.1': {},
|
|
197
|
+
'gulls.$': {},
|
|
198
|
+
'gulls.$.$': {},
|
|
199
|
+
'gulls.$.$.1': {},
|
|
200
|
+
'gulls.$.1': {},
|
|
201
|
+
'gulls.1.$': {},
|
|
202
|
+
'gulls.1.1': {},
|
|
203
|
+
'gulls.1.1.$': {},
|
|
204
|
+
'gulls.name': {},
|
|
205
|
+
'gulls.$.name': {},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
// Object with schema
|
|
209
|
+
// console.log(user.messages)
|
|
210
|
+
expect(Object.keys(user.messages)).toEqual([
|
|
211
|
+
'cats.name',
|
|
212
|
+
'dogs.name',
|
|
213
|
+
'dogs.1.name',
|
|
214
|
+
'dogs.1',
|
|
215
|
+
'pigs.name',
|
|
216
|
+
'pigs.1.name',
|
|
217
|
+
'pigs.2.name',
|
|
218
|
+
'gulls.1.1',
|
|
219
|
+
'gulls.name',
|
|
220
|
+
'gulls.1.1.$',
|
|
221
|
+
'gulls.1.$.1',
|
|
222
|
+
'gulls.1.$',
|
|
223
|
+
'dogs.$.name',
|
|
224
|
+
'dogs.$',
|
|
225
|
+
'pigs.$.name',
|
|
226
|
+
'gulls.$',
|
|
227
|
+
'gulls.$.1',
|
|
228
|
+
'gulls.$.name',
|
|
229
|
+
'gulls.$.1.$',
|
|
230
|
+
'gulls.$.$',
|
|
231
|
+
'gulls.$.$.1',
|
|
232
|
+
])
|
|
233
|
+
|
|
234
|
+
expect(user.messages).toEqual({
|
|
235
|
+
// these are sorted in model initialisation
|
|
236
|
+
'cats.name': {
|
|
237
|
+
'regex': /^cats\.name$/,
|
|
238
|
+
},
|
|
239
|
+
'dogs.$': {
|
|
240
|
+
'regex': /^dogs\.[0-9]+$/,
|
|
241
|
+
},
|
|
242
|
+
'dogs.$.name': {
|
|
243
|
+
'regex': /^dogs\.[0-9]+\.name$/,
|
|
244
|
+
},
|
|
245
|
+
'dogs.1': {
|
|
246
|
+
'regex': /^dogs\.1$/,
|
|
247
|
+
},
|
|
248
|
+
'dogs.1.name': {
|
|
249
|
+
'regex': /^dogs\.1\.name$/,
|
|
250
|
+
},
|
|
251
|
+
'dogs.name': {
|
|
252
|
+
'regex': /^dogs\.name$/,
|
|
253
|
+
},
|
|
254
|
+
'gulls.$': {
|
|
255
|
+
'regex': /^gulls\.[0-9]+$/,
|
|
256
|
+
},
|
|
257
|
+
'gulls.$.$': {
|
|
258
|
+
'regex': /^gulls\.[0-9]+\.[0-9]+$/,
|
|
259
|
+
},
|
|
260
|
+
'gulls.$.$.1': {
|
|
261
|
+
'regex': /^gulls\.[0-9]+\.[0-9]+\.1$/,
|
|
262
|
+
},
|
|
263
|
+
'gulls.$.1': {
|
|
264
|
+
'regex': /^gulls\.[0-9]+\.1$/,
|
|
265
|
+
},
|
|
266
|
+
'gulls.$.1.$': {
|
|
267
|
+
'regex': /^gulls\.[0-9]+\.1\.[0-9]+$/,
|
|
268
|
+
},
|
|
269
|
+
'gulls.$.name': {
|
|
270
|
+
'regex': /^gulls\.[0-9]+\.name$/,
|
|
271
|
+
},
|
|
272
|
+
'gulls.1.$': {
|
|
273
|
+
'regex': /^gulls\.1\.[0-9]+$/,
|
|
274
|
+
},
|
|
275
|
+
'gulls.1.$.1': {
|
|
276
|
+
'regex': /^gulls\.1\.[0-9]+\.1$/,
|
|
277
|
+
},
|
|
278
|
+
'gulls.1.1': {
|
|
279
|
+
'regex': /^gulls\.1\.1$/,
|
|
280
|
+
},
|
|
281
|
+
'gulls.1.1.$': {
|
|
282
|
+
'regex': /^gulls\.1\.1\.[0-9]+$/,
|
|
283
|
+
},
|
|
284
|
+
'gulls.name': {
|
|
285
|
+
'regex': /^gulls\.name$/,
|
|
286
|
+
},
|
|
287
|
+
'pigs.$.name': {
|
|
288
|
+
'regex': /^pigs\.[0-9]+\.name$/,
|
|
289
|
+
},
|
|
290
|
+
'pigs.1.name': {
|
|
291
|
+
'regex': /^pigs\.1\.name$/,
|
|
292
|
+
},
|
|
293
|
+
'pigs.2.name': {
|
|
294
|
+
'regex': /^pigs\.2\.name$/,
|
|
295
|
+
},
|
|
296
|
+
'pigs.name': {
|
|
297
|
+
'regex': /^pigs\.name$/,
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
}),
|
|
173
301
|
|
|
174
302
|
test('model reserved rules', async () => {
|
|
175
303
|
// Setup
|
package/test/plugin-images.js
CHANGED
package/test/validate.js
CHANGED
|
@@ -381,35 +381,43 @@ module.exports = function(monastery, opendb) {
|
|
|
381
381
|
})
|
|
382
382
|
|
|
383
383
|
test('validation getMostSpecificKeyMatchingPath', async () => {
|
|
384
|
-
let
|
|
385
|
-
let
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
384
|
+
let db = (await opendb(false)).db
|
|
385
|
+
let user = db.model('user', {
|
|
386
|
+
fields: {
|
|
387
|
+
name: { type: 'string' },
|
|
388
|
+
},
|
|
389
|
+
messages: {
|
|
390
|
+
// these are sorted when trhe model's initialised
|
|
391
|
+
'cats.name': {},
|
|
392
|
+
|
|
393
|
+
'dogs.name': {},
|
|
394
|
+
'dogs.$.name': {},
|
|
395
|
+
|
|
396
|
+
'pigs.name': {},
|
|
397
|
+
'pigs.$.name': {},
|
|
398
|
+
'pigs.1.name': {},
|
|
399
|
+
'pigs.2.name': {},
|
|
400
|
+
|
|
401
|
+
'gulls.$': {},
|
|
402
|
+
'gulls.$.$': {},
|
|
403
|
+
'gulls.name': {},
|
|
404
|
+
'gulls.$.name': {},
|
|
405
|
+
},
|
|
406
|
+
})
|
|
395
407
|
|
|
396
|
-
|
|
397
|
-
'gulls.$.$': true,
|
|
398
|
-
'gulls.name': true,
|
|
399
|
-
'gulls.$.name': true,
|
|
400
|
-
}
|
|
408
|
+
let fn = validate._getMostSpecificKeyMatchingPath
|
|
401
409
|
// subdocument
|
|
402
|
-
expect(fn(
|
|
410
|
+
expect(fn(user.messages, 'cats.name')).toEqual('cats.name')
|
|
403
411
|
// array subdocuments
|
|
404
|
-
expect(fn(
|
|
405
|
-
expect(fn(
|
|
406
|
-
expect(fn(
|
|
407
|
-
expect(fn(
|
|
408
|
-
expect(fn(
|
|
409
|
-
expect(fn(
|
|
412
|
+
// expect(fn(user.messages, 'cats.1.name')).toEqual('cats.name') // no longer matches
|
|
413
|
+
expect(fn(user.messages, 'dogs.1.name')).toEqual('dogs.$.name')
|
|
414
|
+
expect(fn(user.messages, 'dogs.2.name')).toEqual('dogs.$.name')
|
|
415
|
+
expect(fn(user.messages, 'pigs.1.name')).toEqual('pigs.1.name')
|
|
416
|
+
expect(fn(user.messages, 'pigs.2.name')).toEqual('pigs.2.name')
|
|
417
|
+
expect(fn(user.messages, 'pigs.3.name')).toEqual('pigs.$.name')
|
|
410
418
|
// array
|
|
411
|
-
expect(fn(
|
|
412
|
-
expect(fn(
|
|
419
|
+
expect(fn(user.messages, 'gulls.1.2')).toEqual('gulls.$.$')
|
|
420
|
+
expect(fn(user.messages, 'gulls.1')).toEqual('gulls.$')
|
|
413
421
|
})
|
|
414
422
|
|
|
415
423
|
test('validation default messages', async () => {
|
|
@@ -478,7 +486,7 @@ module.exports = function(monastery, opendb) {
|
|
|
478
486
|
messages: {
|
|
479
487
|
'name': { minLength: 'Oops min length is 4' },
|
|
480
488
|
'dog.name': { minLength: 'Oops min length is 4' },
|
|
481
|
-
'dogNames': { minLength: 'Oops min length is 4' },
|
|
489
|
+
'dogNames.$': { minLength: 'Oops min length is 4' },
|
|
482
490
|
}
|
|
483
491
|
})
|
|
484
492
|
|
|
@@ -519,24 +527,26 @@ module.exports = function(monastery, opendb) {
|
|
|
519
527
|
catNames: [{
|
|
520
528
|
name: { type: 'string', minLength: 4 }
|
|
521
529
|
}],
|
|
522
|
-
pigNames: [
|
|
523
|
-
|
|
524
|
-
|
|
530
|
+
pigNames: [
|
|
531
|
+
[{
|
|
532
|
+
name: { type: 'string', minLength: 4 },
|
|
533
|
+
}]
|
|
534
|
+
],
|
|
525
535
|
},
|
|
526
536
|
messages: {
|
|
527
537
|
'dogNames': { minLength: 'add one dog name' },
|
|
528
538
|
'dogNames.$': { minLength: 'add one sub dog name' },
|
|
529
539
|
|
|
530
|
-
'catNames
|
|
540
|
+
'catNames.$.name': { minLength: 'min length error (name)' },
|
|
531
541
|
'catNames.1.name': { minLength: 'min length error (1)' },
|
|
532
542
|
'catNames.2.name': { minLength: 'min length error (2)' },
|
|
533
543
|
|
|
534
|
-
'pigNames
|
|
535
|
-
'pigNames
|
|
536
|
-
'pigNames
|
|
537
|
-
'pigNames.2
|
|
538
|
-
'pigNames.0.2.name': { minLength: 'min length error (
|
|
539
|
-
'pigNames.$.2.name': { minLength: 'min length error (
|
|
544
|
+
// 'pigNames.$.$.name': { minLength: 'min length error (name)' },
|
|
545
|
+
'pigNames.$.$.name': { minLength: 'min length error ($ $)' }, // catches
|
|
546
|
+
'pigNames.$.1.name': { minLength: 'min length error ($ 1)' },
|
|
547
|
+
'pigNames.2.$.name': { minLength: 'min length error (2 $)' },
|
|
548
|
+
'pigNames.0.2.name': { minLength: 'min length error (0 2)' },
|
|
549
|
+
'pigNames.$.2.name': { minLength: 'min length error ($ 2)' },
|
|
540
550
|
}
|
|
541
551
|
})
|
|
542
552
|
|
|
@@ -587,7 +597,7 @@ module.exports = function(monastery, opendb) {
|
|
|
587
597
|
.rejects.toContainEqual({
|
|
588
598
|
status: '400',
|
|
589
599
|
title: 'pigNames.0.0.name',
|
|
590
|
-
detail: 'min length error ($)',
|
|
600
|
+
detail: 'min length error ($ $)',
|
|
591
601
|
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
592
602
|
})
|
|
593
603
|
// array-subdocument-1-field error
|
|
@@ -595,7 +605,14 @@ module.exports = function(monastery, opendb) {
|
|
|
595
605
|
.rejects.toContainEqual({
|
|
596
606
|
status: '400',
|
|
597
607
|
title: 'pigNames.0.1.name',
|
|
598
|
-
detail: 'min length error (1)',
|
|
608
|
+
detail: 'min length error ($ 1)',
|
|
609
|
+
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
610
|
+
})
|
|
611
|
+
// array-subdocument-2-0-field error (lower fallback)
|
|
612
|
+
await expect(user.validate({ pigNames: [[],[],[{ name: 'ben' }]] })).rejects.toContainEqual({
|
|
613
|
+
status: '400',
|
|
614
|
+
title: 'pigNames.2.0.name',
|
|
615
|
+
detail: 'min length error (2 $)',
|
|
599
616
|
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
600
617
|
})
|
|
601
618
|
// array-subdocument-0-2-field error
|
|
@@ -603,22 +620,15 @@ module.exports = function(monastery, opendb) {
|
|
|
603
620
|
.rejects.toContainEqual({
|
|
604
621
|
status: '400',
|
|
605
622
|
title: 'pigNames.0.2.name',
|
|
606
|
-
detail: 'min length error (
|
|
623
|
+
detail: 'min length error (0 2)',
|
|
607
624
|
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
608
625
|
})
|
|
609
626
|
// array-subdocument-2-0-field error (fallback)
|
|
610
|
-
await expect(user.validate({ pigNames: [[],[
|
|
627
|
+
await expect(user.validate({ pigNames: [[], [{ name: 'carla' },{ name: 'carla' },{ name: 'ben' }], []] }))
|
|
611
628
|
.rejects.toContainEqual({
|
|
612
629
|
status: '400',
|
|
613
|
-
title: 'pigNames.
|
|
614
|
-
detail: 'min length error (
|
|
615
|
-
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
616
|
-
})
|
|
617
|
-
// array-subdocument-2-0-field error (lower fallback)
|
|
618
|
-
await expect(user.validate({ pigNames: [[],[],[{ name: 'ben' }]] })).rejects.toContainEqual({
|
|
619
|
-
status: '400',
|
|
620
|
-
title: 'pigNames.2.0.name',
|
|
621
|
-
detail: 'min length error (2)',
|
|
630
|
+
title: 'pigNames.1.2.name',
|
|
631
|
+
detail: 'min length error ($ 2)',
|
|
622
632
|
meta: { rule: 'minLength', model: 'user', field: 'name' }
|
|
623
633
|
})
|
|
624
634
|
})
|