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 +1 -1
- package/plugins/images/index.js +94 -87
- package/test/plugin-images.js +136 -6
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.
|
|
5
|
+
"version": "1.30.1",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/plugins/images/index.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
193
|
+
if (typeof options.files == 'undefined') return
|
|
189
194
|
|
|
190
195
|
// Find all documents from the same query
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
209
|
+
// console.log(1, useCount, preExistingImages)
|
|
205
210
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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 (
|
|
250
|
-
useCount[
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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) {
|
package/test/plugin-images.js
CHANGED
|
@@ -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:
|
|
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
|
}
|