monastery 1.30.0 → 1.30.1

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/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.30.0",
5
+ "version": "1.30.1",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -54,8 +54,8 @@ let plugin = module.exports = {
54
54
  model.imageFields = plugin._findAndTransformImageFields(model.fields, '')
55
55
 
56
56
  if (model.imageFields.length) {
57
- // Update image fields and whitelists with the new object schema
58
- // model._setupFieldsAndWhitelists(model.fields)
57
+ // Todo?: Update image fields and whitelists with the new object schema
58
+ // model._setupFieldsAndWhitelists(model.fields)
59
59
  model.beforeUpdate.push(function(data, n) {
60
60
  plugin.removeImages(this, data).then(() => n(null, data)).catch(e => n(e))
61
61
  })
@@ -91,7 +91,9 @@ let plugin = module.exports = {
91
91
  * @param {object} options - monastery operation options {model, query, files, ..}
92
92
  * @param {object} data -
93
93
  * @param {boolean} test -
94
- * @return promise
94
+ * @return promise(
95
+ * {object} data - data object containing new S3 image-object
96
+ * ])
95
97
  * @this plugin
96
98
  */
97
99
  let { model, query, files } = options
@@ -167,7 +169,7 @@ let plugin = module.exports = {
167
169
  })
168
170
  },
169
171
 
