monastery 1.38.1 → 1.38.2

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 CHANGED
@@ -2,6 +2,13 @@
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.38.2](https://github.com/boycce/monastery/compare/1.38.1...1.38.2) (2022-07-31)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * init load time ([2273f11](https://github.com/boycce/monastery/commit/2273f11a654a9cfe05ee15ed682203a8752237d6))
11
+
5
12
  ### [1.38.1](https://github.com/boycce/monastery/compare/1.38.0...1.38.1) (2022-06-20)
6
13
 
7
14
 
@@ -212,7 +212,10 @@ Since unique indexes by default don't allow multiple documents with `null`, you
212
212
 
213
213
  ### Custom field rules
214
214
 
215
- You are able to define custom field rules to use. (`this` will refer to the data object passed in)
215
+ You are able to define custom field rules to use.
216
+
217
+ - `this` will refer to the data object passed in
218
+ - by default, custom rules will ignore `undefined` values
216
219
 
217
220
  ```js
218
221
  {
@@ -223,6 +226,9 @@ You are able to define custom field rules to use. (`this` will refer to the data
223
226
  },
224
227
  // Full definition
225
228
  isGrandMaster: {
229
+ validateUndefined: false, // default
230
+ validateNull: true, // default
231
+ validateEmptyString: true, // default
226
232
  message: function(value, ruleArgument, path, model) {
227
233
  return 'Only grand masters are permitted'
228
234
  },
@@ -68,3 +68,21 @@ user.update({
68
68
  Due to known limitations, we are not able to verify the contents of non-binary files, only the filename extension (e.g. .txt, .svg) before uploading to S3
69
69
 
70
70
  ...to be continued
71
+
72
+ ### Image sizes
73
+
74
+ I've put together a AWS Lambda function which you can use to generate small/medium/large image sizes automatically for any new files uploaded to your bucket.
75
+ [https://github.com/boycce/s3-lambda-thumbnail-generator](https://github.com/boycce/s3-lambda-thumbnail-generator#install-bucket)
76
+
77
+ You can override the function's default image sizes via the `metadata` option globally in the manager options, or per file:
78
+ ```js
79
+ // Per file
80
+ let user = db.model('user', {
81
+ fields: {
82
+ logo: {
83
+ type: 'image',
84
+ metadata: { small: '*x300', medium: '*x800', large: '*x1200' },
85
+ },
86
+ }
87
+ }
88
+ ```
@@ -74,7 +74,7 @@ user.find({ query: {...}, populate: ['myBook'] })
74
74
  ```
75
75
 
76
76
  You can also populate within embedded document fields. Although at this time arrays are not supported,
77
- you would need to use the [example below](#more-control).
77
+ you would need to use the [example below](#populate-multiple-documents-into-virtual-fields).
78
78
  ```js
79
79
  user.find({ query: {...}, populate: ['myBooks.book'] })
80
80
  ```
@@ -14,7 +14,7 @@ Validate and insert document(s) in a collection and calls model hooks: `beforeIn
14
14
  - `data` *(object\|array)* - Data that is validated against the model fields. Key names can be in dot or bracket notation which is handy for HTML FormData.
15
15
  - [[`blacklist`](#blacklisting)] *(array\|string\|false)*: augment `definition.insertBL`. `false` will remove all blacklisting
16
16
  - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
17
- - [`skipValidation`] (string\|array): skip validation for this field name(s)
17
+ - [`skipValidation`] (string\|array\|boolean): skip validation for these field name(s), or `true` for all fields
18
18
  - [`timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are automatically inserted, defaults to `manager.timestamps`
19
19
  - [[`any mongodb option`](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#insert)] *(any)*
20
20
 
@@ -15,7 +15,7 @@ Update document(s) in a collection and calls model hooks: `beforeUpdate`, `afte
15
15
  - [`data`](#data) *(object)* - data that's validated against the model fields (always wrapped in `{ $set: .. }`)
16
16
  - [[`blacklist`](#blacklisting)]*(array\|string\|false)*: augment `definition.updateBL`. `false` will remove all blacklisting
17
17
  - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
18
- - [`skipValidation`] (string\|array): skip validation for this field name(s)
18
+ - [`skipValidation`] (string\|array\|boolean): skip validation for these field name(s), or `true` for all fields
19
19
  - [`sort`] *(string\|object\|array)*: same as the mongodb option, but allows for string parsing e.g. 'name', 'name:1'
20
20
  - [`timestamps`] *(boolean)*: whether `updatedAt` is automatically updated, defaults to the `manager.timestamps` value
21
21
  - [[`any mongodb option`](http://mongodb.github.io/node-mongodb-native/3.2/api/Collection.html#update)] *(any)*
@@ -14,7 +14,7 @@ Validate a model and calls the model hook: `beforeValidate`
14
14
 
15
15
  [`options`] *(object)*
16
16
 
17
- - [`skipValidation`] (string\|array): skip validation for this field name(s)
17
+ - [`skipValidation`] (string\|array\/boolean): skip validation for thse field name(s), or `true` for all fields
18
18
  - [[`blacklist`](#blacklisting)] *(array\|string\|false)*: augment the model's blacklist. `false` will remove all blacklisting
19
19
  - [`project`] *(string\|array\|object)*: project these fields, ignores blacklisting
20
20
  - [`timestamps`] *(boolean)*: whether `createdAt` and `updatedAt` are inserted, or `updatedAt` is updated, depending on the `update` value. Defaults to the `manager.timestamps` value
package/docs/readme.md CHANGED
@@ -96,9 +96,16 @@ Coming soon...
96
96
  - ~~Ability to change ACL default on the manager~~
97
97
  - ~~Public db.arrayWithSchema method~~
98
98
  - Global after/before hooks
99
+ - before hooks can receive a data array, remove this
99
100
  - docs: Make the implicit ID query conversion more apparent
100
101
  - Split away from Monk (unless updated)
101
102
 
103
+ ## Versions
104
+
105
+ - Monk: `v7.3.4`
106
+ - MongoDB NodeJS driver: `v3.2.3` ([compatibility](https://www.mongodb.com/docs/drivers/node/current/compatibility/#compatibility))
107
+ - MongoDB: [`v4.0.0`](https://www.mongodb.com/docs/v4.2/reference/)
108
+
102
109
  ## Special Thanks
103
110
 
104
111
  [Jerome Gravel-Niquet](https://github.com/jeromegn)
package/lib/rules.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // Todo: remove stringnums in date/number/integer rules
2
2
  let ObjectId = require('mongodb').ObjectId
3
3
  let util = require('./util')
4
- let validator = require('validator')
5
4
 
6
5
  module.exports = {
7
6
 
@@ -175,31 +174,31 @@ module.exports = {
175
174
  },
176
175
  isAfter: {
177
176
  message: (x, arg) => 'Value was before the configured time (' + arg + ')',
178
- fn: function(x, arg) { return validator.isAfter(x, arg) }
177
+ fn: function(x, arg) { return require('validator').isAfter(x, arg) }
179
178
  },
180
179
  isBefore: {
181
180
  message: (x, arg) => 'Value was after the configured time (' + arg + ')',
182
- fn: function(x, arg) { return validator.isBefore(x, arg) }
181
+ fn: function(x, arg) { return require('validator').isBefore(x, arg) }
183
182
  },
184
183
  isCreditCard: {
185
184
  message: 'Value was not a valid credit card.',
186
- fn: function(x, arg) { return validator.isCreditCard(x, arg) }
185
+ fn: function(x, arg) { return require('validator').isCreditCard(x, arg) }
187
186
  },
188
187
  isEmail: {
189
188
  message: 'Please enter a valid email address.',
190
- fn: function(x, arg) { return validator.isEmail(x, arg) }
189
+ fn: function(x, arg) { return require('validator').isEmail(x, arg) }
191
190
  },
192
191
  isHexColor: {
193
192
  message: 'Value was not a valid hex color.',
194
- fn: function(x, arg) { return validator.isHexColor(x, arg) }
193
+ fn: function(x, arg) { return require('validator').isHexColor(x, arg) }
195
194
  },
196
195
  isIn: {
197
196
  message: (x, arg) => 'Value was not in the configured whitelist (' + arg.join(', ') + ')',
198
- fn: function(x, arg) { return validator.isIn(x, arg) }
197
+ fn: function(x, arg) { return require('validator').isIn(x, arg) }
199
198
  },
200
199
  isIP: {
201
200
  message: 'Value was not a valid IP address.',
202
- fn: function(x, arg) { return validator.isIP(x, arg) }
201
+ fn: function(x, arg) { return require('validator').isIP(x, arg) }
203
202
  },
204
203
  isNotEmptyString: {
205
204
  validateEmptyString: true,
@@ -210,15 +209,15 @@ module.exports = {
210
209
  },
211
210
  isNotIn: {
212
211
  message: (x, arg) => 'Value was in the configured blacklist (' + arg.join(', ') + ')',
213
- fn: function(x, arg) { return !validator.isIn(x, arg) }
212
+ fn: function(x, arg) { return !require('validator').isIn(x, arg) }
214
213
  },
215
214
  isURL: {
216
215
  message: 'Value was not a valid URL.',
217
- fn: function(x, arg) { return validator.isURL(x, arg === true? undefined : arg) }
216
+ fn: function(x, arg) { return require('validator').isURL(x, arg === true? undefined : arg) }
218
217
  },
219
218
  isUUID: {
220
219
  message: 'Value was not a valid UUID.',
221
- fn: function(x, arg) { return validator.isUUID(x) }
220
+ fn: function(x, arg) { return require('validator').isUUID(x) }
222
221
  },
223
222
  minLength: {
224
223
  message: function(x, arg) {
@@ -227,7 +226,7 @@ module.exports = {
227
226
  },
228
227
  fn: function(x, arg) {
229
228
  if (typeof x !== 'string' && !util.isArray(x)) throw new Error ('Value was not a string or an array.')
230
- else if (typeof x === 'string') return validator.isLength(x, arg)
229
+ else if (typeof x === 'string') return require('validator').isLength(x, arg)
231
230
  else return x.length >= arg
232
231
  }
233
232
  },
@@ -238,14 +237,14 @@ module.exports = {
238
237
  },
239
238
  fn: function(x, arg) {
240
239
  if (typeof x !== 'string' && !util.isArray(x)) throw new Error ('Value was not a string or an array.')
241
- else if (typeof x === 'string') return validator.isLength(x, 0, arg)
240
+ else if (typeof x === 'string') return require('validator').isLength(x, 0, arg)
242
241
  else return x.length <= arg
243
242
  }
244
243
  },
245
244
  regex: {
246
245
  message: (x, arg) => 'Value did not match the configured regular expression (' + arg + ')',
247
246
  fn: function(x, arg) {
248
- if (util.isRegex(arg)) return validator.matches(x, arg)
247
+ if (util.isRegex(arg)) return require('validator').matches(x, arg)
249
248
  throw new Error('This rule expects a regular expression to be configured, but instead got the '
250
249
  + typeof arg + ' `' + arg + '`.')
251
250
  }
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.38.1",
5
+ "version": "1.38.2",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -1,6 +1,4 @@
1
- let fileType = require('file-type')
2
- let nanoid = require('nanoid')
3
- let S3 = require('aws-sdk/clients/s3')
1
+ // requiring: nanoid, file-type, aws-sdk/clients/s3
4
2
  let util = require('../../lib/util')
5
3
 
6
4
  let plugin = module.exports = {
@@ -9,7 +7,6 @@ let plugin = module.exports = {
9
7
  /**
10
8
  * Monastery plugin that allows models to automatically process and store uploaded images
11
9
  * e.g. fields.avatar: { image: true }
12
- * e.g. fields.photos: [{ image: true, //sizes: { large: [800, 600], .. } (not implemented) }]
13
10
  *
14
11
  * Note we cannot accurately test for non binary file-types, e.g. 'txt', 'csv'
15
12
  *
@@ -44,15 +41,17 @@ let plugin = module.exports = {
44
41
  return
45
42
  }
46
43
 
47
- // Create s3 'service' instance
44
+ // Create s3 'service' instance (defer require since it takes 120ms to load)
48
45
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
49
46
  manager._getSignedUrl = this._getSignedUrl
50
- this.s3 = new S3({
51
- credentials: {
52
- accessKeyId: this.awsAccessKeyId,
53
- secretAccessKey: this.awsSecretAccessKey
54
- }
55
- })
47
+ this.s3 = () => {
48
+ return this._s3 || (this._s3 = new (require('aws-sdk/clients/s3'))({
49
+ credentials: {
50
+ accessKeyId: this.awsAccessKeyId,
51
+ secretAccessKey: this.awsSecretAccessKey
52
+ }
53
+ }))
54
+ }
56
55
 
57
56
  // Add before model hook
58
57
  manager.beforeModel.push(this.setupModel.bind(this))
@@ -141,7 +140,7 @@ let plugin = module.exports = {
141
140
  return Promise.all(files.map(filesArr => {
142
141
  return Promise.all(filesArr.map(file => {
143
142
  return new Promise((resolve, reject) => {
144
- let uid = nanoid.nanoid()
143
+ let uid = require('nanoid').nanoid()
145
144
  let path = filesArr.imageField.path || plugin.path
146
145
  let image = {
147
146
  bucket: filesArr.imageField.awsBucket || plugin.awsBucket,
@@ -169,7 +168,7 @@ let plugin = module.exports = {
169
168
  plugin._addImageObjectsToData(filesArr.inputPath, data, image)
170
169
  resolve(s3Options)
171
170
  } else {
172
- plugin.s3.upload(s3Options, (err, response) => {
171
+ plugin.s3().upload(s3Options, (err, response) => {
173
172
  if (err) return reject(err)
174
173
  plugin._addImageObjectsToData(filesArr.inputPath, data, image)
175
174
  resolve(s3Options)
@@ -365,7 +364,7 @@ let plugin = module.exports = {
365
364
  // the file doesnt get deleted, we only delete from plugin.awsBucket.
366
365
  if (!unused.length) return
367
366
  await new Promise((resolve, reject) => {
368
- plugin.s3.deleteObjects({
367
+ plugin.s3().deleteObjects({
369
368
  Bucket: plugin.awsBucket,
370
369
  Delete: { Objects: unused }
371
370
  }, (err, data) => {
@@ -431,7 +430,7 @@ let plugin = module.exports = {
431
430
  return Promise.all(validFiles.map(filesArr => {
432
431
  return Promise.all(filesArr.map((file, i) => {
433
432
  return new Promise((resolve, reject) => {
434
- fileType.fromBuffer(file.data).then(res => {
433
+ require('file-type').fromBuffer(file.data).then(res => {
435
434
  let filesize = filesArr.imageField.filesize || plugin.filesize
436
435
  let formats = filesArr.imageField.formats || plugin.formats
437
436
  let allowAny = util.inArray(formats, 'any')
@@ -578,7 +577,7 @@ let plugin = module.exports = {
578
577
  * @param {number} <bucket>
579
578
  * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
580
579
  */
581
- let signedUrl = plugin.s3.getSignedUrl('getObject', {
580
+ let signedUrl = plugin.s3().getSignedUrl('getObject', {
582
581
  Bucket: bucket || plugin.awsBucket,
583
582
  Expires: expires,
584
583
  Key: path,
package/test/crud.js CHANGED
@@ -213,6 +213,45 @@ module.exports = function(monastery, opendb) {
213
213
  db.close()
214
214
  })
215
215
 
216
+ test('update general max', async () => {
217
+ let db = (await opendb(null)).db
218
+ let user = db.model('user', {
219
+ fields: {
220
+ name: { type: 'string' },
221
+ active: { type: 'boolean' },
222
+ },
223
+ })
224
+
225
+ // Insert
226
+ let inserted = await user.insert({ data: { name: 'Martin Luther' }})
227
+
228
+ expect(inserted).toEqual({
229
+ _id: expect.any(Object),
230
+ name: 'Martin Luther',
231
+ })
232
+
233
+ let updatedUser1 = await user.update({
234
+ query: { _id: inserted._id },
235
+ data: { name: 'Martin' },
236
+ blacklist: ['active'],
237
+ })
238
+
239
+ expect(updatedUser1).toEqual({ name: 'Martin' })
240
+
241
+ let updatedUser2 = await user._update(
242
+ { _id: db.id(inserted._id) },
243
+ { $set: { name: 'Martin2' }},
244
+ )
245
+
246
+
247
+ expect(updatedUser2).toEqual({ n: 1, nModified: 1, ok: 1 })
248
+
249
+ // Mongo connected?
250
+ // Try native updates
251
+
252
+ db.close()
253
+ })
254
+
216
255
  test('update defaults', async () => {
217
256
  let db = (await opendb(null, { useMilliseconds: true, serverSelectionTimeoutMS: 2000 })).db
218
257
  let user = db.model('user', {
@@ -864,7 +864,7 @@ module.exports = function(monastery, opendb) {
864
864
  awsBucket: 'fake',
865
865
  awsAccessKeyId: 'fake',
866
866
  awsSecretAccessKey: 'fake',
867
- metadata: { small: '*x200' , medium: '*x800', large: '*x1600' },
867
+ metadata: { small: '*x300' , medium: '*x800', large: '*x1200' },
868
868
  params: { ContentLanguage: 'DE'},
869
869
  path: (uid, basename, ext, file) => `images/${basename}`,
870
870
  }
@@ -908,7 +908,7 @@ module.exports = function(monastery, opendb) {
908
908
  date: expect.any(Number),
909
909
  filename: 'logo.png',
910
910
  filesize: expect.any(Number),
911
- metadata: { small: '*x200' , medium: '*x800', large: '*x1600' },
911
+ metadata: { small: '*x300' , medium: '*x800', large: '*x1200' },
912
912
  path: 'images/logo.png',
913
913
  uid: expect.any(String),
914
914
  },
@@ -930,7 +930,7 @@ module.exports = function(monastery, opendb) {
930
930
  Bucket: 'fake',
931
931
  ContentLanguage: 'DE',
932
932
  Key: 'images/logo.png',
933
- Metadata: { small: '*x200' , medium: '*x800', large: '*x1600' },
933
+ Metadata: { small: '*x300' , medium: '*x800', large: '*x1200' },
934
934
  }],
935
935
  [{
936
936
  ACL: 'public-read-write',
package/test/validate.js CHANGED
@@ -816,12 +816,14 @@ module.exports = function(monastery, opendb) {
816
816
  let user = db.model('user', { fields: {
817
817
  name: { type: 'string', minLength: 7 },
818
818
  email: { type: 'string', isEmail: true },
819
- names: { type: 'string', enum: ['Martin', 'Luther'] },
820
819
  amount: { type: 'number' },
821
820
  }})
822
821
  let user2 = db.model('user', { fields: {
823
822
  amount: { type: 'number', required: true },
824
823
  }})
824
+ let user3 = db.model('user', { fields: {
825
+ names: { type: 'string', enum: ['Martin', 'Luther'], default: 'Martin' },
826
+ }})
825
827
 
826
828
  // MinLength
827
829
  await expect(user.validate({ name: 'Martin Luther' })).resolves.toEqual({name: 'Martin Luther'})
@@ -850,8 +852,9 @@ module.exports = function(monastery, opendb) {
850
852
  })
851
853
 
852
854
  // Enum
853
- await expect(user.validate({ names: 'Martin' })).resolves.toEqual({ names: 'Martin' })
854
- await expect(user.validate({ names: 'bad name' })).rejects.toContainEqual({
855
+ await expect(user3.validate({})).resolves.toEqual({ names: 'Martin' })
856
+ await expect(user3.validate({ names: 'Luther' })).resolves.toEqual({ names: 'Luther' })
857
+ await expect(user3.validate({ names: 'bad name' })).rejects.toContainEqual({
855
858
  detail: 'Invalid enum value',
856
859
  status: '400',
857
860
  title: 'names',