monastery 1.28.4 → 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/.eslintrc.json +30 -4
- package/docs/errors.md +0 -0
- package/docs/image-plugin.md +0 -0
- package/docs/manager/model.md +0 -0
- package/docs/manager/models.md +0 -0
- package/docs/model/findOne.md +0 -0
- package/docs/model/index.md +0 -0
- package/docs/model/remove.md +0 -0
- package/docs/readme.md +1 -1
- package/docs/rules.md +0 -0
- package/lib/index.js +0 -1
- package/lib/model-crud.js +33 -17
- package/lib/model-validate.js +25 -24
- package/lib/model.js +23 -12
- package/lib/rules.js +38 -38
- package/lib/util.js +13 -7
- package/package.json +16 -15
- package/plugins/images/index.js +97 -90
- package/test/assets/bad.svg +0 -0
- package/test/assets/image.ico +0 -0
- package/test/assets/image.webp +0 -0
- package/test/assets/lion1.png +0 -0
- package/test/assets/lion2.jpg +0 -0
- package/test/assets/logo.png +0 -0
- package/test/assets/logo2.png +0 -0
- package/test/blacklisting.js +4 -8
- package/test/crud.js +28 -31
- package/test/model.js +114 -28
- package/test/monk.js +4 -4
- package/test/plugin-images.js +486 -346
- package/test/populate.js +15 -18
- package/test/util.js +8 -8
- package/test/validate.js +227 -108
- package/test/virtuals.js +2 -4
package/lib/rules.js
CHANGED
|
@@ -5,6 +5,8 @@ let validator = require('validator')
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
|
|
7
7
|
required: {
|
|
8
|
+
validateUndefined: true,
|
|
9
|
+
validateEmptyString: true,
|
|
8
10
|
message: 'This field is required.',
|
|
9
11
|
fn: function(x) {
|
|
10
12
|
if (util.isArray(x) && !x.length) return false
|
|
@@ -12,9 +14,10 @@ module.exports = {
|
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
|
|
15
|
-
//
|
|
17
|
+
// Type rules below ignore undefined (default for custom model rules)
|
|
16
18
|
|
|
17
19
|
'isBoolean': {
|
|
20
|
+
validateEmptyString: true,
|
|
18
21
|
message: 'Value was not a boolean.',
|
|
19
22
|
tryParse: function(x) {
|
|
20
23
|
if (typeof x === 'string' && x === 'true') return true
|
|
@@ -25,19 +28,13 @@ module.exports = {
|
|
|
25
28
|
return typeof x === 'boolean'
|
|
26
29
|
}
|
|
27
30
|
},
|
|
28
|
-
'isNotEmptyString': {
|
|
29
|
-
message: 'Value was an empty string.',
|
|
30
|
-
fn: function(x) {
|
|
31
|
-
return x !== ''
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
31
|
'isArray': {
|
|
32
|
+
validateEmptyString: true,
|
|
35
33
|
message: 'Value was not an array.',
|
|
36
34
|
tryParse: function(x) {
|
|
37
35
|
if (x === '') return null
|
|
38
36
|
if (!util.isString(x)) return x
|
|
39
|
-
try { var parsed = JSON.parse(x) }
|
|
40
|
-
catch (e) { return x }
|
|
37
|
+
try { var parsed = JSON.parse(x) } catch (e) { return x }
|
|
41
38
|
return Array.isArray(parsed)? parsed : x
|
|
42
39
|
},
|
|
43
40
|
fn: function(x) {
|
|
@@ -45,25 +42,26 @@ module.exports = {
|
|
|
45
42
|
}
|
|
46
43
|
},
|
|
47
44
|
'isDate': {
|
|
45
|
+
validateEmptyString: true,
|
|
48
46
|
message: 'Value was not a unix timestamp.',
|
|
49
47
|
tryParse: function(x) {
|
|
50
|
-
if (util.isString(x) && x.match(/^[
|
|
48
|
+
if (util.isString(x) && x.match(/^[+-]?[0-9]+$/)) return x // keep string nums intact
|
|
51
49
|
return isNaN(parseInt(x))? x : parseInt(x)
|
|
52
50
|
},
|
|
53
51
|
fn: function(x) {
|
|
54
|
-
if (util.isString(x) && x.match(/^[
|
|
52
|
+
if (util.isString(x) && x.match(/^[+-]?[0-9]+$/)) return true
|
|
55
53
|
return typeof x === 'number' && (parseInt(x) === x)
|
|
56
54
|
}
|
|
57
55
|
},
|
|
58
56
|
'isImageObject': {
|
|
57
|
+
validateEmptyString: true,
|
|
59
58
|
message: 'Invalid image value',
|
|
60
59
|
messageLong: 'Image fields need to either be null, undefined, file, or an object containing the following '
|
|
61
60
|
+ 'fields \'{ bucket, date, filename, filesize, path, uid }\'',
|
|
62
61
|
tryParse: function(x) {
|
|
63
62
|
if (x === '') return null
|
|
64
63
|
if (!util.isString(x)) return x
|
|
65
|
-
try { var parsed = JSON.parse(x) }
|
|
66
|
-
catch (e) { return x }
|
|
64
|
+
try { var parsed = JSON.parse(x) } catch (e) { return x }
|
|
67
65
|
return parsed !== null && typeof parsed === 'object' && !(parsed instanceof Array)? parsed : x
|
|
68
66
|
},
|
|
69
67
|
fn: function(x) {
|
|
@@ -72,34 +70,36 @@ module.exports = {
|
|
|
72
70
|
}
|
|
73
71
|
},
|
|
74
72
|
'isInteger': {
|
|
73
|
+
validateEmptyString: true,
|
|
75
74
|
message: 'Value was not an integer.',
|
|
76
75
|
tryParse: function(x) {
|
|
77
|
-
if (util.isString(x) && x.match(/^[
|
|
76
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
|
|
78
77
|
return isNaN(parseInt(x)) || (!x && x!==0) || x===true? x : parseInt(x)
|
|
79
78
|
},
|
|
80
79
|
fn: function(x) {
|
|
81
|
-
if (util.isString(x) && x.match(/^[
|
|
80
|
+
if (util.isString(x) && x.match(/^[+-]?[0-9]+$/)) return true
|
|
82
81
|
return typeof x === 'number' && (parseInt(x) === x)
|
|
83
82
|
}
|
|
84
83
|
},
|
|
85
84
|
'isNumber': {
|
|
85
|
+
validateEmptyString: true,
|
|
86
86
|
message: 'Value was not a number.',
|
|
87
87
|
tryParse: function(x) {
|
|
88
|
-
if (util.isString(x) && x.match(/^[
|
|
88
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return x // keep string nums intact
|
|
89
89
|
return isNaN(Number(x)) || (!x && x!==0) || x===true? x : Number(x)
|
|
90
90
|
},
|
|
91
91
|
fn: function(x) {
|
|
92
|
-
if (util.isString(x) && x.match(/^[
|
|
92
|
+
if (util.isString(x) && x.match(/^[+-][0-9]+$/)) return true
|
|
93
93
|
return typeof x === 'number'
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
'isObject': {
|
|
97
|
+
validateEmptyString: true,
|
|
97
98
|
message: 'Value was not an object.',
|
|
98
99
|
tryParse: function(x) {
|
|
99
100
|
if (x === '') return null
|
|
100
101
|
if (!util.isString(x)) return x
|
|
101
|
-
try { var parsed = JSON.parse(x) }
|
|
102
|
-
catch (e) { return x }
|
|
102
|
+
try { var parsed = JSON.parse(x) } catch (e) { return x }
|
|
103
103
|
return parsed !== null && typeof parsed === 'object' && !(parsed instanceof Array)? parsed : x
|
|
104
104
|
},
|
|
105
105
|
fn: function(x) {
|
|
@@ -107,18 +107,21 @@ module.exports = {
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
'isString': {
|
|
110
|
+
validateEmptyString: true,
|
|
110
111
|
message: 'Value was not a string.',
|
|
111
112
|
fn: function(x) {
|
|
112
113
|
return typeof x === 'string'
|
|
113
114
|
}
|
|
114
115
|
},
|
|
115
116
|
'isAny': {
|
|
117
|
+
validateEmptyString: true,
|
|
116
118
|
message: '',
|
|
117
119
|
fn: function(x) {
|
|
118
120
|
return true
|
|
119
121
|
}
|
|
120
122
|
},
|
|
121
123
|
'isId': {
|
|
124
|
+
validateEmptyString: true,
|
|
122
125
|
message: 'Value was not a valid ObjectId.',
|
|
123
126
|
tryParse: function(x) {
|
|
124
127
|
// Try and parse value to a mongodb ObjectId
|
|
@@ -133,6 +136,7 @@ module.exports = {
|
|
|
133
136
|
}
|
|
134
137
|
},
|
|
135
138
|
'max': {
|
|
139
|
+
validateEmptyString: true,
|
|
136
140
|
message: (x, arg) => 'Value was greater than the configured maximum (' + arg + ')',
|
|
137
141
|
fn: function(x, arg) {
|
|
138
142
|
if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
|
|
@@ -140,16 +144,25 @@ module.exports = {
|
|
|
140
144
|
}
|
|
141
145
|
},
|
|
142
146
|
'min': {
|
|
147
|
+
validateEmptyString: true,
|
|
143
148
|
message: (x, arg) => 'Value was less than the configured minimum (' + arg + ')',
|
|
144
149
|
fn: function(x, arg) {
|
|
145
150
|
if (typeof x !== 'number') { throw new Error ('Value was not a number.') }
|
|
146
151
|
return x >= arg
|
|
147
152
|
}
|
|
148
153
|
},
|
|
154
|
+
'isNotEmptyString': {
|
|
155
|
+
validateEmptyString: true,
|
|
156
|
+
message: 'Value was an empty string.',
|
|
157
|
+
fn: function(x) {
|
|
158
|
+
return x !== ''
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Rules below ignore undefined, & empty strings
|
|
163
|
+
// (e.g. an empty email field can be saved that isn't required)
|
|
149
164
|
|
|
150
|
-
// Rules below ignore null & empty strings
|
|
151
165
|
'enum': {
|
|
152
|
-
ignoreEmptyString: true,
|
|
153
166
|
message: (x, arg) => 'Invalid enum value',
|
|
154
167
|
fn: function(x, arg) {
|
|
155
168
|
for (let item of arg) {
|
|
@@ -157,62 +170,51 @@ module.exports = {
|
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
},
|
|
160
|
-
'hasAgreed': {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
},
|
|
173
|
+
// 'hasAgreed': {
|
|
174
|
+
// message: (x, arg) => 'Please agree to the terms and conditions.',
|
|
175
|
+
// fn: function(x, arg) { return !x }
|
|
176
|
+
// },
|
|
164
177
|
'isAfter': {
|
|
165
|
-
ignoreEmptyString: true,
|
|
166
178
|
message: (x, arg) => 'Value was before the configured time (' + arg + ')',
|
|
167
179
|
fn: function(x, arg) { return validator.isAfter(x, arg) }
|
|
168
180
|
},
|
|
169
181
|
'isBefore': {
|
|
170
|
-
ignoreEmptyString: true,
|
|
171
182
|
message: (x, arg) => 'Value was after the configured time (' + arg + ')',
|
|
172
183
|
fn: function(x, arg) { return validator.isBefore(x, arg) }
|
|
173
184
|
},
|
|
174
185
|
'isCreditCard': {
|
|
175
|
-
ignoreEmptyString: true,
|
|
176
186
|
message: 'Value was not a valid credit card.',
|
|
177
187
|
fn: function(x, arg) { return validator.isCreditCard(x, arg) }
|
|
178
188
|
},
|
|
179
189
|
'isEmail': {
|
|
180
|
-
ignoreEmptyString: true,
|
|
181
190
|
message: 'Please enter a valid email address.',
|
|
182
191
|
fn: function(x, arg) { return validator.isEmail(x, arg) }
|
|
183
192
|
},
|
|
184
193
|
'isHexColor': {
|
|
185
|
-
ignoreEmptyString: true,
|
|
186
194
|
message: 'Value was not a valid hex color.',
|
|
187
195
|
fn: function(x, arg) { return validator.isHexColor(x, arg) }
|
|
188
196
|
},
|
|
189
197
|
'isIn': {
|
|
190
|
-
ignoreEmptyString: true,
|
|
191
198
|
message: (x, arg) => 'Value was not in the configured whitelist (' + arg.join(', ') + ')',
|
|
192
199
|
fn: function(x, arg) { return validator.isIn(x, arg) }
|
|
193
200
|
},
|
|
194
201
|
'isIP': {
|
|
195
|
-
ignoreEmptyString: true,
|
|
196
202
|
message: 'Value was not a valid IP address.',
|
|
197
203
|
fn: function(x, arg) { return validator.isIP(x, arg) }
|
|
198
204
|
},
|
|
199
205
|
'isNotIn': {
|
|
200
|
-
ignoreEmptyString: true,
|
|
201
206
|
message: (x, arg) => 'Value was in the configured blacklist (' + arg.join(', ') + ')',
|
|
202
207
|
fn: function(x, arg) { return !validator.isIn(x, arg) }
|
|
203
208
|
},
|
|
204
209
|
'isURL': {
|
|
205
|
-
ignoreEmptyString: true,
|
|
206
210
|
message: 'Value was not a valid URL.',
|
|
207
211
|
fn: function(x, arg) { return validator.isURL(x, arg === true? undefined : arg) }
|
|
208
212
|
},
|
|
209
213
|
'isUUID': {
|
|
210
|
-
ignoreEmptyString: true,
|
|
211
214
|
message: 'Value was not a valid UUID.',
|
|
212
215
|
fn: function(x, arg) { return validator.isUUID(x) }
|
|
213
216
|
},
|
|
214
217
|
'minLength': {
|
|
215
|
-
ignoreEmptyString: true,
|
|
216
218
|
message: function(x, arg) {
|
|
217
219
|
if (typeof x === 'string') return 'Value needs to be at least ' + arg + ' characters long.'
|
|
218
220
|
else return 'Value needs to contain a minimum of ' + arg + ' items.'
|
|
@@ -224,7 +226,6 @@ module.exports = {
|
|
|
224
226
|
}
|
|
225
227
|
},
|
|
226
228
|
'maxLength': {
|
|
227
|
-
ignoreEmptyString: true,
|
|
228
229
|
message: function(x, arg) {
|
|
229
230
|
if (typeof x === 'string') return 'Value was longer than the configured maximum length (' + arg + ')'
|
|
230
231
|
else return 'Value cannot contain more than ' + arg + ' items.'
|
|
@@ -236,7 +237,6 @@ module.exports = {
|
|
|
236
237
|
}
|
|
237
238
|
},
|
|
238
239
|
'regex': {
|
|
239
|
-
ignoreEmptyString: true,
|
|
240
240
|
message: (x, arg) => 'Value did not match the configured regular expression (' + arg + ')',
|
|
241
241
|
fn: function(x, arg) {
|
|
242
242
|
if (util.isRegex(arg)) return validator.matches(x, arg)
|
package/lib/util.js
CHANGED
|
@@ -9,7 +9,7 @@ module.exports = {
|
|
|
9
9
|
for (let key in obj) {
|
|
10
10
|
let v = obj[key]
|
|
11
11
|
if (this.isId(v)) obj2[key] = v.toString()
|
|
12
|
-
else obj2[key] = (typeof v ===
|
|
12
|
+
else obj2[key] = (typeof v === 'object')? this.deepCopy(v) : v
|
|
13
13
|
}
|
|
14
14
|
return obj2
|
|
15
15
|
},
|
|
@@ -165,7 +165,7 @@ module.exports = {
|
|
|
165
165
|
* @param {object}
|
|
166
166
|
*/
|
|
167
167
|
for (let key in obj) {
|
|
168
|
-
if (key.indexOf(
|
|
168
|
+
if (key.indexOf('.') !== -1) recurse(key, obj[key], obj)
|
|
169
169
|
}
|
|
170
170
|
return obj
|
|
171
171
|
function recurse(str, val, obj) {
|
|
@@ -199,8 +199,12 @@ module.exports = {
|
|
|
199
199
|
*/
|
|
200
200
|
return new Promise(res => {
|
|
201
201
|
for (let key in obj) {
|
|
202
|
-
if (key.match(/\[\]\[/i))
|
|
203
|
-
|
|
202
|
+
if (key.match(/\[\]\[/i)) {
|
|
203
|
+
throw `Array items in bracket notation need array indexes "${key}", e.g. users[0][name]`
|
|
204
|
+
}
|
|
205
|
+
if (key.indexOf('[') !== -1) {
|
|
206
|
+
setup(key)
|
|
207
|
+
}
|
|
204
208
|
}
|
|
205
209
|
res(obj)
|
|
206
210
|
})
|
|
@@ -241,7 +245,7 @@ module.exports = {
|
|
|
241
245
|
removeUndefined: (variable) => {
|
|
242
246
|
// takes an array or object
|
|
243
247
|
if (Array.isArray(variable)) {
|
|
244
|
-
for (let
|
|
248
|
+
for (let i=variable.length; i--;) {
|
|
245
249
|
if (variable[i] === undefined) variable.splice(i, 1)
|
|
246
250
|
}
|
|
247
251
|
} else {
|
|
@@ -308,7 +312,7 @@ module.exports = {
|
|
|
308
312
|
target[chunks[i]] = value
|
|
309
313
|
}
|
|
310
314
|
} else {
|
|
311
|
-
let isArray = chunks[i+1].match(/^[0-9
|
|
315
|
+
let isArray = chunks[i+1].match(/^[0-9$]+$/)
|
|
312
316
|
let parentCopy = target[chunks[i]] || (isArray? [] : {})
|
|
313
317
|
if (ignoreEmptyArrays && isArray && !parentCopy.length) break
|
|
314
318
|
target = target[chunks[i]] = parentCopy
|
|
@@ -316,7 +320,9 @@ module.exports = {
|
|
|
316
320
|
if (chunks[i+1] == '$') {
|
|
317
321
|
for (let m=0, n=target.length; m<n; m++) {
|
|
318
322
|
let newPath = chunks.slice(i+2).join('.')
|
|
319
|
-
if (newPath)
|
|
323
|
+
if (newPath) {
|
|
324
|
+
this.setDeepValue(target[m], newPath, value, onlyUndefined, onlyUndefinedNull, ignoreEmptyArrays)
|
|
325
|
+
}
|
|
320
326
|
}
|
|
321
327
|
break
|
|
322
328
|
}
|
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.
|
|
5
|
+
"version": "1.30.1",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
|
@@ -19,27 +19,28 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"minor": "standard-version --release-as minor && npm publish",
|
|
21
21
|
"patch": "standard-version && npm publish",
|
|
22
|
-
"test": "jest",
|
|
22
|
+
"test": "npm run lint && jest",
|
|
23
23
|
"test-one-example": "jest -t images",
|
|
24
|
-
"dev": "DEBUG=-monastery:info jest --watchAll --runInBand --verbose=false",
|
|
24
|
+
"dev": "npm run lint & DEBUG=-monastery:info jest --watchAll --runInBand --verbose=false",
|
|
25
|
+
"lint": "eslint ./lib ./plugins ./test",
|
|
25
26
|
"docs": "cd docs && bundle exec jekyll serve --livereload --livereload-port 4001"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"aws-sdk": "
|
|
29
|
+
"aws-sdk": "2.1062.0",
|
|
29
30
|
"debug": "4.1.1",
|
|
30
|
-
"file-type": "
|
|
31
|
-
"monk": "
|
|
32
|
-
"nanoid": "
|
|
33
|
-
"validator": "
|
|
31
|
+
"file-type": "15.0.0",
|
|
32
|
+
"monk": "7.3.4",
|
|
33
|
+
"nanoid": "3.2.0",
|
|
34
|
+
"validator": "13.7.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"body-parser": "
|
|
37
|
-
"eslint": "
|
|
38
|
-
"express": "
|
|
39
|
-
"express-fileupload": "
|
|
40
|
-
"jest": "
|
|
41
|
-
"standard-version": "
|
|
42
|
-
"supertest": "
|
|
37
|
+
"body-parser": "1.19.0",
|
|
38
|
+
"eslint": "8.7.0",
|
|
39
|
+
"express": "4.17.1",
|
|
40
|
+
"express-fileupload": "1.2.0",
|
|
41
|
+
"jest": "27.4.7",
|
|
42
|
+
"standard-version": "9.3.2",
|
|
43
|
+
"supertest": "4.0.2"
|
|
43
44
|
},
|
|
44
45
|
"standard-version": {
|
|
45
46
|
"releaseCommitMessageFormat": "{{currentTag}}",
|
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
|
|
@@ -155,8 +157,8 @@ let plugin = module.exports = {
|
|
|
155
157
|
if (test) return [prunedData]
|
|
156
158
|
return model._update(
|
|
157
159
|
idquery,
|
|
158
|
-
{
|
|
159
|
-
{
|
|
160
|
+
{ '$set': prunedData },
|
|
161
|
+
{ 'multi': options.multi || options.create }
|
|
160
162
|
)
|
|
161
163
|
|
|
162
164
|
// If errors, remove inserted documents to prevent double ups when the user resaves.
|
|
@@ -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) {
|
|
@@ -380,7 +387,7 @@ let plugin = module.exports = {
|
|
|
380
387
|
let list = []
|
|
381
388
|
util.forEach(fields, (field, fieldName) => {
|
|
382
389
|
let path2 = `${path}.${fieldName}`.replace(/^\./, '')
|
|
383
|
-
let schema = field.schema || {}
|
|
390
|
+
// let schema = field.schema || {}
|
|
384
391
|
|
|
385
392
|
// Subdocument field
|
|
386
393
|
if (util.isSubdocument(field)) {//schema.isObject
|
package/test/assets/bad.svg
CHANGED
|
File without changes
|
package/test/assets/image.ico
CHANGED
|
File without changes
|
package/test/assets/image.webp
CHANGED
|
File without changes
|
package/test/assets/lion1.png
CHANGED
|
File without changes
|
package/test/assets/lion2.jpg
CHANGED
|
File without changes
|
package/test/assets/logo.png
CHANGED
|
File without changes
|
package/test/assets/logo2.png
CHANGED
|
File without changes
|