170
- removeImages: function(options, data, test) {
172
+ removeImages: async function(options, data, test) {
171
173
  /**
172
174
  * Hook before update/remove
173
175
  * Removes images not found in data, this means you will need to pass the image objects to every update operation
@@ -179,108 +181,113 @@ let plugin = module.exports = {
179
181
  *
180
182
  * @ref https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObjects-property
181
183
  * @param {object} options - monastery operation options {query, model, files, multi, ..}
182
- * @return promise
184
+ * @return promise([
185
+ * {object} useCount - images that wont be removed, e.g. { lion1: 1 }
186
+ * {array} unused - S3 image uris to be removed, e.g. [{ Key: 'small/lion1.jpg' }, ..]
187
+ * ])
183
188
  * @this plugin
184
189
  */
185
190
  let pre
186
191
  let preExistingImages = []
187
192
  let useCount = {}
188
- if (typeof options.files == 'undefined') return new Promise(res => res())
193
+ if (typeof options.files == 'undefined') return
189
194
 
190
195
  // Find all documents from the same query
191
- return options.model._find(options.query, options)
192
- .then(docs => {
193
- // Find all pre-existing image objects in documents
194
- for (let doc of util.toArray(docs)) { //x2
195
- for (let imageField of options.model.imageFields) { //x5
196
- let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
197
- for (let image of images) {
198
- preExistingImages.push(image)
199
- useCount[image.image.uid] = (useCount[image.image.uid] || 0) + 1
200
- }
201
- }
196
+ let docs = await options.model._find(options.query, options)
197
+
198
+ // Find all pre-existing image objects in documents
199
+ for (let doc of util.toArray(docs)) { //x2
200
+ for (let imageField of options.model.imageFields) { //x5
201
+ let images = plugin._findImagesInData(doc, imageField, 0, '').filter(o => o.image)
202
+ for (let image of images) {
203
+ preExistingImages.push(image)
204
+ useCount[image.image.uid] = (useCount[image.image.uid] || 0) + 1
202
205
  }
206
+ }
207
+ }
203
208
 
204
- // console.log(1, useCount, preExistingImages)
209
+ // console.log(1, useCount, preExistingImages)
205
210
 
206
- // Assign pre-existing images within undefined deep objects and missing array items to null
207
- // ignore undefined root images
208
- let dataFilled = util.deepCopy(data)
209
- for (let key in dataFilled) {
210
- for (let pre of preExistingImages) {
211
- if (!pre.dataPath.match(new RegExp('^' + key + '(\\.|$)'))) continue
212
- util.setDeepValue(dataFilled, pre.dataPath, null, true)
213
- }
214
- }
211
+ // Assign pre-existing images within undefined deep objects and missing array items to null,
212
+ // ignore undefined root images
213
+ let dataFilled = util.deepCopy(data)
214
+ for (let key in dataFilled) {
215
+ for (let pre of preExistingImages) {
216
+ if (!pre.dataPath.match(new RegExp('^' + key + '(\\.|$)'))) continue
217
+ util.setDeepValue(dataFilled, pre.dataPath, null, true)
218
+ }
219
+ }
220
+ // console.log(dataFilled)
215
221
 
216
- // Loop all schema image fields
217
- for (let imageField of options.model.imageFields) { //x5
218
- let images = plugin._findImagesInData(dataFilled, imageField, 0, '')
219
- if (!images.length) continue
220
- // console.log(images)
222
+ // Check upload errors and find valid uploaded images
223
+ let files = await plugin._findValidImages(options.files || {}, options.model)
221
224
 
222
- // Data contains null images that once had a pre-existing image
223
- for (let image of images) {
224
- if (image.image == null && (pre = preExistingImages.find(o => o.dataPath == image.dataPath))) {
225
- useCount[pre.image.uid]--
226
- }
227
- }
225
+ // Loop all schema image fields
226
+ for (let imageField of options.model.imageFields) { //x5
227
+ let images = plugin._findImagesInData(dataFilled, imageField, 0, '')
228
+ if (!images.length) continue
229
+ // console.log(images)
228
230
 
229
- // Data contains valid (pre-existing) image-objects? And are we overriding a pre-existing image?
230
- for (let image of images) {
231
- if (image.image != null) {
232
- let pre = preExistingImages.find(o => o.dataPath == image.dataPath)
233
- if (typeof useCount[image.image.uid] == 'undefined') {
234
- throw `The passed image object for '${image.dataPath}' does not match any pre-existing
235
- images saved on this document.`
236
- } else if (pre && pre.image.uid != image.image.uid) {
237
- useCount[pre.image.uid]--
238
- useCount[image.image.uid]++
239
- } else if (!pre) {
240
- useCount[image.image.uid]++
241
- }
242
- }
243
- }
231
+ // Data contains null images that once had a pre-existing image
232
+ for (let image of images) {
233
+ if (image.image == null && (pre = preExistingImages.find(o => o.dataPath == image.dataPath))) {
234
+ useCount[pre.image.uid]--
244
235
  }
245
- // Check upload errors and find valid uploaded images. If any file is overriding a
246
- // pre-existing image, push to unused
247
- return plugin._findValidImages(options.files || {}, options.model).then(files => {
236
+ }
237
+
238
+ // Loop images found in the data
239
+ for (let image of images) {
240
+ if (image.image != null) {
241
+ let preExistingImage = preExistingImages.find(o => o.dataPath == image.dataPath)
242
+ // valid image-object?
243
+ if (typeof useCount[image.image.uid] == 'undefined') {
244
+ throw `The passed image object for '${image.dataPath}' does not match any pre-existing
245
+ images saved on this document.`
246
+ // Different image from prexisting image
247
+ } else if (preExistingImage && preExistingImage.image.uid != image.image.uid) {
248
+ useCount[preExistingImage.image.uid]--
249
+ useCount[image.image.uid]++
250
+ // No pre-existing image found
251
+ } else if (!preExistingImage) {
252
+ useCount[image.image.uid]++
253
+ }
254
+ // Any file overriding this image?
248
255
  for (let filesArray of files) {
249
- if ((pre = preExistingImages.find(o => o.dataPath == filesArray.inputPath))) {
250
- useCount[pre.image.uid]--
256
+ if (image.dataPath == filesArray.inputPath) {
257
+ useCount[image.image.uid]--
251
258
  }
252
259
  }
253
- })
254
-
255
- }).then(() => {
256
- // Retrieve all the unused files
257
- let unused = []
258
- // console.log(3, useCount)
259
- for (let key in useCount) {
260
- if (useCount[key] > 0) continue
261
- let pre = preExistingImages.find(o => o.image.uid == key)
262
- unused.push(
263
- // original key can have a different extension
264
- { Key: pre.image.path },
265
- { Key: `small/${key}.jpg` },
266
- { Key: `medium/${key}.jpg` },
267
- { Key: `large/${key}.jpg` }
268
- )
269
- this.manager.info(
270
- `Removing '${pre.image.filename}' from '${pre.image.bucket}/${pre.image.path}'`
271
- )
272
260
  }
273
- if (test) return Promise.resolve([useCount, unused])
274
- // Delete any unused images from s3. If the image is on a different bucket
275
- // the file doesnt get deleted, we only delete from plugin.awsBucket.
276
- if (!unused.length) return
277
- return new Promise((resolve, reject) => {
278
- plugin.s3.deleteObjects({ Bucket: plugin.awsBucket, Delete: { Objects: unused }}, (err, data) => {
279
- if (err) reject(err)
280
- resolve()
281
- })
282
- })
261
+ }
262
+ }
263
+
264
+ // Retrieve all the unused files
265
+ // console.log(3, useCount)
266
+ let unused = []
267
+ for (let key in useCount) {
268
+ if (useCount[key] > 0) continue
269
+ let pre = preExistingImages.find(o => o.image.uid == key)
270
+ unused.push(
271
+ // original key can have a different extension
272
+ { Key: pre.image.path },
273
+ { Key: `small/${key}.jpg` },
274
+ { Key: `medium/${key}.jpg` },
275
+ { Key: `large/${key}.jpg` }
276
+ )
277
+ this.manager.info(
278
+ `Removing '${pre.image.filename}' from '${pre.image.bucket}/${pre.image.path}'`
279
+ )
280
+ }
281
+ if (test) return [useCount, unused]
282
+ // Delete any unused images from s3. If the image is on a different bucket
283
+ // the file doesnt get deleted, we only delete from plugin.awsBucket.
284
+ if (!unused.length) return
285
+ await new Promise((resolve, reject) => {
286
+ plugin.s3.deleteObjects({ Bucket: plugin.awsBucket, Delete: { Objects: unused }}, (err, data) => {
287
+ if (err) reject(err)
288
+ resolve()
283
289
  })
290
+ })
284
291
  },
285
292
 
286
293
  _addImageObjectsToData: function(path, data, image) {
@@ -299,13 +299,8 @@ module.exports = function(monastery, opendb) {
299
299
 
300
300
  plugin.removeImages(options, req.body, true)
301
301
  .then(res => {
302
- expect(res[0]).toEqual({ test1: 0, test2: -1, test3: 1, test4: 0 })
302
+ expect(res[0]).toEqual({ test1: 1, test2: 0, test3: 1, test4: 0 })
303
303
  expect(res[1]).toEqual([
304
- { Key: 'dir/test1.png' },
305
- { Key: 'small/test1.jpg' },
306
- { Key: 'medium/test1.jpg' },
307
- { Key: 'large/test1.jpg' },
308
-
309
304
  { Key: 'dir/test2.png' },
310
305
  { Key: 'small/test2.jpg' },
311
306
  { Key: 'medium/test2.jpg' },
@@ -560,4 +555,139 @@ module.exports = function(monastery, opendb) {
560
555
  })()
561
556
  })
562
557
 
558
+ test('images reorder', async () => {
559
+ let db = (await opendb(null, {
560
+ timestamps: false,
561
+ serverSelectionTimeoutMS: 2000,
562
+ imagePlugin: { awsBucket: 'fake', awsAccessKeyId: 'fake', awsSecretAccessKey: 'fake' }
563
+ })).db
564
+
565
+ let user = db.model('user', { fields: {
566
+ logos: [{ type: 'image' }],
567
+ }})
568
+
569
+ let image = {
570
+ bucket: 'test',
571
+ date: 1234,
572
+ filename: 'lion1.png',
573
+ filesize: 1234,
574
+ path: 'test/lion1.png',
575
+ uid: 'lion1'
576
+ }
577
+
578
+ let user1 = await db.user._insert({
579
+ logos: [image],
580
+ })
581
+
582
+ let plugin = db.imagePluginFile
583
+ let supertest = require('supertest')
584
+ let express = require('express')
585
+ let upload = require('express-fileupload')
586
+ let app = express()
587
+ app.use(upload())
588
+
589
+ // Reorder
590
+ app.post('/', async function(req, res) {
591
+ try {
592
+ req.body.logos = JSON.parse(req.body.logos)
593
+ let options = { files: req.files, model: user, query: { _id: user1._id } }
594
+ let response = await plugin.removeImages(options, req.body, true)
595
+ expect(response[0]).toEqual({ lion1: 1 })
596
+ expect(response[1]).toEqual([])
597
+ res.send()
598
+ } catch (e) {
599
+ console.log(e.message || e)
600
+ res.status(500).send()
601
+ }
602
+ })
603
+ await supertest(app)
604
+ .post('/')
605
+ .field('logos', JSON.stringify([ null, image ]))
606
+ .expect(200)
607
+
608
+ db.close()
609
+ })
610
+
611
+ test('images reorder and added image', async () => {
612
+ // latest (2022.02)
613
+ let db = (await opendb(null, {
614
+ timestamps: false,
615
+ serverSelectionTimeoutMS: 2000,
616
+ imagePlugin: { awsBucket: 'fake', awsAccessKeyId: 'fake', awsSecretAccessKey: 'fake' }
617
+ })).db
618
+
619
+ let user = db.model('user', { fields: {
620
+ logos: [{ type: 'image' }],
621
+ }})
622
+
623
+ let image = {
624
+ bucket: 'test',
625
+ date: 1234,
626
+ filename: 'lion1.png',
627
+ filesize: 1234,
628
+ path: 'test/lion1.png',
629
+ uid: 'lion1'
630
+ }
631
+
632
+ let user1 = await db.user._insert({
633
+ logos: [image],
634
+ })
635
+
636
+ let plugin = db.imagePluginFile
637
+ let supertest = require('supertest')
638
+ let express = require('express')
639
+ let upload = require('express-fileupload')
640
+ let app = express()
641
+ app.use(upload())
642
+
643
+ app.post('/', async function(req, res) {
644
+ try {
645
+ req.body.logos = JSON.parse(req.body.logos)
646
+ let options = { files: req.files, model: user, query: { _id: user1._id } }
647
+
648
+ // Remove images
649
+ let response = await plugin.removeImages(options, req.body, true)
650
+ expect(response[0]).toEqual({ lion1: 1 }) // useCount
651
+ expect(response[1]).toEqual([]) // unused
652
+
653
+ // File exists
654
+ let validFiles = await plugin._findValidImages(req.files, user)
655
+ expect(((validFiles||[])[0]||{}).inputPath).toEqual('logos.0') // Valid inputPath
656
+
657
+ // Add images
658
+ response = await plugin.addImages(options, req.body, true)
659
+ expect(response[0]).toEqual({
660
+ logos: [{
661
+ bucket: 'fake',
662
+ date: expect.any(Number),
663
+ filename: 'lion2.jpg',
664
+ filesize: expect.any(Number),
665
+ path: expect.any(String),
666
+ uid: expect.any(String)
667
+ }, {
668
+ bucket: 'test', // still the same image-object reference (nothing new)
669
+ date: expect.any(Number),
670
+ filename: 'lion1.png',
671
+ filesize: expect.any(Number),
672
+ path: expect.any(String),
673
+ uid: expect.any(String)
674
+ },],
675
+ })
676
+
677
+ res.send()
678
+ } catch (e) {
679
+ console.log(e.message || e)
680
+ res.status(500).send()
681
+ }
682
+ })
683
+
684
+ await supertest(app)
685
+ .post('/')
686
+ .field('logos', JSON.stringify([ null, image ]))
687
+ .attach('logos.0', `${__dirname}/assets/lion2.jpg`)
688
+ .expect(200)
689
+
690
+ db.close()
691
+ })
692
+
563
693
  }