monastery 3.4.1 → 3.4.3

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,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
+ ### [3.4.3](https://github.com/boycce/monastery/compare/3.4.2...3.4.3) (2024-08-27)
6
+
7
+ ### [3.4.2](https://github.com/boycce/monastery/compare/3.4.1...3.4.2) (2024-08-24)
8
+
5
9
  ### [3.4.1](https://github.com/boycce/monastery/compare/3.4.0...3.4.1) (2024-08-09)
6
10
 
7
11
  ## [3.4.0](https://github.com/boycce/monastery/compare/3.3.0...3.4.0) (2024-08-09)
@@ -17,13 +17,14 @@ Model definition object.
17
17
  - [Custom field rules](#custom-field-rules)
18
18
  - [Custom error messages](#custom-error-messages)
19
19
  - [Operation hooks](#operation-hooks)
20
+ - [Methods](#methods)
20
21
  - [Full example](#full-example)
21
22
 
22
23
  ### Fields
23
24
 
24
25
  1. Fields can either be a field-type, embedded document, or an array of field-types or embedded documents
25
26
  2. Field-types are recognised by having a `type` property defined as a string
26
- 3. Field-types can contain [custom](#custom-field-rules) and [default field rules](./rules), e.g. `{ minLength: 2 }`
27
+ 3. Field-types can contain [custom](#custom-field-rules) and [default field rules](./field-rules), e.g. `{ minLength: 2 }`
27
28
  4. Field-types can contain [field options](#field-options).
28
29
 
29
30
  ```js
@@ -61,7 +62,7 @@ Model definition object.
61
62
  }
62
63
  ```
63
64
 
64
- The fields below implicitly get assigned and take presidence over any input data when [`manager.timestamps`](./manager) is true (default). You can override the `timestamps` value per operation, e.g. `db.user.update({ ..., timestamps: false})`. These fields use unix timestamps in seconds (by default), but can be configured to use use milliseconds via the manager [`useMilliseconds` ](./manager) option.
65
+ The fields below implicitly get assigned and take presidence over any input data when [`manager.timestamps`](../manager) is true (default). You can override the `timestamps` value per operation, e.g. `db.user.update({ ..., timestamps: false})`. These fields use unix timestamps in seconds (by default), but can be configured to use use milliseconds via the manager [`useMilliseconds` ](../manager) option.
65
66
 
66
67
  ```js
67
68
  {
@@ -307,19 +308,44 @@ You are able provide an array of callbacks to these model operation hooks. If yo
307
308
 
308
309
  ```js
309
310
  {
310
- afterFind: [function(data, next) {}],
311
- afterInsert: [function(data, next) {}],
312
- afterInsertUpdate: [function(data, next) {}],
313
- afterUpdate: [function(data, next) {}],
314
- afterRemove: [function(next) {}],
315
- beforeInsert: [function(data, next) {}],
316
- beforeInsertUpdate: [function(data, next) {}],
317
- beforeUpdate: [function(data, next) {}],
318
- beforeRemove: [function(next) {}],
319
- beforeValidate: [function(data, next) {}],
311
+ // Model hooks
312
+ afterFind: [await function(data) {}],
313
+ afterInsert: [await function(data) {}],
314
+ afterInsertUpdate: [await function(data) {}],
315
+ afterUpdate: [await function(data) {}],
316
+ afterRemove: [await function() {}],
317
+ beforeInsert: [await function(data) {}],
318
+ beforeInsertUpdate: [await function(data) {}],
319
+ beforeUpdate: [await function(data) {}],
320
+ beforeRemove: [await function() {}],
321
+ beforeValidate: [await function(data) {}],
322
+
323
+ // You can also return an object which will be passed to the next hook in the chain, or if it's the last hook, returned as the result:
324
+ afterFind: [await function(data) {
325
+ data.age = 30
326
+ return data
327
+ }],
320
328
  }
321
329
  ```
322
330
 
331
+ ### Methods
332
+
333
+ You are able define reusable methods in the definition which is handy for storing related functions.
334
+
335
+ ```js
336
+ // Method definition
337
+ {
338
+ methods: {
339
+ getAge: function () {
340
+ return 30
341
+ },
342
+ }
343
+ }
344
+
345
+ // Above can be called directly from the model via:
346
+ db.user.getAge()
347
+ ```
348
+
323
349
  ### Definition example
324
350
 
325
351
  ```js
@@ -11,7 +11,9 @@ Sets up a model, retrieves a collection, and sets up any required indexes
11
11
 
12
12
  `name` *(string)*: name of the mongo collection
13
13
 
14
- [`definition`] *(object)*: [definition](../definition)
14
+ `definition` *(object)*: [definition](../definition)
15
+
16
+ `waitForIndexes` *(boolean)*: wait for indexes to be setup (returns a promise instead of a model)
15
17
 
16
18
  ### Returns
17
19
 
@@ -13,7 +13,7 @@ Setup model definitions from a folder location
13
13
 
14
14
  [`options`] *(object)*:
15
15
  - [`commonJs=false`] *(boolean)*: for old commonjs projects, you will need to set this to `true` which uses `require` instead of `import` (removed in `3.0.0`)
16
- - [`waitForIndexes=false`] *(boolean)*: returns a proimse that waits for the Mongo collection indexes to be setup
16
+ - [`waitForIndexes=false`] *(boolean)*: wait for collection indexes to be setup
17
17
 
18
18
  ### Returns
19
19
 
@@ -39,5 +39,5 @@ export default { // Make sure the model definition is exported as the default
39
39
  ```
40
40
 
41
41
  ```js
42
- await db.models(__dirname + "models")
42
+ await db.models(__dirname + 'models', { waitForIndexes: true })
43
43
  ```
package/lib/index.js CHANGED
@@ -211,7 +211,7 @@ Manager.prototype.models = async function(pathname, opts) {
211
211
  if (definition && definition.default) definition = definition.default
212
212
  if (!definition) throw new Error(`The model definition for '${name}' must export a default object`)
213
213
  // Wait for indexes to be created?
214
- if (waitForIndexes) out[name] = await this.model(name, { ...definition, waitForIndexes })
214
+ if (waitForIndexes) out[name] = await this.model(name, definition, waitForIndexes)
215
215
  else out[name] = this.model(name, definition)
216
216
  }
217
217
  return out
@@ -282,7 +282,10 @@ Manager.prototype.parseData = function(obj, parseBracketToDotNotation, parseDotN
282
282
  }
283
283
 
284
284
  Manager.prototype.model = Model
285
- Manager.prototype.getSignedUrl = Manager.prototype._getSignedUrl = imagePluginFile.getSignedUrl
285
+ Manager.prototype.getSignedUrl = imagePluginFile.getSignedUrl
286
+ Manager.prototype._getSignedUrl = () => {
287
+ throw new Error('monastery._getSignedUrl() has been moved to monastery.getSignedUrl()')
288
+ }
286
289
 
287
290
  inherits(Manager, EventEmitter)
288
291
  module.exports = Manager
package/lib/model-crud.js CHANGED
@@ -676,7 +676,7 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
676
676
  (async (_item) => {
677
677
  const _modelName = _item.modelName
678
678
  const _model = models[_modelName]
679
- const _opts = { ...afterFindContext, afterFindName: _modelName }
679
+ const _opts = { ...afterFindContext, model: _model }
680
680
  const _dataRef = _item.dataRefParent[_item.dataRefKey]
681
681
  _item.dataRefParent[_item.dataRefKey] = (
682
682
  await _model._callHooks('afterFind', _dataRef, _opts)
package/lib/model.js CHANGED
@@ -1,58 +1,59 @@
1
1
  const rules = require('./rules.js')
2
2
  const util = require('./util.js')
3
3
 
4
- function Model(name, opts, manager) {
4
+ function Model(name, definition, waitForIndexes, manager) {
5
5
  /**
6
6
  * Setup a model
7
7
  * @param {string} name
8
- * @param {object} opts - see mongodb collection documentation
9
- * @param {boolean} opts.waitForIndexes
10
- * @return Promise(model) | this
11
- * @this model
8
+ * @param {object} definition - model definition
9
+ * @param {boolean} waitForIndexes - wait for indexes to be created before returning (returns a promise)
10
+ *
11
+ * @return model or Promise(model)
12
+ * @this manager
12
13
  */
13
14
  if (!(this instanceof Model)) {
14
- return new Model(name, opts, this)
15
+ return new Model(name, definition, waitForIndexes, this)
15
16
  } else if (!name) {
16
17
  throw 'No model name defined'
17
18
  } else if (name.match(/^_/)) {
18
19
  throw 'Model names cannot start with an underscore'
19
- } else if (!opts) {
20
+ } else if (!definition) {
20
21
  throw `No model definition passed for "${name}"`
21
- } else if (!opts.fields) {
22
+ } else if (!definition.fields) {
22
23
  throw `We couldn't find ${name}.fields in the model definition, the model maybe setup `
23
- + `or exported incorrectly:\n${JSON.stringify(opts, null, 2)}`
24
- } else if (!util.isSubdocument(opts.fields) && opts.fields.type == 'any') {
24
+ + `or exported incorrectly:\n${JSON.stringify(definition, null, 2)}`
25
+ } else if (!util.isSubdocument(definition.fields) && definition.fields.type == 'any') {
25
26
  throw `Instead of using { type: 'any' } for ${name}.fields, please use the new 'strict' definition rule` +
26
27
  ', e.g. { schema: { strict: false }}'
27
- } else if (!util.isSubdocument(opts.fields) && !util.isEmpty(opts.fields)) {
28
+ } else if (!util.isSubdocument(definition.fields) && !util.isEmpty(definition.fields)) {
28
29
  throw `The ${name}.fields object should be a valid document, e.g. { name: { type: 'string' }}`
29
30
  }
30
31
 
31
32
  // Add schema options
32
33
  Object.assign(this, {
33
- ...(opts.methods || {}),
34
- afterFind: opts.afterFind || [],
35
- afterInsert: (opts.afterInsert || []).concat(opts.afterInsertUpdate || []),
36
- afterUpdate: (opts.afterUpdate || []).concat(opts.afterInsertUpdate || []),
37
- afterRemove: opts.afterRemove || [],
38
- beforeInsert: (opts.beforeInsert || []).concat(opts.beforeInsertUpdate || []),
39
- beforeUpdate: (opts.beforeUpdate || []).concat(opts.beforeInsertUpdate || []),
40
- beforeRemove: opts.beforeRemove || [],
41
- beforeValidate: opts.beforeValidate || [],
34
+ ...(definition.methods || {}),
35
+ afterFind: definition.afterFind || [],
36
+ afterInsert: (definition.afterInsert || []).concat(definition.afterInsertUpdate || []),
37
+ afterUpdate: (definition.afterUpdate || []).concat(definition.afterInsertUpdate || []),
38
+ afterRemove: definition.afterRemove || [],
39
+ beforeInsert: (definition.beforeInsert || []).concat(definition.beforeInsertUpdate || []),
40
+ beforeUpdate: (definition.beforeUpdate || []).concat(definition.beforeInsertUpdate || []),
41
+ beforeRemove: definition.beforeRemove || [],
42
+ beforeValidate: definition.beforeValidate || [],
42
43
  error: manager.error,
43
44
  info: manager.info,
44
45
  warn: manager.warn,
45
- insertBL: opts.insertBL
46
- ? !opts.insertBL.includes('_id') && !opts.insertBL.includes('-_id') ? ['_id'].concat(opts.insertBL) : opts.insertBL
47
- : ['_id'],
48
- fields: { ...(util.deepCopy(opts.fields) || {}) },
49
- findBL: opts.findBL || ['password'], // todo: password should be removed
46
+ insertBL: !definition.insertBL
47
+ ? ['_id'] : !definition.insertBL.includes('_id') && !definition.insertBL.includes('-_id')
48
+ ? ['_id'].concat(definition.insertBL) : definition.insertBL,
49
+ fields: { ...(util.deepCopy(definition.fields) || {}) },
50
+ findBL: definition.findBL || ['password'], // todo: password should be removed
50
51
  manager: manager,
51
- messages: opts.messages || {},
52
- messagesLen: Object.keys(opts.messages || {}).length > 0,
52
+ messages: definition.messages || {},
53
+ messagesLen: Object.keys(definition.messages || {}).length > 0,
53
54
  name: name,
54
- rules: { ...(opts.rules || {}) },
55
- updateBL: opts.updateBL || [],
55
+ rules: { ...(definition.rules || {}) },
56
+ updateBL: definition.updateBL || [],
56
57
  })
57
58
 
58
59
  // Sort messages by specifity first, then we can just return the first match
@@ -140,7 +141,7 @@ function Model(name, opts, manager) {
140
141
  if (err.type == 'info') this.info(err.detail)
141
142
  else this.error(err)
142
143
  }
143
- if (opts.waitForIndexes) return this._setupIndexes().catch(errHandler).then(() => this)
144
+ if (waitForIndexes) return this._setupIndexes().catch(errHandler).then(() => this)
144
145
  else this._setupIndexes().catch(errHandler) // returns this
145
146
  }
146
147
 
@@ -405,19 +406,19 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
405
406
  }
406
407
  }
407
408
 
408
- Model.prototype._callHooks = async function(hookName, data, opts) {
409
+ Model.prototype._callHooks = async function(hookName, data, hookContext) {
409
410
  /**
410
411
  * Calls hooks in series
411
412
  *
412
413
  * @param {string} hookName - e.g. 'beforeValidate'
413
- * @param {object} opts - operation options, e.g. { data, skipValidation, ... }
414
414
  * @param {any} arg - data to pass to the first function
415
+ * @param {object} hookContext - operation options, e.g. { data, skipValidation, ... }
415
416
  *
416
417
  * @return {any} - the result of the last function
417
418
  * @this model
418
419
  */
419
- if (opts.skipHooks) return data
420
- return await util.runSeries.call(this, this[hookName].map(f => f.bind(opts)), hookName, data)
420
+ if (hookContext.skipHooks) return data
421
+ return await util.runSeries.call(this, this[hookName].map(f => f.bind(hookContext)), hookName, data)
421
422
  }
422
423
 
423
424
  Model.prototype._defaultFields = {
@@ -446,4 +447,4 @@ module.exports = Model
446
447
 
447
448
  // Extend Model prototype
448
449
  require('./model-crud.js')
449
- require('./model-validate.js')
450
+ require('./model-validate.js')
package/lib/util.js CHANGED
@@ -371,7 +371,7 @@ module.exports = {
371
371
  **/
372
372
  let current = 0
373
373
  let isSync = true
374
- let caller = (this.afterFindName || this.name) + '.' + hookName
374
+ let caller = this.name + '.' + hookName
375
375
  let lastDefinedResult = data
376
376
 
377
377
  return new Promise((res, rej) => {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "monastery",
3
3
  "description": "⛪ A simple, straightforward MongoDB ODM",
4
4
  "author": "Ricky Boyce",
5
- "version": "3.4.1",
5
+ "version": "3.4.3",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -13,7 +13,6 @@ let plugin = module.exports = {
13
13
  *
14
14
  * @param {object} monastery manager instance
15
15
  * @param {options} options - plugin options
16
- * @this plugin
17
16
  */
18
17
 
19
18
  // Depreciation warnings
@@ -23,19 +22,21 @@ let plugin = module.exports = {
23
22
  }
24
23
 
25
24
  // Settings
26
- this.awsAcl = options.awsAcl || 'public-read' // default
27
- this.awsBucket = options.awsBucket
28
- this.awsAccessKeyId = options.awsAccessKeyId
29
- this.awsSecretAccessKey = options.awsSecretAccessKey
30
- this.awsRegion = options.awsRegion
31
- this.bucketDir = options.bucketDir || 'full' // depreciated > 1.36.2
32
- this.filesize = options.filesize
33
- this.formats = options.formats || ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff']
34
- this.getSignedUrlOption = options.getSignedUrl
35
- this.manager = manager
36
- this.metadata = options.metadata ? util.deepCopy(options.metadata) : undefined,
37
- this.params = options.params ? util.deepCopy(options.params) : {},
38
- this.path = options.path || function (uid, basename, ext, file) { return `full/${uid}.${ext}` }
25
+ manager.imagePlugin = {
26
+ _s3Client: null,
27
+ awsAcl: options.awsAcl || 'public-read', // default
28
+ awsBucket: options.awsBucket,
29
+ awsAccessKeyId: options.awsAccessKeyId,
30
+ awsSecretAccessKey: options.awsSecretAccessKey,
31
+ awsRegion: options.awsRegion,
32
+ bucketDir: options.bucketDir || 'full', // depreciated > 1.36.2
33
+ filesize: options.filesize,
34
+ formats: options.formats || ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff'],
35
+ getSignedUrlOption: options.getSignedUrl,
36
+ metadata: options.metadata ? util.deepCopy(options.metadata) : undefined,
37
+ params: options.params ? util.deepCopy(options.params) : {},
38
+ path: options.path || function (uid, basename, ext, file) { return `full/${uid}.${ext}` },
39
+ }
39
40
 
40
41
  if (!options.awsBucket || !options.awsAccessKeyId || !options.awsSecretAccessKey) {
41
42
  throw new Error('Monastery imagePlugin: awsRegion, awsBucket, awsAccessKeyId, or awsSecretAccessKey is not defined')
@@ -48,21 +49,21 @@ let plugin = module.exports = {
48
49
  // v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
49
50
  // v3: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/
50
51
  // v3 examples: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_s3_code_examples.html
51
- this.getS3Client = (useRegion) => {
52
+ manager.imagePlugin.getS3Client = (useRegion) => {
52
53
  const { S3 } = require('@aws-sdk/client-s3')
53
54
  const key = '_s3Client'// useRegion ? '_s3ClientRegional' : '_s3Client'
54
- return this[key] || (this[key] = new S3({
55
+ return manager.imagePlugin[key] || (manager.imagePlugin[key] = new S3({
55
56
  // ...(region: useRegion ? this.awsRegion : undefined,
56
- region: this.awsRegion, // if region is missing it throws an error, but only in production...
57
+ region: manager.imagePlugin.awsRegion, // if region is missing it throws an error, but only in production...
57
58
  credentials: {
58
- accessKeyId: this.awsAccessKeyId,
59
- secretAccessKey: this.awsSecretAccessKey,
59
+ accessKeyId: manager.imagePlugin.awsAccessKeyId,
60
+ secretAccessKey: manager.imagePlugin.awsSecretAccessKey,
60
61
  },
61
62
  }))
62
63
  }
63
64
 
64
65
  // Add before model hook
65
- manager.beforeModel.push(this.setupModel.bind(this))
66
+ manager.beforeModel.push(plugin.setupModel)
66
67
  },
67
68
 
68
69
  setupModel: function(model) {
@@ -70,35 +71,35 @@ let plugin = module.exports = {
70
71
  * Cache all model image paths for a model and add monastery operation hooks
71
72
  * Todo: need to test the model hook arguement signatures here
72
73
  * @param {object} model
73
- * @this plugin
74
+ * @this {object} - null
74
75
  */
75
- model.imageFields = plugin._findAndTransformImageFields(model.fields, '')
76
+ model.imageFields = plugin._findAndTransformImageFields.call(model, model.fields, '')
76
77
 
77
78
  if (model.imageFields.length) {
78
79
  // Todo?: Update image fields / blacklists with the new object schema
79
80
  // model._setupFields(model.fields)/model._getFieldsFlattened(model.fields)
80
81
  model.beforeValidate.push(function(data, n) {
81
- plugin.keepImagePlacement(this, data).then(() => n(null, data)).catch(e => n(e))
82
+ plugin.keepImagePlacement.call(this, data).then(() => n(null, data)).catch(e => n(e))
82
83
  })
83
84
  model.beforeUpdate.push(function(data, n) {
84
- plugin.removeImages(this, data).then(() => n(null, data)).catch(e => n(e))
85
+ plugin.removeImages.call(this, data).then(() => n(null, data)).catch(e => n(e))
85
86
  })
86
87
  model.beforeRemove.push(function(n) {
87
- plugin.removeImages(this, {}).then(() => n(null, {})).catch(e => n(e))
88
+ plugin.removeImages.call(this, {}).then(() => n(null, {})).catch(e => n(e))
88
89
  })
89
90
  model.afterUpdate.push(function(data, n) {
90
- plugin.addImages(this, data).then(() => n(null, data)).catch(e => n(e))
91
+ plugin.addImages.call(this, data).then(() => n(null, data)).catch(e => n(e))
91
92
  })
92
93
  model.afterInsert.push(function(data, n) {
93
- plugin.addImages(this, data).then(() => n(null, data)).catch(e => n(e))
94
+ plugin.addImages.call(this, data).then(() => n(null, data)).catch(e => n(e))
94
95
  })
95
96
  model.afterFind.push(function(data, n) {
96
- plugin.getSignedUrls.call(model, this, data).then(() => n(null, data)).catch(e => n(e))
97
+ plugin.getSignedUrls.call(this, data).then(() => n(null, data)).catch(e => n(e))
97
98
  })
98
99
  }
99
100
  },
100
101
 
101
- addImages: function(options, data, test) {
102
+ addImages: function(data, test) {
102
103
  /**
103
104
  * Hooked after create/update
104
105
  * Uploads viable images and saves their details on the model. AWS Lambda takes
@@ -115,19 +116,20 @@ let plugin = module.exports = {
115
116
  * 2mb: 1864ms, 1164ms
116
117
  * 0.1mb: 480ms
117
118
  *
118
- * @param {object} options - monastery operation options {model, query, files, ..}
119
119
  * @param {object} data -
120
120
  * @param {boolean} test -
121
+ *
121
122
  * @return promise(
122
123
  * {object} data - data object containing new S3 image-object
123
124
  * ])
124
- * @this plugin
125
+ * @this {object} - monastery operation options {model, query, files, create, multi }
125
126
  */
126
- let { model, query, files } = options
127
+ const { model, query, files, create, multi } = this
128
+ const imagePlugin = model.manager.imagePlugin
127
129
  if (!files) return Promise.resolve([])
128
130
 
129
131
  // Build an ID query from query/data. Inserts add _id to the data automatically.
130
- let idquery = query && query._id? query : { _id: data._id }
132
+ const idquery = query && query._id? query : { _id: data._id }
131
133
 
132
134
  // We currently don't support an array of data objects.
133
135
  if (util.isArray(data)) {
@@ -144,32 +146,32 @@ let plugin = module.exports = {
144
146
  }
145
147
 
146
148
  // Find valid images and upload to S3, and update data with image objects
147
- return plugin._findValidImages(files, model).then(files => {
149
+ return plugin._findValidImages.call(model, files).then(files => {
148
150
  return Promise.all(files.map(filesArr => {
149
151
  return Promise.all(filesArr.map(file => {
150
152
  return new Promise((resolve, reject) => {
151
153
  let uid = require('nanoid').nanoid()
152
- let path = filesArr.imageField.path || plugin.path
154
+ let path = filesArr.imageField.path || imagePlugin.path
153
155
  let image = {
154
- bucket: filesArr.imageField.awsBucket || plugin.awsBucket,
155
- date: plugin.manager.opts.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000),
156
+ bucket: filesArr.imageField.awsBucket || imagePlugin.awsBucket,
157
+ date: model.manager.opts.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000),
156
158
  filename: file.name,
157
159
  filesize: file.size,
158
- metadata: filesArr.imageField.metadata || plugin.metadata,
160
+ metadata: filesArr.imageField.metadata || imagePlugin.metadata,
159
161
  path: path(uid, file.name, file.ext, file),
160
162
  uid: uid,
161
163
  }
162
164
  let s3Options = {
163
165
  // ACL: Some IAM permissions "s3:PutObjectACL" must be included in the policy
164
166
  // params: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
165
- ACL: filesArr.imageField.awsAcl || plugin.awsAcl,
167
+ ACL: filesArr.imageField.awsAcl || imagePlugin.awsAcl,
166
168
  Body: file.data,
167
169
  Bucket: image.bucket,
168
170
  Key: image.path,
169
171
  Metadata: image.metadata,
170
- ...(filesArr.imageField.params || plugin.params),
172
+ ...(filesArr.imageField.params || imagePlugin.params),
171
173
  }
172
- plugin.manager.info(
174
+ model.manager.info(
173
175
  `Uploading '${image.filename}' to '${image.bucket}/${image.path}'`
174
176
  )
175
177
  if (test) {
@@ -178,7 +180,7 @@ let plugin = module.exports = {
178
180
  } else {
179
181
  const { Upload } = require('@aws-sdk/lib-storage')
180
182
  const upload = new Upload({
181
- client: plugin.getS3Client(),
183
+ client: imagePlugin.getS3Client(),
182
184
  params: s3Options,
183
185
  })
184
186
  // upload.on('httpUploadProgress', (progress) => {
@@ -206,44 +208,74 @@ let plugin = module.exports = {
206
208
  return model._update(
207
209
  idquery,
208
210
  { '$set': prunedData },
209
- { 'multi': options.multi || options.create }
211
+ { 'multi': multi || create }
210
212
  )
211
213
 
212
214
  // If errors, remove inserted documents to prevent double ups when the user resaves.
213
215
  // We are pretty much trying to emulate a db transaction.
214
216
  }).catch(err => {
215
- if (options.create) model._remove(idquery)
217
+ if (create) model._remove(idquery)
216
218
  throw err
217
219
  })
218
220
  },
219
221
 
220
- getSignedUrls: async function(options, data) {
222
+ getSignedUrl: async function(path, expires=3600, bucket) {
223
+ /**
224
+ * @param {string} path - aws file path
225
+ * @param {number} <expires> - seconds
226
+ * @param {string} <bucket>
227
+ *
228
+ * @return {promise} signedUrl
229
+ * @see v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
230
+ * @see v3: https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-presigned-url
231
+ *
232
+ * @this manager
233
+ */
234
+ const { imagePlugin } = this
235
+ if (!imagePlugin) {
236
+ throw new Error(
237
+ 'You must call getSignedUrl() with a manager as the context. The manager also needs to have the imagePlugin setup too, ' +
238
+ 'e.g. `monastery(..., { imagePlugin })`'
239
+ )
240
+ }
241
+ const { GetObjectCommand } = require('@aws-sdk/client-s3')
242
+ const params = { Bucket: bucket || imagePlugin.awsBucket, Key: path }
243
+ const command = new GetObjectCommand(params)
244
+ let signedUrl = await getSignedUrl(imagePlugin.getS3Client(true), command, { expiresIn: expires })
245
+ // console.log(signedUrl)
246
+ return signedUrl
247
+ },
248
+
249
+ getSignedUrls: async function(data) {
221
250
  /**
222
251
  * Get signed urls for all image objects in data
223
- * @param {object} options - monastery operation options {model, query, files, ..}
252
+ *
224
253
  * @param {object} data
254
+ *
225
255
  * @return promise() - mutates data
226
- * @this model
256
+ * @this {object} - monastery operation options {model, query, files, ..}
227
257
  */
228
258
  // Not wanting signed urls for this operation?
229
- if (util.isDefined(options.getSignedUrls) && !options.getSignedUrls) return
259
+ const { getSignedUrls, model } = this
260
+ const imagePlugin = model.manager.imagePlugin
261
+ if (util.isDefined(getSignedUrls) && !getSignedUrls) return
230
262
 
231
263
  // Find all image objects in data
232
264
  for (let doc of util.toArray(data)) {
233
- for (let imageField of this.imageFields) {
234
- if (options.getSignedUrls
235
- || (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : plugin.getSignedUrlOption)) {
265
+ for (let imageField of model.imageFields) {
266
+ if (getSignedUrls
267
+ || (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : imagePlugin.getSignedUrlOption)) {
236
268
  let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
237
269
  // todo: we could do this in parallel
238
270
  for (let image of images) {
239
- image.image.signedUrl = await plugin.getSignedUrl(image.image.path, 3600, imageField.awsBucket)
271
+ image.image.signedUrl = await plugin.getSignedUrl.call(model.manager, image.image.path, 3600, imageField.awsBucket)
240
272
  }
241
273
  }
242
274
  }
243
275
  }
244
276
  },
245
277
 
246
- keepImagePlacement: async function(options, data) {
278
+ keepImagePlacement: async function(data) {
247
279
  /**
248
280
  * Hook before update/remove
249
281
  * Since monastery removes undefined array items on validate, we need to convert any
@@ -254,22 +286,22 @@ let plugin = module.exports = {
254
286
  * req.body = 'photos[0]' : undefined || non existing (set to null)
255
287
  * req.files = 'photos[0]' : { ...binary }
256
288
  *
257
- * @param {object} options - monastery operation options {query, model, files, multi, ..}
258
289
  * @return promise
259
- * @this plugin
290
+ * @this {object} - monastery operation options {query, model, files, multi, ..}
260
291
  */
261
- if (typeof options.files == 'undefined') return
292
+ const { model, files } = this
293
+ if (typeof files == 'undefined') return
262
294
  // Check upload errors and find valid uploaded images
263
- let files = await plugin._findValidImages(options.files || {}, options.model)
295
+ let validFiles = await plugin._findValidImages.call(model, files || {})
264
296
  // Set undefined primative-array items to null where files are located
265
- for (let filesArray of files) {
266
- if (filesArray.inputPath.match(/\.[0-9]+$/)) {
267
- util.setDeepValue(data, filesArray.inputPath, null, true, false, true)
297
+ for (let item of validFiles) {
298
+ if (item.inputPath.match(/\.[0-9]+$/)) {
299
+ util.setDeepValue(data, item.inputPath, null, true, false, true)
268
300
  }
269
301
  }
270
302
  },
271
303
 
272
- removeImages: async function(options, data, test) {
304
+ removeImages: async function(data, test) {
273
305
  /**
274
306
  * Hook before update/remove
275
307
  * Removes images not found in data, this means you will need to pass the image objects to every update operation
@@ -280,24 +312,25 @@ let plugin = module.exports = {
280
312
  * 3. delete leftovers from S3
281
313
  *
282
314
  * @ref https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObjects-property
283
- * @param {object} options - monastery operation options {query, model, files, multi, ..}
284
315
  * @return promise([
285
316
  * {object} useCount - images that wont be removed, e.g. { lion1: 1 }
286
317
  * {array} unused - S3 image uris to be removed, e.g. [{ Key: 'small/lion1.jpg' }, ..]
287
318
  * ])
288
- * @this plugin
319
+ * @this {object} - monastery operation options {query, model, files, multi, ..}
289
320
  */
290
321
  let pre
291
- let preExistingImages = []
292
- let useCount = {}
293
- if (typeof options.files == 'undefined') return
322
+ const preExistingImages = []
323
+ const useCount = {}
324
+ const { model, files, query } = this
325
+ const imagePlugin = model.manager.imagePlugin
326
+ if (typeof files == 'undefined') return
294
327
 
295
328
  // Find all documents from the same query
296
- let docs = await options.model._find(options.query, options)
329
+ const docs = await model._find(query, this)
297
330
 
298
331
  // Find all pre-existing image objects in documents
299
332
  for (let doc of util.toArray(docs)) { //x2
300
- for (let imageField of options.model.imageFields) { //x5
333
+ for (let imageField of model.imageFields) { //x5
301
334
  let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
302
335
  for (let image of images) {
303
336
  preExistingImages.push(image)
@@ -320,10 +353,10 @@ let plugin = module.exports = {
320
353
  // console.log(dataFilled)
321
354
 
322
355
  // Check upload errors and find valid uploaded images
323
- let files = await plugin._findValidImages(options.files || {}, options.model)
356
+ let validFiles = await plugin._findValidImages.call(model, files || {})
324
357
 
325
358
  // Loop all schema image fields
326
- for (let imageField of options.model.imageFields) { //x5
359
+ for (let imageField of model.imageFields) { //x5
327
360
  let images = plugin._findImagesInData(dataFilled, imageField, 0, '')
328
361
  if (!images.length) continue
329
362
  // console.log(images)
@@ -353,8 +386,8 @@ let plugin = module.exports = {
353
386
  useCount[image.image.uid]++
354
387
  }
355
388
  // Any file overriding this image?
356
- for (let filesArray of files) {
357
- if (image.dataPath == filesArray.inputPath) {
389
+ for (let item of validFiles) {
390
+ if (image.dataPath == item.inputPath) {
358
391
  useCount[image.image.uid]--
359
392
  }
360
393
  }
@@ -376,17 +409,17 @@ let plugin = module.exports = {
376
409
  { Key: `medium/${key}.jpg` },
377
410
  { Key: `large/${key}.jpg` }
378
411
  )
379
- plugin.manager.info(
412
+ model.manager.info(
380
413
  `Removing '${pre.image.filename}' from '${pre.image.bucket}/${pre.image.path}'`
381
414
  )
382
415
  }
383
416
  if (test) return [useCount, unused]
384
417
  // Delete any unused images from s3. If the image is on a different bucket
385
- // the file doesnt get deleted, we only delete from plugin.awsBucket.
418
+ // the file doesnt get deleted, we only delete from imagePlugin.awsBucket.
386
419
  if (!unused.length) return
387
420
  await new Promise((resolve, reject) => {
388
- plugin.getS3Client().deleteObjects({
389
- Bucket: plugin.awsBucket,
421
+ imagePlugin.getS3Client().deleteObjects({
422
+ Bucket: imagePlugin.awsBucket,
390
423
  Delete: { Objects: unused },
391
424
  }, (err, data) => {
392
425
  if (err) reject(err)
@@ -402,6 +435,7 @@ let plugin = module.exports = {
402
435
  * @param {object} data
403
436
  * @param {object} image
404
437
  * @return mutates data
438
+ * @this null
405
439
  */
406
440
  let chunks = path.split('.')
407
441
  let target = data
@@ -421,22 +455,24 @@ let plugin = module.exports = {
421
455
  return data
422
456
  },
423
457
 
424
- _findValidImages: function(files, model) {
458
+ _findValidImages: function(files) {
425
459
  /**
426
460
  * Find and return valid uploaded files
427
461
  * @param {object} files - req.files
428
- * @param {object} model
429
462
  * @return promise([
430
463
  * [{..file}, .imageField, .inputPath],
431
464
  * ..
432
465
  * ])
466
+ * @this model
433
467
  */
434
468
  let validFiles = []
469
+ const { imageFields, manager } = this
470
+ const imagePlugin = manager.imagePlugin
435
471
 
436
472
  // Filter valid image files by `type='image'`, convert file keys to dot notation, and force array
437
473
  for (let key in files) {
438
474
  let key2 = key.replace(/\]/g, '').replace(/\[/g, '.')
439
- let imageField = model.imageFields.find(o => key2.match(o.fullPathRegex))
475
+ let imageField = imageFields.find(o => key2.match(o.fullPathRegex))
440
476
  if (imageField) {
441
477
  let filesArr = util.toArray(files[key])
442
478
  filesArr.imageField = imageField
@@ -452,8 +488,8 @@ let plugin = module.exports = {
452
488
  return Promise.all(filesArr.map((file, i) => {
453
489
  return new Promise((resolve, reject) => {
454
490
  require('file-type').fromBuffer(file.data).then(res => {
455
- let filesize = filesArr.imageField.filesize || plugin.filesize
456
- let formats = filesArr.imageField.formats || plugin.formats
491
+ let filesize = filesArr.imageField.filesize || imagePlugin.filesize
492
+ let formats = filesArr.imageField.formats || imagePlugin.formats
457
493
  let allowAny = util.inArray(formats, 'any')
458
494
  file.format = res? res.ext : ''
459
495
  file.ext = file.format || (file.name.match(/\.(.*)$/) || [])[1] || 'unknown'
@@ -488,10 +524,11 @@ let plugin = module.exports = {
488
524
  * @param {object|array} unprocessedFields - fields not yet setup
489
525
  * @param {string} path
490
526
  * @return [{}, ...]
491
- * @this plugin
527
+ * @this model
492
528
  */
493
529
  let list = []
494
- let that = this
530
+ const { manager } = this
531
+ const imagePlugin = manager.imagePlugin
495
532
  util.forEach(unprocessedFields, (field, fieldName) => {
496
533
  let path2 = `${path}.${fieldName}`.replace(/^\./, '')
497
534
  if (fieldName == 'schema') return
@@ -499,12 +536,12 @@ let plugin = module.exports = {
499
536
  // Subdocument field
500
537
  if (util.isSubdocument(field)) {
501
538
  // log(`Recurse 1: ${path2}`)
502
- list = list.concat(plugin._findAndTransformImageFields(field, path2))
539
+ list = list.concat(plugin._findAndTransformImageFields.call(this, field, path2))
503
540
 
504
541
  // Array field
505
542
  } else if (util.isArray(field)) {
506
543
  // log(`Recurse 2: ${path2}`)
507
- list = list.concat(plugin._findAndTransformImageFields(field, path2))
544
+ list = list.concat(plugin._findAndTransformImageFields.call(this, field, path2))
508
545
 
509
546
  // Image field. Test for field.image as field.type may be 'any'
510
547
  } else if (field.type == 'image' || field.image) {
@@ -514,8 +551,9 @@ let plugin = module.exports = {
514
551
  }
515
552
  if (field.filename) { // > v1.36.3
516
553
  this.manager.warn(`${path2}.filename has been depreciated in favour of ${path2}.path()`)
517
- field.path = field.path
518
- || function(uid, basename, ext, file) { return `${that.bucketDir}/${uid}/${field.filename}.${ext}` }
554
+ field.path = field.path || function(uid, basename, ext, file) {
555
+ return `${imagePlugin.bucketDir}/${uid}/${field.filename}.${ext}`
556
+ }
519
557
  }
520
558
 
521
559
  list.push({
@@ -540,7 +578,7 @@ let plugin = module.exports = {
540
578
  metadata: { type: 'any' },
541
579
  path: { type: 'string' },
542
580
  uid: { type: 'string' },
543
- schema: { image: true, isImageObject: true, nullObject: true },
581
+ schema: { image: true, isImageObject: true, nullObject: true, default: undefined },
544
582
  }
545
583
  }
546
584
  })
@@ -555,6 +593,7 @@ let plugin = module.exports = {
555
593
  * @param {number} imageFieldChunkIndex - imageField path chunk index
556
594
  * @param {string} dataPath
557
595
  * @return [{ imageField: {}, dataPath: '', image: {} }, ..]
596
+ * @this null
558
597
  */
559
598
  let list = []
560
599
  let chunks = imageField.fullPath.split('.').slice(imageFieldChunkIndex)
@@ -590,27 +629,4 @@ let plugin = module.exports = {
590
629
 
591
630
  return list
592
631
  },
593
-
594
- getSignedUrl: async (path, expires=3600, bucket) => {
595
- /**
596
- * @param {string} path - aws file path
597
- * @param {number} <expires> - seconds
598
- * @param {string} <bucket>
599
- * @return {promise} signedUrl
600
- * @see v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
601
- * @see v3: https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md#s3-presigned-url
602
- */
603
- if (!plugin.getS3Client) {
604
- throw new Error(
605
- 'To use db.getSignedUrl(), the imagePlugin manager option must be defined, e.g. `monastery(..., { imagePlugin })`'
606
- )
607
- }
608
- const { GetObjectCommand } = require('@aws-sdk/client-s3')
609
- const params = { Bucket: bucket || plugin.awsBucket, Key: path }
610
- const command = new GetObjectCommand(params)
611
- let signedUrl = await getSignedUrl(plugin.getS3Client(true), command, { expiresIn: expires })
612
- // console.log(signedUrl)
613
- return signedUrl
614
- },
615
-
616
632
  }
package/test/crud.js CHANGED
@@ -52,19 +52,32 @@ test('insert basics', async () => {
52
52
  })
53
53
 
54
54
  test('insert option defaultObjects', async () => {
55
- let db2 = monastery('127.0.0.1/monastery', { defaultObjects: true, timestamps: false })
55
+ let db2 = monastery('127.0.0.1/monastery', {
56
+ defaultObjects: true,
57
+ timestamps: false,
58
+ imagePlugin: {
59
+ awsBucket: 'test',
60
+ awsRegion: 'test',
61
+ awsAccessKeyId: 'test',
62
+ awsSecretAccessKey: 'test',
63
+ },
64
+ })
56
65
  let schema = {
57
66
  fields: {
67
+ avatar: { type: 'image' }, // its an object but should default to `undefined`
58
68
  name: { type: 'string' },
59
69
  names: [{ type: 'string' }],
70
+ animal: {
71
+ name: { type: 'string' },
72
+ },
60
73
  animals: {
61
74
  dog: { type: 'string' },
62
75
  dogs: [{ name: { type: 'string' } }],
63
76
  },
64
77
  },
65
78
  }
66
- let user1 = db.model('user', schema)
67
- let user2 = db2.model('user', schema)
79
+ let user1 = db.model('user-defaultObjects', schema)
80
+ let user2 = db2.model('user-defaultObjects', schema)
68
81
 
69
82
  // defaultObjects off (default)
70
83
  let inserted1 = await user1.insert({ data: {} })
@@ -76,6 +89,7 @@ test('insert option defaultObjects', async () => {
76
89
  let inserted2 = await user2.insert({ data: {} })
77
90
  expect(inserted2).toEqual({
78
91
  _id: inserted2._id,
92
+ animal: {},
79
93
  names: [],
80
94
  animals: { dogs: [] },
81
95
  })
@@ -339,10 +353,7 @@ test('find default field blacklisted', async () => {
339
353
 
340
354
  test('find default field population with option noDefaults', async () => {
341
355
  async function setup(noDefaults) {
342
- /**
343
- * Setup
344
- * @returns {Object} {dog, user, dog1Doc, dog2Doc, user1Doc}
345
- */
356
+ // @returns {Object} {dog, user, dog1Doc, dog2Doc, user1Doc}
346
357
  // similar to "find default field population"
347
358
  const db = monastery('127.0.0.1/monastery', { noDefaults: noDefaults, timestamps: false })
348
359
  const userDefinition = {
@@ -494,6 +505,9 @@ test('find default field population with option noDefaults', async () => {
494
505
  },
495
506
  dogs: [{ _id: s2.dog1Doc._id, user: s2.user1Doc._id }], // should not have a default name
496
507
  })
508
+
509
+ s1.db.close()
510
+ s2.db.close()
497
511
  })
498
512
 
499
513
  test('update general', async () => {
@@ -1060,7 +1074,6 @@ test('hooks > basic', async () => {
1060
1074
  await expect(user2.update({ query: userDoc2._id, data: { first: 'MM' } })).resolves.toEqual({ first: 'MM' })
1061
1075
  await expect(user2.update({ query: userDoc2._id, data: { first: 'M', bad: true } })).rejects.toThrow('error2')
1062
1076
  })
1063
-
1064
1077
  test('hooks > chained values', async () => {
1065
1078
  let bookCount = 0
1066
1079
  const afterInsertAsync = [
@@ -1500,4 +1513,6 @@ test('update set and unset with option skipValidation', async () => {
1500
1513
  const u8 = { query: userId, $unset: { 'profile.name': '' }, skipValidation: true }
1501
1514
  await expect(user.update(u8)).resolves.toEqual({})
1502
1515
  await expect(user.findOne(userId)).resolves.toEqual({ _id: userId, profile: {} })
1516
+
1517
+ db2.close()
1503
1518
  })
package/test/model.js CHANGED
@@ -449,12 +449,11 @@ test('model indexes basic', async () => {
449
449
 
450
450
  // Unique & text index
451
451
  let userIndexModel = await db.model('userIndex', {
452
- waitForIndexes: true,
453
452
  fields: {
454
453
  email: { type: 'string', index: 'unique' },
455
454
  name: { type: 'string', index: 'text' },
456
455
  },
457
- })
456
+ }, { waitForIndexes: true })
458
457
 
459
458
  let userIndexModelIndexes = await db.db.collection('userIndex').indexes()
460
459
  expect(userIndexModelIndexes[0]).toEqual({
@@ -504,7 +503,6 @@ test('model indexes unique', async () => {
504
503
 
505
504
  // Partial unique indexes (allows mulitple null values)
506
505
  await db.model('userUniqueIndex', {
507
- waitForIndexes: true,
508
506
  fields: {
509
507
  email: {
510
508
  type: 'string',
@@ -516,7 +514,7 @@ test('model indexes unique', async () => {
516
514
  },
517
515
  },
518
516
  },
519
- })
517
+ }, { waitForIndexes: true })
520
518
 
521
519
  let indexes2 = await db.db.collection('userUniqueIndex').indexes()
522
520
  expect(indexes2[0]).toEqual({
@@ -648,7 +646,6 @@ test('model indexes 2dsphere', async () => {
648
646
  // Setup. The tested model needs to be unique as race condition issue arises when the same model
649
647
  // with text indexes are setup at the same time
650
648
  await db.model('user99', {
651
- waitForIndexes: true,
652
649
  fields: {
653
650
  location: {
654
651
  index: '2dsphere',
@@ -661,7 +658,7 @@ test('model indexes 2dsphere', async () => {
661
658
  coordinates: [{ type: 'number' }], // lat, lng
662
659
  },
663
660
  },
664
- })
661
+ }, { waitForIndexes: true })
665
662
 
666
663
  // Schema check
667
664
  expect(db.user99.fields.location).toEqual({
@@ -158,11 +158,15 @@ test('images addImages helper functions', async () => {
158
158
  })
159
159
 
160
160
  test('images addImages', async () => {
161
- let user = db.model('user', { fields: {
162
- logo: { type: 'image' },
163
- logos: [{ type: 'image' }],
164
- users: [{ logo: { type: 'image' } }],
165
- }})
161
+ let user = db.model('user', {
162
+ fields: {
163
+ logo: { type: 'image' },
164
+ logos: [{ type: 'image' }],
165
+ users: [{ logo: { type: 'image' } }],
166
+ },
167
+ })
168
+ // console.log(db.opts, user.fields.logo)
169
+ // throw 'qwef'
166
170
 
167
171
  let supertest = require('supertest')
168
172
  let express = require('express')
@@ -174,7 +178,7 @@ test('images addImages', async () => {
174
178
  try {
175
179
  // Files exist
176
180
  expect(req.files.logo).toEqual(expect.any(Object))
177
- let validFiles = await imagePluginFile._findValidImages(req.files, user)
181
+ let validFiles = await imagePluginFile._findValidImages.call(user, req.files)
178
182
  // Valid file count
179
183
  expect(validFiles).toEqual([
180
184
  expect.any(Array),
@@ -198,7 +202,7 @@ test('images addImages', async () => {
198
202
  expect(validFiles[2][0].format).toEqual('png')
199
203
  expect(validFiles[3][0].format).toEqual('png')
200
204
 
201
- let response = await imagePluginFile.addImages(
205
+ let response = await imagePluginFile.addImages.call(
202
206
  { model: user, files: req.files, query: { _id: 1234 }},
203
207
  req.body,
204
208
  true
@@ -240,7 +244,8 @@ test('images addImages', async () => {
240
244
  })
241
245
  res.send()
242
246
  } catch (e) {
243
- console.log(e.message || e)
247
+ console.log(e)
248
+ // console.log(e.message || e)
244
249
  res.status(500).send()
245
250
  }
246
251
  })
@@ -297,7 +302,7 @@ test('images removeImages', async () => {
297
302
  req.body.logos = JSON.parse(req.body.logos)
298
303
  req.body.users = JSON.parse(req.body.users)
299
304
  let options = { files: req.files, model: user, query: { _id: user1._id }}
300
- let response = await imagePluginFile.removeImages(options, req.body, true)
305
+ let response = await imagePluginFile.removeImages.call(options, req.body, true)
301
306
  expect(response[0]).toEqual({ test1: 1, test2: 0, test3: 1, test4: 0 })
302
307
  expect(response[1]).toEqual([
303
308
  { Key: 'dir/test2.png' },
@@ -358,7 +363,7 @@ test('images removeImages with no data', async () => {
358
363
  app.post('/', async function(req, res) {
359
364
  try {
360
365
  let options = { files: req.files, model: user, query: { _id: user1._id }}
361
- let response = await imagePluginFile.removeImages(options, req.body, true)
366
+ let response = await imagePluginFile.removeImages.call(options, req.body, true)
362
367
  expect(response[0]).toEqual({})
363
368
  expect(response[1]).toEqual([])
364
369
  res.send()
@@ -474,7 +479,7 @@ test('images reorder', async () => {
474
479
  try {
475
480
  req.body.logos = JSON.parse(req.body.logos)
476
481
  let options = { files: req.files, model: user, query: { _id: user1._id } }
477
- let response = await imagePluginFile.removeImages(options, req.body, true)
482
+ let response = await imagePluginFile.removeImages.call(options, req.body, true)
478
483
  expect(response[0]).toEqual({ lion1: 1 })
479
484
  expect(response[1]).toEqual([])
480
485
  res.send()
@@ -526,16 +531,16 @@ test('images reorder and added image', async () => {
526
531
  expect(data.photos[1]).toEqual(image)
527
532
 
528
533
  // Remove images
529
- let response = await imagePluginFile.removeImages(options, data, true)
534
+ let response = await imagePluginFile.removeImages.call(options, data, true)
530
535
  expect(response[0]).toEqual({ lion1: 1 }) // useCount
531
536
  expect(response[1]).toEqual([]) // unused
532
537
 
533
538
  // New file exists
534
- let validFiles = await imagePluginFile._findValidImages(req.files, user)
539
+ let validFiles = await imagePluginFile._findValidImages.call(user, req.files)
535
540
  expect(((validFiles||[])[0]||{}).inputPath).toEqual('photos.0') // Valid inputPath
536
541
 
537
542
  // Add images
538
- response = await imagePluginFile.addImages(options, data, true)
543
+ response = await imagePluginFile.addImages.call(options, data, true)
539
544
  expect(response[0]).toEqual({
540
545
  photos: [{
541
546
  bucket: 'fake',
@@ -592,13 +597,13 @@ test('images option defaults', async () => {
592
597
  app.use(upload({ limits: { fileSize: 1000 * 480, files: 10 }}))
593
598
 
594
599
  // Basic tests
595
- expect(imagePluginFile.awsAcl).toEqual('public-read')
596
- expect(imagePluginFile.filesize).toEqual(undefined)
597
- expect(imagePluginFile.formats).toEqual(['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff'])
598
- expect(imagePluginFile.getSignedUrlOption).toEqual(undefined)
599
- expect(imagePluginFile.metadata).toEqual(undefined)
600
- expect(imagePluginFile.path).toEqual(expect.any(Function))
601
- expect(imagePluginFile.params).toEqual({})
600
+ expect(db.imagePlugin.awsAcl).toEqual('public-read')
601
+ expect(db.imagePlugin.filesize).toEqual(undefined)
602
+ expect(db.imagePlugin.formats).toEqual(['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff'])
603
+ expect(db.imagePlugin.getSignedUrlOption).toEqual(undefined)
604
+ expect(db.imagePlugin.metadata).toEqual(undefined)
605
+ expect(db.imagePlugin.path).toEqual(expect.any(Function))
606
+ expect(db.imagePlugin.params).toEqual({})
602
607
 
603
608
  // Images not signed
604
609
  let image
@@ -621,7 +626,7 @@ test('images option defaults', async () => {
621
626
  try {
622
627
  // Files exist
623
628
  expect(req.files.logo).toEqual(expect.any(Object))
624
- let response = await imagePluginFile.addImages(
629
+ let response = await imagePluginFile.addImages.call(
625
630
  { model: user, files: req.files, query: { _id: 1234 }},
626
631
  req.body || {},
627
632
  true
@@ -698,20 +703,20 @@ test('images options formats & filesizes', async () => {
698
703
  delete req.files.imageSize2
699
704
  delete req.files.imageSize3
700
705
  // Ico, Webp, and imageSvgGood will throw an error first if it's not a valid type
701
- await expect(imagePluginFile._findValidImages(req.files, user)).resolves.toEqual(expect.any(Array))
702
- await expect(imagePluginFile._findValidImages(imageSvgBad, user)).rejects.toEqual({
706
+ await expect(imagePluginFile._findValidImages.call(user, req.files)).resolves.toEqual(expect.any(Array))
707
+ await expect(imagePluginFile._findValidImages.call(user, imageSvgBad)).rejects.toEqual({
703
708
  title: 'imageSvgBad',
704
709
  detail: 'The file format \'svg\' for \'bad.svg\' is not supported',
705
710
  })
706
- await expect(imagePluginFile._findValidImages(imageSize1, user)).rejects.toEqual({
711
+ await expect(imagePluginFile._findValidImages.call(user, imageSize1)).rejects.toEqual({
707
712
  title: 'imageSize1',
708
713
  detail: 'The file size for \'lion1.png\' is bigger than 0.1MB.',
709
714
  })
710
- await expect(imagePluginFile._findValidImages(imageSize2, user)).rejects.toEqual({
715
+ await expect(imagePluginFile._findValidImages.call(user, imageSize2)).rejects.toEqual({
711
716
  title: 'imageSize2',
712
717
  detail: 'The file size for \'lion2.jpg\' is bigger than 0.3MB.',
713
718
  })
714
- await expect(imagePluginFile._findValidImages(imageSize3, user)).rejects.toEqual({
719
+ await expect(imagePluginFile._findValidImages.call(user, imageSize3)).rejects.toEqual({
715
720
  title: 'imageSize3',
716
721
  detail: 'The file size for \'house.jpg\' is too big.',
717
722
  })
@@ -833,7 +838,7 @@ test('images options awsAcl, awsBucket, metadata, params, path', async () => {
833
838
  // Files exist
834
839
  expect(req.files.optionDefaults).toEqual(expect.any(Object))
835
840
  expect(req.files.optionOverrides).toEqual(expect.any(Object))
836
- let response = await imagePluginFile.addImages(
841
+ let response = await imagePluginFile.addImages.call(
837
842
  { model: user, files: req.files, query: { _id: 1234 }},
838
843
  req.body || {},
839
844
  true
@@ -922,7 +927,7 @@ test('images option depreciations', async () => {
922
927
  try {
923
928
  // Files exist
924
929
  expect(req.files.logo).toEqual(expect.any(Object))
925
- let response = await imagePluginFile.addImages(
930
+ let response = await imagePluginFile.addImages.call(
926
931
  { model: user, files: req.files, query: { _id: 1234 }},
927
932
  req.body || {},
928
933
  true