monastery 1.36.1 → 1.37.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.
@@ -18,13 +18,24 @@ let plugin = module.exports = {
18
18
  * @this plugin
19
19
  */
20
20
 
21
+ // Depreciation warnings
22
+ if (options.bucketDir) { // > 1.36.2
23
+ manager.warn('imagePlugin.bucketDir has been depreciated in favour of imagePlugin.path')
24
+ options.path = function (uid, basename, ext, file) { `${options.bucketDir}/${uid}.${ext}` }
25
+ }
26
+
21
27
  // Settings
28
+ this.awsAcl = options.awsAcl || 'public-read'
22
29
  this.awsBucket = options.awsBucket
23
30
  this.awsAccessKeyId = options.awsAccessKeyId
24
31
  this.awsSecretAccessKey = options.awsSecretAccessKey
25
- this.bucketDir = options.bucketDir || 'full'
32
+ this.bucketDir = options.bucketDir || 'full' // depreciated > 1.36.2
33
+ this.filesize = options.filesize
26
34
  this.formats = options.formats || ['bmp', 'gif', 'jpg', 'jpeg', 'png', 'tiff']
35
+ this.getSignedUrl = options.getSignedUrl
27
36
  this.manager = manager
37
+ this.params = options.params ? util.deepCopy(options.params) : {},
38
+ this.path = options.path || function (uid, basename, ext, file) { return `full/${uid}.${ext}` }
28
39
 
29
40
  if (!options.awsBucket || !options.awsAccessKeyId || !options.awsSecretAccessKey) {
30
41
  manager.error('Monastery imagePlugin: awsBucket, awsAccessKeyId, or awsSecretAccessKey is not defined')
@@ -34,7 +45,7 @@ let plugin = module.exports = {
34
45
 
35
46
  // Create s3 'service' instance
36
47
  // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
37
- manager.getSignedUrl = this._getSignedUrl
48
+ manager._getSignedUrl = this._getSignedUrl
38
49
  this.s3 = new S3({
39
50
  credentials: {
40
51
  accessKeyId: this.awsAccessKeyId,
@@ -114,9 +125,13 @@ let plugin = module.exports = {
114
125
  if (util.isArray(data)) {
115
126
  return Promise.reject('Adding images to mulitple data objects is not supported.')
116
127
 
128
+ // We currently don't support an array of data objects.
129
+ } else if (!util.isObject(data)) {
130
+ return Promise.reject('No creat e/ update data object passed to addImages?')
131
+
117
132
  // We require the update query OR data object to contain _id. This is because non-id queries
118
133
  // will not suffice when updating the document(s) against the same query again.
119
- } else if (!data._id && (!query || !query._id)) {
134
+ } else if (!(data||{})._id && (!query || !query._id)) {
120
135
  return Promise.reject('Adding images requires the update operation to query via _id\'s.')
121
136
  }
122
137
 
@@ -126,36 +141,36 @@ let plugin = module.exports = {
126
141
  return Promise.all(filesArr.map(file => {
127
142
  return new Promise((resolve, reject) => {
128
143
  let uid = nanoid.nanoid()
129
- let pathFilename = filesArr.imageField.filename ? '/' + filesArr.imageField.filename : ''
144
+ let path = filesArr.imageField.path || plugin.path
130
145
  let image = {
131
- bucket: plugin.awsBucket,
146
+ bucket: filesArr.imageField.awsBucket || plugin.awsBucket,
132
147
  date: plugin.manager.useMilliseconds? Date.now() : Math.floor(Date.now() / 1000),
133
148
  filename: file.name,
134
149
  filesize: file.size,
135
- path: `${plugin.bucketDir}/${uid}${pathFilename}.${file.ext}`,
150
+ path: path(uid, file.name, file.ext, file),
136
151
  // sizes: ['large', 'medium', 'small'],
137
152
  uid: uid,
138
153
  }
154
+ let s3Options = {
155
+ // ACL: Some IAM permissions "s3:PutObjectACL" must be included in the policy
156
+ // params: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
157
+ ACL: filesArr.imageField.awsAcl || plugin.awsAcl,
158
+ Body: file.data,
159
+ Bucket: image.bucket,
160
+ Key: image.path,
161
+ ...(filesArr.imageField.params || plugin.params),
162
+ }
139
163
  plugin.manager.info(
140
164
  `Uploading '${image.filename}' to '${image.bucket}/${image.path}'`
141
165
  )
142
166
  if (test) {
143
167
  plugin._addImageObjectsToData(filesArr.inputPath, data, image)
144
- resolve()
168
+ resolve(s3Options)
145
169
  } else {
146
- plugin.s3.upload({
147
- Bucket: plugin.awsBucket,
148
- Key: image.path,
149
- Body: file.data,
150
- // The IAM permission "s3:PutObjectACL" must be included in the appropriate policy
151
- ACL: 'public-read',
152
- // upload params,https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
153
- ...filesArr.imageField.params,
154
-
155
- }, (err, response) => {
170
+ plugin.s3.upload(s3Options, (err, response) => {
156
171
  if (err) return reject(err)
157
172
  plugin._addImageObjectsToData(filesArr.inputPath, data, image)
158
- resolve()
173
+ resolve(s3Options)
159
174
  })
160
175
  }
161
176
  })
@@ -163,11 +178,11 @@ let plugin = module.exports = {
163
178
  }))
164
179
 
165
180
  // Save the data against the matching document(s)
166
- }).then(() => {
167
- // Remove update's _output object
181
+ }).then((s3Options) => {
168
182
  let prunedData = { ...data }
183
+ // Remove update's _output object
169
184
  delete prunedData._output
170
- if (test) return [prunedData]
185
+ if (test) return [prunedData, s3Options]
171
186
  return model._update(
172
187
  idquery,
173
188
  { '$set': prunedData },
@@ -196,10 +211,11 @@ let plugin = module.exports = {
196
211
  // Find all image objects in data
197
212
  for (let doc of util.toArray(data)) {
198
213
  for (let imageField of this.imageFields) {
199
- if (options.getSignedUrls || imageField.getSignedUrl) {
214
+ if (options.getSignedUrls
215
+ || (util.isDefined(imageField.getSignedUrl) ? imageField.getSignedUrl : plugin.getSignedUrl)) {
200
216
  let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
201
217
  for (let image of images) {
202
- image.image.signedUrl = plugin._getSignedUrl(image.image.path)
218
+ image.image.signedUrl = plugin._getSignedUrl(image.image.path, 3600, imageField.awsBucket)
203
219
  }
204
220
  }
205
221
  }
@@ -346,7 +362,10 @@ let plugin = module.exports = {
346
362
  // the file doesnt get deleted, we only delete from plugin.awsBucket.
347
363
  if (!unused.length) return
348
364
  await new Promise((resolve, reject) => {
349
- plugin.s3.deleteObjects({ Bucket: plugin.awsBucket, Delete: { Objects: unused }}, (err, data) => {
365
+ plugin.s3.deleteObjects({
366
+ Bucket: plugin.awsBucket,
367
+ Delete: { Objects: unused }
368
+ }, (err, data) => {
350
369
  if (err) reject(err)
351
370
  resolve()
352
371
  })
@@ -410,7 +429,7 @@ let plugin = module.exports = {
410
429
  return Promise.all(filesArr.map((file, i) => {
411
430
  return new Promise((resolve, reject) => {
412
431
  fileType.fromBuffer(file.data).then(res => {
413
- let maxSize = filesArr.imageField.filesize
432
+ let filesize = filesArr.imageField.filesize || plugin.filesize
414
433
  let formats = filesArr.imageField.formats || plugin.formats
415
434
  let allowAny = util.inArray(formats, 'any')
416
435
  file.format = res? res.ext : ''
@@ -421,9 +440,9 @@ let plugin = module.exports = {
421
440
  title: filesArr.inputPath + (i? `.${i}` : ''),
422
441
  detail: `The file size for '${file.nameClipped}' is too big.`
423
442
  })
424
- else if (maxSize && maxSize < file.size) reject({ // file.size == bytes
443
+ else if (filesize && filesize < file.size) reject({ // file.size == bytes
425
444
  title: filesArr.inputPath + (i? `.${i}` : ''),
426
- detail: `The file size for '${file.nameClipped}' is bigger than ${(maxSize/1000/1000).toFixed(1)}MB.`
445
+ detail: `The file size for '${file.nameClipped}' is bigger than ${(filesize/1000/1000).toFixed(1)}MB.`
427
446
  })
428
447
  else if (file.ext == 'unknown') reject({
429
448
  title: filesArr.inputPath + (i? `.${i}` : ''),
@@ -446,8 +465,10 @@ let plugin = module.exports = {
446
465
  * @param {object|array} fields
447
466
  * @param {string} path
448
467
  * @return [{}, ...]
468
+ * @this plugin
449
469
  */
450
470
  let list = []
471
+ let that = this
451
472
  util.forEach(fields, (field, fieldName) => {
452
473
  let path2 = `${path}.${fieldName}`.replace(/^\./, '')
453
474
  // let schema = field.schema || {}
@@ -464,11 +485,28 @@ let plugin = module.exports = {
464
485
 
465
486
  // Image field. Test for field.image as field.type may be 'any'
466
487
  } else if (field.type == 'image' || field.image) {
467
- let formats = field.formats
468
- let filesize = field.filesize || field.fileSize // old <= v1.31.7
469
- let filename = field.filename
470
- let getSignedUrl = field.getSignedUrl
471
- let params = { ...field.params||{} }
488
+ if (field.fileSize) { // > v1.31.7
489
+ this.manager.warn(`${path2}.fileSize has been depreciated in favour of ${path2}.filesize`)
490
+ field.filesize = field.filesize || field.fileSize
491
+ }
492
+ if (field.filename) { // > v1.36.3
493
+ this.manager.warn(`${path2}.filename has been depreciated in favour of ${path2}.path()`)
494
+ field.path = field.path
495
+ || function(uid, basename, ext, file) { return `${that.bucketDir}/${uid}/${field.filename}.${ext}` }
496
+ }
497
+
498
+ list.push({
499
+ awsAcl: field.awsAcl,
500
+ awsBucket: field.awsBucket,
501
+ filename: field.filename,
502
+ filesize: field.filesize,
503
+ formats: field.formats,
504
+ fullPath: path2,
505
+ fullPathRegex: new RegExp('^' + path2.replace(/\.[0-9]+/g, '.[0-9]+').replace(/\./g, '\\.') + '$'),
506
+ getSignedUrl: field.getSignedUrl,
507
+ path: field.path,
508
+ params: field.params ? util.deepCopy(field.params) : undefined,
509
+ })
472
510
  // Convert image field to subdocument
473
511
  fields[fieldName] = {
474
512
  bucket: { type: 'string' },
@@ -479,15 +517,6 @@ let plugin = module.exports = {
479
517
  schema: { image: true, nullObject: true, isImageObject: true },
480
518
  uid: { type: 'string' },
481
519
  }
482
- list.push({
483
- fullPath: path2,
484
- fullPathRegex: new RegExp('^' + path2.replace(/\.[0-9]+/g, '.[0-9]+').replace(/\./g, '\\.') + '$'),
485
- formats: formats,
486
- filesize: filesize,
487
- filename: filename,
488
- getSignedUrl: getSignedUrl,
489
- params: params,
490
- })
491
520
  }
492
521
  })
493
522
  return list
@@ -537,12 +566,17 @@ let plugin = module.exports = {
537
566
  return list
538
567
  },
539
568
 
540
- _getSignedUrl: (path, expires=3600) => {
541
- // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
569
+ _getSignedUrl: (path, expires=3600, bucket) => {
570
+ /**
571
+ * @param {string} path - aws file path
572
+ * @param {number} <expires> - seconds
573
+ * @param {number} <bucket>
574
+ * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
575
+ */
542
576
  let signedUrl = plugin.s3.getSignedUrl('getObject', {
543
- Bucket: plugin.awsBucket,
577
+ Bucket: bucket || plugin.awsBucket,
578
+ Expires: expires,
544
579
  Key: path,
545
- Expires: expires
546
580
  })
547
581
  return signedUrl
548
582
  },
Binary file
package/test/crud.js CHANGED
@@ -309,6 +309,77 @@ module.exports = function(monastery, opendb) {
309
309
  db.close()
310
310
  })
311
311
 
312
+ test('update mixing formData', async() => {
313
+ // Mixing data
314
+ let db = (await opendb(null)).db
315
+ let consignment = db.model('consignment', {
316
+ fields: {
317
+ specialInstructions: [{
318
+ text: { type: 'string' },
319
+ createdAt: { type: 'date' },
320
+ updatedByName: { type: 'string' },
321
+ importance: { type: 'string' },
322
+ }],
323
+ },
324
+ })
325
+ let inserted = await consignment.insert({
326
+ data: {
327
+ specialInstructions: [{
328
+ text: 'filler',
329
+ createdAt: 1653601752472,
330
+ updatedByName: 'Paul',
331
+ importance: 'low'
332
+ }]
333
+ }
334
+ })
335
+ let specialInstructions = [
336
+ {
337
+ text: 'POD added by driver',
338
+ createdAt: 1653603212886,
339
+ updatedByName: 'Paul Driver 3',
340
+ importance: 'low'
341
+ }, {
342
+ text: 'filler',
343
+ createdAt: 1653601752472,
344
+ updatedByName: 'Paul',
345
+ importance: 'low'
346
+ }
347
+ ]
348
+ // Key order maintained (not always guaranteed in browsers)
349
+ await consignment.update({
350
+ query: inserted._id,
351
+ data: {
352
+ 'specialInstructions[0][text]': 'filler',
353
+ 'specialInstructions[0][createdAt]': 1653601752472,
354
+ 'specialInstructions[0][updatedByName]': 'Paul',
355
+ 'specialInstructions[0][importance]': 'low',
356
+ specialInstructions: specialInstructions.map(o => ({ ...o })),
357
+ }
358
+ })
359
+ await expect(consignment.findOne({ query: inserted._id })).resolves.toEqual({
360
+ _id: expect.any(Object),
361
+ specialInstructions: specialInstructions,
362
+ })
363
+
364
+ // Key order maintained (not always guaranteed in browsers)
365
+ await consignment.update({
366
+ query: inserted._id,
367
+ data: {
368
+ specialInstructions: specialInstructions.map(o => ({ ...o })),
369
+ 'specialInstructions[0][text]': 'filler',
370
+ 'specialInstructions[0][createdAt]': 1653601752472,
371
+ 'specialInstructions[0][updatedByName]': 'Paul',
372
+ 'specialInstructions[0][importance]': 'low',
373
+ }
374
+ })
375
+ await expect(consignment.findOne({ query: inserted._id })).resolves.toEqual({
376
+ _id: expect.any(Object),
377
+ specialInstructions: [specialInstructions[1], specialInstructions[1]],
378
+ })
379
+
380
+ db.close()
381
+ })
382
+
312
383
  test('insert with id casting', async () => {
313
384
  let db = (await opendb(null)).db
314
385
  db.model('company', { fields: {
@@ -335,7 +406,7 @@ module.exports = function(monastery, opendb) {
335
406
 
336
407
  test('find default field population', async () => {
337
408
  let db = (await opendb(null)).db
338
- let user = db.model('user', {
409
+ db.model('user', {
339
410
  fields: {
340
411
  name: { type: 'string', default: 'Martin Luther' },
341
412
  addresses: [{ city: { type: 'string' }, country: { type: 'string', default: 'Germany' } }],
@@ -344,7 +415,7 @@ module.exports = function(monastery, opendb) {
344
415
  dogs: [{ model: 'dog' }], // virtual association
345
416
  }
346
417
  })
347
- let dog = db.model('dog', {
418
+ db.model('dog', {
348
419
  fields: {
349
420
  name: { type: 'string', default: 'Scruff' },
350
421
  user: { model: 'user' }
@@ -352,35 +423,35 @@ module.exports = function(monastery, opendb) {
352
423
  })
353
424
 
354
425
  // Default field doesn't override null
355
- let nulldoc1 = await dog.insert({ data: { name: null }})
356
- let nullfind1 = await dog.findOne({ query: nulldoc1._id })
426
+ let nulldoc1 = await db.dog.insert({ data: { name: null }})
427
+ let nullfind1 = await db.dog.findOne({ query: nulldoc1._id })
357
428
  expect(nullfind1).toEqual({ _id: nulldoc1._id, name: null })
358
429
 
359
430
  // Default field doesn't override empty string
360
- let nulldoc2 = await dog.insert({ data: { name: '' }})
361
- let nullfind2 = await dog.findOne({ query: nulldoc2._id })
431
+ let nulldoc2 = await db.dog.insert({ data: { name: '' }})
432
+ let nullfind2 = await db.dog.findOne({ query: nulldoc2._id })
362
433
  expect(nullfind2).toEqual({ _id: nulldoc2._id, name: '' })
363
434
 
364
435
  // Default field overrides undefined
365
- let nulldoc3 = await dog.insert({ data: { name: undefined }})
366
- let nullfind3 = await dog.findOne({ query: nulldoc3._id })
436
+ let nulldoc3 = await db.dog.insert({ data: { name: undefined }})
437
+ let nullfind3 = await db.dog.findOne({ query: nulldoc3._id })
367
438
  expect(nullfind3).toEqual({ _id: nullfind3._id, name: 'Scruff' })
368
439
 
369
440
  // Default field population test
370
- // Note that addresses.1.country shouldn't be overriden
441
+ // Note that addresses.1.country shouldn't be overridden
371
442
  // Insert documents (without defaults)
372
- let inserted = await dog._insert({})
373
- let inserted2 = await user._insert({
443
+ let dog1 = await db.dog._insert({})
444
+ let user1 = await db.user._insert({
374
445
  addresses: [
375
446
  { city: 'Frankfurt' },
376
447
  { city: 'Christchurch', country: 'New Zealand' }
377
448
  ],
378
- pet: { dog: inserted._id }
449
+ pet: { dog: dog1._id }
379
450
  })
380
- await dog._update(inserted._id, { $set: { user: inserted2._id }})
451
+ await db.dog._update(dog1._id, { $set: { user: user1._id }})
381
452
 
382
- let find1 = await user.findOne({
383
- query: inserted2._id,
453
+ let find1 = await db.user.findOne({
454
+ query: user1._id,
384
455
  populate: ['pet.dog', {
385
456
  from: 'dog',
386
457
  localField: '_id',
@@ -389,20 +460,20 @@ module.exports = function(monastery, opendb) {
389
460
  }]
390
461
  })
391
462
  expect(find1).toEqual({
392
- _id: inserted2._id,
463
+ _id: user1._id,
393
464
  name: 'Martin Luther',
394
465
  addresses: [
395
466
  { city: 'Frankfurt', country: 'Germany' },
396
467
  { city: 'Christchurch', country: 'New Zealand' }
397
468
  ],
398
469
  address: { country: 'Germany' },
399
- pet: { dog: { _id: inserted._id, name: 'Scruff', user: inserted2._id }},
400
- dogs: [{ _id: inserted._id, name: 'Scruff', user: inserted2._id }]
470
+ pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
471
+ dogs: [{ _id: dog1._id, name: 'Scruff', user: user1._id }]
401
472
  })
402
473
 
403
474
  // Blacklisted default field population test
404
- let find2 = await user.findOne({
405
- query: inserted2._id,
475
+ let find2 = await db.user.findOne({
476
+ query: user1._id,
406
477
  populate: ['pet.dog', {
407
478
  from: 'dog',
408
479
  localField: '_id',
@@ -413,11 +484,11 @@ module.exports = function(monastery, opendb) {
413
484
  // ^ great test (address should cancel addresses if not stopping at the .)
414
485
  })
415
486
  expect(find2).toEqual({
416
- _id: inserted2._id,
487
+ _id: user1._id,
417
488
  name: 'Martin Luther',
418
489
  addresses: [{ city: 'Frankfurt' }, { city: 'Christchurch' }],
419
- pet: { dog: { _id: inserted._id, name: 'Scruff', user: inserted2._id }},
420
- dogs: [{ _id: inserted._id, user: inserted2._id }]
490
+ pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
491
+ dogs: [{ _id: dog1._id, user: user1._id }]
421
492
  })
422
493
 
423
494
  db.close()
package/test/model.js CHANGED
@@ -117,8 +117,7 @@ module.exports = function(monastery, opendb) {
117
117
 
118
118
  test('model reserved rules', async () => {
119
119
  // Setup
120
- let db = (await opendb(false, {})).db
121
- db.error = () => {} // hiding debug error
120
+ let db = (await opendb(false, { hideErrors: true })).db // hide debug error
122
121
  let user = db.model('user', {
123
122
  fields: {
124
123
  name: {