@unchainedshop/file-upload 1.1.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.
@@ -0,0 +1,7 @@
1
+ This directory and the files immediately inside it are automatically generated
2
+ when you change this package's NPM dependencies. Commit the files in this
3
+ directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
4
+ so that others run the same versions of sub-dependencies.
5
+
6
+ You should NOT check in the node_modules directory that Meteor automatically
7
+ creates; if you are using git, the .gitignore file tells git to ignore it.
@@ -0,0 +1,351 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "dependencies": {
4
+ "@colors/colors": {
5
+ "version": "1.5.0",
6
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
7
+ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
8
+ },
9
+ "@dabh/diagnostics": {
10
+ "version": "2.0.3",
11
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
12
+ "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="
13
+ },
14
+ "@unchainedshop/logger": {
15
+ "version": "1.1.3",
16
+ "resolved": "https://registry.npmjs.org/@unchainedshop/logger/-/logger-1.1.3.tgz",
17
+ "integrity": "sha512-tYQIMhkSRehRTcIG7g04lMsYj+9E5gs+rH4/KLWMGkHOMawwn6Gzy2Q3ecuDBGWoR79zTv6LaHJ4b23pAktzwA=="
18
+ },
19
+ "@unchainedshop/utils": {
20
+ "version": "1.1.3",
21
+ "resolved": "https://registry.npmjs.org/@unchainedshop/utils/-/utils-1.1.3.tgz",
22
+ "integrity": "sha512-5kUVHspX3RQK2dSiV6rr7FEn4VbP94M3EySxkDxH9Ck6WvZd9rVPjJiaOQwAK5ChC6EGdghqOI5D7pJQsuKi7g==",
23
+ "dependencies": {
24
+ "@unchainedshop/logger": {
25
+ "version": "1.1.3",
26
+ "resolved": "https://registry.npmjs.org/@unchainedshop/logger/-/logger-1.1.3.tgz",
27
+ "integrity": "sha512-tYQIMhkSRehRTcIG7g04lMsYj+9E5gs+rH4/KLWMGkHOMawwn6Gzy2Q3ecuDBGWoR79zTv6LaHJ4b23pAktzwA=="
28
+ }
29
+ }
30
+ },
31
+ "abort-controller": {
32
+ "version": "3.0.0",
33
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
34
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="
35
+ },
36
+ "async": {
37
+ "version": "3.2.4",
38
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
39
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
40
+ },
41
+ "base-x": {
42
+ "version": "4.0.0",
43
+ "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
44
+ "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
45
+ },
46
+ "base64-js": {
47
+ "version": "1.5.1",
48
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
49
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
50
+ },
51
+ "block-stream2": {
52
+ "version": "2.1.0",
53
+ "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
54
+ "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg=="
55
+ },
56
+ "bson": {
57
+ "version": "4.6.4",
58
+ "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz",
59
+ "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ=="
60
+ },
61
+ "buffer": {
62
+ "version": "5.7.1",
63
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
64
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="
65
+ },
66
+ "clone": {
67
+ "version": "2.1.2",
68
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
69
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
70
+ },
71
+ "color": {
72
+ "version": "3.2.1",
73
+ "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
74
+ "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="
75
+ },
76
+ "color-convert": {
77
+ "version": "1.9.3",
78
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
79
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="
80
+ },
81
+ "color-name": {
82
+ "version": "1.1.3",
83
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
84
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
85
+ },
86
+ "color-string": {
87
+ "version": "1.9.1",
88
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
89
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="
90
+ },
91
+ "colorspace": {
92
+ "version": "1.1.4",
93
+ "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
94
+ "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="
95
+ },
96
+ "enabled": {
97
+ "version": "2.0.0",
98
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
99
+ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
100
+ },
101
+ "es6-error": {
102
+ "version": "4.1.1",
103
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
104
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
105
+ },
106
+ "event-target-shim": {
107
+ "version": "5.0.1",
108
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
109
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
110
+ },
111
+ "fast-xml-parser": {
112
+ "version": "3.21.1",
113
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz",
114
+ "integrity": "sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg=="
115
+ },
116
+ "fecha": {
117
+ "version": "4.2.3",
118
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
119
+ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
120
+ },
121
+ "fn.name": {
122
+ "version": "1.1.0",
123
+ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
124
+ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
125
+ },
126
+ "hashids": {
127
+ "version": "2.2.10",
128
+ "resolved": "https://registry.npmjs.org/hashids/-/hashids-2.2.10.tgz",
129
+ "integrity": "sha512-nXnYums7F8B5Y+GSThutLPlKMaamW1yjWNZVt0WModiJfdjaDZHnhYTWblS+h1OoBx3yjwiBwxldPP3nIbFSSA=="
130
+ },
131
+ "ieee754": {
132
+ "version": "1.2.1",
133
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
134
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
135
+ },
136
+ "inherits": {
137
+ "version": "2.0.4",
138
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
139
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
140
+ },
141
+ "is-arrayish": {
142
+ "version": "0.3.2",
143
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
144
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
145
+ },
146
+ "is-stream": {
147
+ "version": "2.0.1",
148
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
149
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
150
+ },
151
+ "json-stream": {
152
+ "version": "1.0.0",
153
+ "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz",
154
+ "integrity": "sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg=="
155
+ },
156
+ "kuler": {
157
+ "version": "2.0.0",
158
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
159
+ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
160
+ },
161
+ "locale": {
162
+ "version": "0.1.0",
163
+ "resolved": "https://registry.npmjs.org/locale/-/locale-0.1.0.tgz",
164
+ "integrity": "sha512-rY3ZMnUkMfaZEix1fp/pjdwsHJkDQcE0UEcXHuYI77J03K8gddL3iD+UuJCLAmT2Z2oz5A70/0QS4J9OLCCvXw=="
165
+ },
166
+ "lodash": {
167
+ "version": "4.17.21",
168
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
169
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
170
+ },
171
+ "lodash._reinterpolate": {
172
+ "version": "3.0.0",
173
+ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
174
+ "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA=="
175
+ },
176
+ "lodash.template": {
177
+ "version": "4.5.0",
178
+ "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
179
+ "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A=="
180
+ },
181
+ "lodash.templatesettings": {
182
+ "version": "4.2.0",
183
+ "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
184
+ "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ=="
185
+ },
186
+ "logform": {
187
+ "version": "2.4.1",
188
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.1.tgz",
189
+ "integrity": "sha512-7XB/tqc3VRbri9pRjU6E97mQ8vC27ivJ3lct4jhyT+n0JNDd4YKldFl0D75NqDp46hk8RC7Ma1Vjv/UPf67S+A=="
190
+ },
191
+ "lru-cache": {
192
+ "version": "7.10.2",
193
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.2.tgz",
194
+ "integrity": "sha512-9zDbhgmXAUvUMPV81A705K3tVzcPiZL3Bf5/5JC/FjYJlLZ5AJCeqIRFHJqyBppiLosqF+uKB7p8/RDXylqBIw=="
195
+ },
196
+ "message-box": {
197
+ "version": "0.2.7",
198
+ "resolved": "https://registry.npmjs.org/message-box/-/message-box-0.2.7.tgz",
199
+ "integrity": "sha512-C4ccA5nHb58kTS+pLrgF/JWtr7fAIkHxRDceH7tdy5fMA783nUfbYwZ7H2XLvSeYfcnWIYCig5dWW+icK9X/Ag=="
200
+ },
201
+ "mime-db": {
202
+ "version": "1.52.0",
203
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
204
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
205
+ },
206
+ "mime-types": {
207
+ "version": "2.1.32",
208
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
209
+ "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
210
+ "dependencies": {
211
+ "mime-db": {
212
+ "version": "1.49.0",
213
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
214
+ "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA=="
215
+ }
216
+ }
217
+ },
218
+ "minimist": {
219
+ "version": "1.2.6",
220
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
221
+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
222
+ },
223
+ "minio": {
224
+ "version": "7.0.18",
225
+ "resolved": "https://registry.npmjs.org/minio/-/minio-7.0.18.tgz",
226
+ "integrity": "sha512-jVRjkw8A5Spf+ETY5OXQUcQckHriuUA3u2+MAcX36btLT8EytlOVivxIseXvyFf9cNn3dy5w1F1UyjMvHU+nqg==",
227
+ "dependencies": {
228
+ "mime-types": {
229
+ "version": "2.1.35",
230
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
231
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="
232
+ }
233
+ }
234
+ },
235
+ "mkdirp": {
236
+ "version": "0.5.6",
237
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
238
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="
239
+ },
240
+ "mongo-object": {
241
+ "version": "0.1.4",
242
+ "resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-0.1.4.tgz",
243
+ "integrity": "sha512-QtYk0gupWEn2+iB+DDRt1L+WbcNYvJRaHdih/dcqthOa1DbnREUGSs2WGcW478GNYpElflo/yybZXu0sTiRXHg=="
244
+ },
245
+ "ms": {
246
+ "version": "2.1.3",
247
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
248
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
249
+ },
250
+ "one-time": {
251
+ "version": "1.0.0",
252
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
253
+ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="
254
+ },
255
+ "querystring": {
256
+ "version": "0.2.0",
257
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
258
+ "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="
259
+ },
260
+ "readable-stream": {
261
+ "version": "3.6.0",
262
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
263
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="
264
+ },
265
+ "safe-buffer": {
266
+ "version": "5.2.1",
267
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
268
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
269
+ },
270
+ "safe-stable-stringify": {
271
+ "version": "2.3.1",
272
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz",
273
+ "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg=="
274
+ },
275
+ "sax": {
276
+ "version": "1.2.4",
277
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
278
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
279
+ },
280
+ "simpl-schema": {
281
+ "version": "1.12.2",
282
+ "resolved": "https://registry.npmjs.org/simpl-schema/-/simpl-schema-1.12.2.tgz",
283
+ "integrity": "sha512-FaisAjfJEt7Ie7K39wNqb/0F7FQ1q7yXmZcNa5JEBiPA9hIt4MpWMouL9mLqNB89alGpZAEiU7U9BelDxRqCVg=="
284
+ },
285
+ "simple-swizzle": {
286
+ "version": "0.2.2",
287
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
288
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="
289
+ },
290
+ "stack-trace": {
291
+ "version": "0.0.10",
292
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
293
+ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
294
+ },
295
+ "string_decoder": {
296
+ "version": "1.3.0",
297
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
298
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="
299
+ },
300
+ "strnum": {
301
+ "version": "1.0.5",
302
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
303
+ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
304
+ },
305
+ "text-hex": {
306
+ "version": "1.0.0",
307
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
308
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
309
+ },
310
+ "through2": {
311
+ "version": "3.0.2",
312
+ "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
313
+ "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ=="
314
+ },
315
+ "triple-beam": {
316
+ "version": "1.3.0",
317
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
318
+ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
319
+ },
320
+ "util-deprecate": {
321
+ "version": "1.0.2",
322
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
323
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
324
+ },
325
+ "winston": {
326
+ "version": "3.8.0",
327
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.0.tgz",
328
+ "integrity": "sha512-Iix1w8rIq2kBDkGvclO0db2CVOHYVamCIkVWcUbs567G9i2pdB+gvqLgDgxx4B4HXHYD6U4Zybh6ojepUOqcFQ=="
329
+ },
330
+ "winston-transport": {
331
+ "version": "4.5.0",
332
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz",
333
+ "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q=="
334
+ },
335
+ "xml": {
336
+ "version": "1.0.1",
337
+ "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
338
+ "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="
339
+ },
340
+ "xml2js": {
341
+ "version": "0.4.23",
342
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
343
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug=="
344
+ },
345
+ "xmlbuilder": {
346
+ "version": "11.0.1",
347
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
348
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
349
+ }
350
+ }
351
+ }
package/.versions ADDED
@@ -0,0 +1,53 @@
1
+ allow-deny@1.1.1
2
+ babel-compiler@7.9.0
3
+ babel-runtime@1.5.1
4
+ base64@1.0.12
5
+ binary-heap@1.0.11
6
+ boilerplate-generator@1.7.1
7
+ callback-hook@1.4.0
8
+ check@1.3.1
9
+ ddp@1.4.0
10
+ ddp-client@2.5.0
11
+ ddp-common@1.4.0
12
+ ddp-server@2.5.0
13
+ diff-sequence@1.1.1
14
+ dynamic-import@0.7.2
15
+ ecmascript@0.16.2
16
+ ecmascript-runtime@0.8.0
17
+ ecmascript-runtime-client@0.12.1
18
+ ecmascript-runtime-server@0.11.0
19
+ ejson@1.1.2
20
+ fetch@0.1.1
21
+ geojson-utils@1.0.10
22
+ id-map@1.1.1
23
+ inter-process-messaging@0.1.1
24
+ local-test:unchained:file-upload@1.1.1
25
+ logging@1.3.1
26
+ meteor@1.10.0
27
+ meteortesting:browser-tests@0.1.2
28
+ meteortesting:mocha@0.4.4
29
+ minimongo@1.8.0
30
+ modern-browsers@0.1.8
31
+ modules@0.18.0
32
+ modules-runtime@0.13.0
33
+ mongo@1.15.0
34
+ mongo-decimal@0.1.3
35
+ mongo-dev-server@1.1.0
36
+ mongo-id@1.0.8
37
+ npm-mongo@4.3.1
38
+ ordered-dict@1.1.0
39
+ practicalmeteor:mocha-core@1.0.1
40
+ promise@0.12.0
41
+ random@1.2.0
42
+ react-fast-refresh@0.2.3
43
+ reload@1.3.1
44
+ retry@1.1.0
45
+ routepolicy@1.1.1
46
+ socket-stream-client@0.5.0
47
+ tracker@1.2.0
48
+ typescript@4.5.4
49
+ unchained:file-upload@1.1.1
50
+ unchained:mongodb@1.1.0
51
+ underscore@1.0.10
52
+ webapp@1.13.1
53
+ webapp-hashing@1.1.0
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # File Upload (Unchained)
@@ -0,0 +1 @@
1
+ export default function buildHashedFilename(directoryName: string, fileName: string, expiryDate: Date): string;
@@ -0,0 +1,20 @@
1
+ import crypto from 'crypto';
2
+ import { slugify } from '@unchainedshop/utils';
3
+ import baseX from 'base-x';
4
+ const b62 = baseX('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
5
+ export default function buildHashedFilename(directoryName, fileName, expiryDate) {
6
+ const hashed = crypto
7
+ .createHash('md5')
8
+ .update(`${directoryName}${fileName}${expiryDate.getTime()}`) // ignore the year, we need
9
+ .digest('hex');
10
+ const splittedFilename = fileName.split('.');
11
+ const ext = splittedFilename?.pop();
12
+ const fileNameWithoutExtension = splittedFilename.join('.') || '';
13
+ const slugifiedFilenameWithExtension = [slugify(fileNameWithoutExtension), ext]
14
+ .filter(Boolean)
15
+ .join('.');
16
+ const arr = Uint8Array.from(Buffer.from(hashed, 'hex'));
17
+ const b62converted = b62.encode(arr);
18
+ return `${b62converted}-${slugifiedFilenameWithExtension}`;
19
+ }
20
+ //# sourceMappingURL=buildHashedFilename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildHashedFilename.js","sourceRoot":"","sources":["../src/buildHashedFilename.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,KAAK,MAAM,QAAQ,CAAC;AAE3B,MAAM,GAAG,GAAG,KAAK,CAAC,gEAAgE,CAAC,CAAC;AAEpF,MAAM,CAAC,OAAO,UAAU,mBAAmB,CACzC,aAAqB,EACrB,QAAgB,EAChB,UAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM;SAClB,UAAU,CAAC,KAAK,CAAC;SACjB,MAAM,CAAC,GAAG,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,2BAA2B;SACxF,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,gBAAgB,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACpC,MAAM,wBAAwB,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,8BAA8B,GAAG,CAAC,OAAO,CAAC,wBAAwB,CAAC,EAAE,GAAG,CAAC;SAC5E,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAErC,OAAO,GAAG,YAAY,IAAI,8BAA8B,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { IFileAdapter } from '@unchainedshop/types/files';
2
+ export declare const FileAdapter: Omit<IFileAdapter, 'key' | 'label' | 'version'>;
@@ -0,0 +1,27 @@
1
+ import { log, LogLevel } from '@unchainedshop/logger';
2
+ export const FileAdapter = {
3
+ createSignedURL() {
4
+ return new Promise((resolve) => {
5
+ resolve(null);
6
+ });
7
+ },
8
+ removeFiles() {
9
+ return new Promise((resolve) => {
10
+ resolve();
11
+ });
12
+ },
13
+ uploadFileFromStream() {
14
+ return new Promise((resolve) => {
15
+ resolve(null);
16
+ });
17
+ },
18
+ uploadFileFromURL() {
19
+ return new Promise((resolve) => {
20
+ resolve(null);
21
+ });
22
+ },
23
+ log(message, { level = LogLevel.Debug, ...options } = {}) {
24
+ return log(message, { level, ...options });
25
+ },
26
+ };
27
+ //# sourceMappingURL=FileAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileAdapter.js","sourceRoot":"","sources":["../../src/director/FileAdapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,CAAC,MAAM,WAAW,GAAoD;IAC1E,eAAe;QACb,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,WAAW;QACT,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC;IACD,oBAAoB;QAClB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,iBAAiB;QACf,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,OAAe,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE;QAC9D,OAAO,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { IFileDirector } from '@unchainedshop/types/files';
2
+ export declare const FileDirector: IFileDirector;
@@ -0,0 +1,15 @@
1
+ import { BaseDirector } from '@unchainedshop/utils';
2
+ const FileUploadRegistry = new Map();
3
+ const baseDirector = BaseDirector('FileDirector');
4
+ export const FileDirector = {
5
+ ...baseDirector,
6
+ registerFileUploadCallback(directoryName, fn) {
7
+ if (!FileUploadRegistry.has(directoryName)) {
8
+ FileUploadRegistry.set(directoryName, fn);
9
+ }
10
+ },
11
+ getFileUploadCallback(directoryName) {
12
+ return FileUploadRegistry.get(directoryName);
13
+ },
14
+ };
15
+ //# sourceMappingURL=FileDirector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileDirector.js","sourceRoot":"","sources":["../../src/director/FileDirector.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAA8B,CAAC;AAEjE,MAAM,YAAY,GAAG,YAAY,CAAe,cAAc,CAAC,CAAC;AAEhE,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,GAAG,YAAY;IAEf,0BAA0B,CAAC,aAAa,EAAE,EAAE;QAC1C,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE;YAC1C,kBAAkB,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;SAC3C;IACH,CAAC;IAED,qBAAqB,CAAC,aAAa;QACjC,OAAO,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/C,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { FileDirector } from './director/FileDirector';
2
+ export { FileAdapter } from './director/FileAdapter';
@@ -0,0 +1,3 @@
1
+ export { FileDirector } from './director/FileDirector';
2
+ export { FileAdapter } from './director/FileAdapter';
3
+ //# sourceMappingURL=file-upload-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-upload-index.js","sourceRoot":"","sources":["../src/file-upload-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC"}
package/package.js ADDED
@@ -0,0 +1,34 @@
1
+ Package.describe({
2
+ name: 'unchained:file-upload',
3
+ version: '1.1.3',
4
+ summary: 'Unchained Engine: Core File Upload',
5
+ git: 'https://github.com/unchainedshop/unchained',
6
+ documentation: 'README.md',
7
+ });
8
+
9
+ Npm.depends({
10
+ minio: '7.0.18',
11
+ 'mime-types': '2.1.32',
12
+ 'base-x': '4.0.0',
13
+ '@unchainedshop/logger': '1.1.3',
14
+ '@unchainedshop/utils': '1.1.3',
15
+ });
16
+
17
+ Package.onUse((api) => {
18
+ api.versionsFrom('2.7.3');
19
+ api.use('ecmascript');
20
+ api.use('typescript');
21
+
22
+ api.mainModule('src/file-upload-index.ts', 'server');
23
+ });
24
+
25
+ Package.onTest((api) => {
26
+ api.use('meteortesting:mocha');
27
+ api.use('ecmascript');
28
+ api.use('typescript');
29
+
30
+ api.use('unchained:mongodb@1.1.3');
31
+ api.use('unchained:file-upload@1.1.3');
32
+
33
+ api.mainModule('tests/file-upload-index.test.ts');
34
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@unchainedshop/file-upload",
3
+ "version": "1.1.3",
4
+ "main": "lib/file-upload-index.js",
5
+ "types": "lib/file-upload-index.d.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "clean": "rm -rf lib",
9
+ "build": "npm run clean && tsc -p tsconfig.build.json",
10
+ "watch": "tsc --watch",
11
+ "link:core": "npm link @unchainedshop/types",
12
+ "test": "cross-env METEOR_PACKAGE_DIRS=../ TEST_CLIENT=0 TEST_WATCH=1 meteor test-packages ./ --driver-package meteortesting:mocha --port 4200"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/unchainedshop/unchained.git"
17
+ },
18
+ "keywords": [
19
+ "unchained",
20
+ "ecommerce",
21
+ "core"
22
+ ],
23
+ "author": "Joël Meiller",
24
+ "license": "EUPL-1.2",
25
+ "bugs": {
26
+ "url": "https://github.com/unchainedshop/unchained/issues"
27
+ },
28
+ "homepage": "https://github.com/unchainedshop/unchained#readme",
29
+ "dependencies": {
30
+ "@unchainedshop/logger": "latest",
31
+ "@unchainedshop/utils": "latest",
32
+ "mime-types": "^2.1.35",
33
+ "minio": "^7.0.28"
34
+ },
35
+ "devDependencies": {
36
+ "@types/chai": "^4.3.1",
37
+ "@types/minio": "^7.0.13",
38
+ "@types/mocha": "^9.1.1",
39
+ "@unchainedshop/types": "latest",
40
+ "chai": "^4.3.6",
41
+ "cross-env": "^7.0.3",
42
+ "typescript": "^4.7.4"
43
+ }
44
+ }
@@ -0,0 +1,136 @@
1
+ import { IFileAdapter } from '@unchainedshop/types/files';
2
+ import { FileAdapter, FileDirector } from 'meteor/unchained:file-upload';
3
+ import https from 'https';
4
+ import http, { OutgoingHttpHeaders } from 'http';
5
+ import mimeType from 'mime-types';
6
+ import { URL } from 'url';
7
+ import { Readable } from 'stream';
8
+ import sign from './sign';
9
+ import promisePipe from './promisePipe';
10
+ import buildHashedFilename from '../../src/buildHashedFilename';
11
+
12
+ const { UNCHAINED_PUT_URL_EXPIRY, ROOT_URL } = process.env;
13
+
14
+ const getExpiryDate = () =>
15
+ new Date(new Date().getTime() + (parseInt(UNCHAINED_PUT_URL_EXPIRY, 10) || 24 * 60 * 60 * 1000));
16
+
17
+ const bufferToStream = (buffer: any) => {
18
+ const stream = new Readable();
19
+ stream.push(buffer);
20
+ stream.push(null);
21
+
22
+ return stream;
23
+ };
24
+
25
+ const createDownloadStream = (fileUrl: string, headers: OutgoingHttpHeaders): Promise<Readable> => {
26
+ const { href, protocol } = new URL(fileUrl);
27
+ return new Promise((resolve, reject) => {
28
+ try {
29
+ if (protocol === 'http:') {
30
+ http.get(href, { headers }, resolve);
31
+ } else {
32
+ https.get(href, { headers }, resolve);
33
+ }
34
+ } catch (e) {
35
+ reject(e);
36
+ }
37
+ });
38
+ };
39
+
40
+ export const GridFSAdapter: IFileAdapter = {
41
+ key: 'shop.unchained.file-upload-plugin.gridfs',
42
+ label: 'Uploads files to Database using GridFS',
43
+ version: '1.0',
44
+
45
+ ...FileAdapter,
46
+
47
+ async createSignedURL(directoryName, fileName) {
48
+ const expiryDate = getExpiryDate();
49
+ const _id = buildHashedFilename(directoryName, fileName, expiryDate);
50
+ const signature = sign(directoryName, _id, expiryDate.getTime());
51
+
52
+ const putURL = new URL(
53
+ `/gridfs/${directoryName}/${encodeURIComponent(
54
+ fileName,
55
+ )}?e=${expiryDate.getTime()}&s=${signature}`,
56
+ ROOT_URL,
57
+ ).href;
58
+ const url = `/gridfs/${directoryName}/${_id}`;
59
+
60
+ return {
61
+ _id,
62
+ directoryName,
63
+ expiryDate,
64
+ fileName,
65
+ type: mimeType.lookup(fileName),
66
+ putURL,
67
+ url,
68
+ };
69
+ },
70
+
71
+ async uploadFileFromStream(directoryName: string, rawFile: any, { modules }: any) {
72
+ let stream;
73
+ let fileName;
74
+ if (rawFile instanceof Promise) {
75
+ const { filename: f, createReadStream } = await rawFile;
76
+ fileName = f;
77
+ stream = createReadStream();
78
+ } else {
79
+ fileName = rawFile.filename;
80
+ stream = bufferToStream(Buffer.from(rawFile.buffer, 'base64'));
81
+ }
82
+
83
+ const expiryDate = getExpiryDate();
84
+ const _id = buildHashedFilename(directoryName, fileName, expiryDate);
85
+
86
+ const writeStream = await modules.gridfsFileUploads.createWriteStream(directoryName, _id, fileName);
87
+
88
+ const file = await promisePipe(stream, writeStream);
89
+ const url = `/gridfs/${directoryName}/${_id}`;
90
+
91
+ return {
92
+ _id,
93
+ directoryName,
94
+ expiryDate: null,
95
+ fileName,
96
+ size: file.length,
97
+ type: mimeType.lookup(fileName) || (await Promise.resolve(rawFile)).mimetype,
98
+ url,
99
+ };
100
+ },
101
+
102
+ async uploadFileFromURL(
103
+ directoryName: string,
104
+ { fileLink, fileName: fname, headers }: any,
105
+ { modules }: any,
106
+ ) {
107
+ const { href } = new URL(fileLink);
108
+ const fileName = fname || href.split('/').pop();
109
+
110
+ const expiryDate = getExpiryDate();
111
+ const _id = buildHashedFilename(directoryName, fileName, expiryDate);
112
+
113
+ const downloadStream = await createDownloadStream(href, headers);
114
+ const writeStream = await modules.gridfsFileUploads.createWriteStream(directoryName, _id, fileName);
115
+ const file = await promisePipe(downloadStream, writeStream);
116
+ const url = `/gridfs/${directoryName}/${_id}`;
117
+
118
+ return {
119
+ _id,
120
+ directoryName,
121
+ expiryDate: null,
122
+ fileName,
123
+ size: file.length,
124
+ type: mimeType.lookup(fileName),
125
+ url,
126
+ };
127
+ },
128
+
129
+ async removeFiles(files, { modules }: any) {
130
+ await Promise.all(
131
+ files.map(async ({ _id, path }) => modules.gridfsFileUploads.removeFileFromBucket(path, _id)),
132
+ );
133
+ },
134
+ };
135
+
136
+ FileDirector.registerAdapter(GridFSAdapter);
@@ -0,0 +1,60 @@
1
+ import { useMiddlewareWithCurrentContext } from 'meteor/unchained:api';
2
+ import { log, LogLevel } from '@unchainedshop/logger';
3
+ import sign from './sign';
4
+ import promisePipe from './promisePipe';
5
+ import buildHashedFilename from '../../src/buildHashedFilename';
6
+
7
+ const { ROOT_URL } = process.env;
8
+
9
+ /*
10
+ curl -vvv -X PUT -T /Users/pozylon/Downloads/grafik.png http://localhost:4010/gridfs/user-avatars/hans.png?e=1653059591506&s=df8626a1c9b9006249d7f1928fd530579fed57216f1bdcb83c28a8322cdc90b1
11
+ */
12
+
13
+ useMiddlewareWithCurrentContext('/gridfs', async (req, res) => {
14
+ try {
15
+ const { services, modules } = req.unchainedContext;
16
+ const url = new URL(req.url, ROOT_URL);
17
+ const [, directoryName, fileName] = url.pathname.split('/');
18
+
19
+ if (req.method === 'PUT') {
20
+ const { s: signature, e: expiryTimestamp } = req.query;
21
+ const expiryDate = new Date(parseInt(expiryTimestamp, 10));
22
+ const fileId = buildHashedFilename(directoryName, fileName, expiryDate);
23
+ if (sign(directoryName, fileId, expiryDate.getTime()) === signature) {
24
+ const file = await modules.files.findFile({ fileId });
25
+ if (file.expires === null) {
26
+ res.writeHead(503);
27
+ res.end('File already linked');
28
+ }
29
+ const writeStream = await modules.gridfsFileUploads.createWriteStream(
30
+ directoryName,
31
+ fileId,
32
+ fileName,
33
+ );
34
+ const { length } = await promisePipe(req, writeStream);
35
+ await services.files.linkFile({ fileId, size: length }, req.unchainedContext);
36
+ res.writeHead(200);
37
+ res.end();
38
+ return;
39
+ }
40
+ res.writeHead(403);
41
+ res.end();
42
+ return;
43
+ }
44
+ if (req.method === 'GET') {
45
+ const fileId = fileName;
46
+ const readStream = await modules.gridfsFileUploads.createReadStream(directoryName, fileId);
47
+ await promisePipe(readStream, res);
48
+ res.writeHead(200);
49
+ res.end();
50
+ return;
51
+ }
52
+ res.writeHead(404);
53
+ res.end();
54
+ return;
55
+ } catch (e) {
56
+ log(e.message, { level: LogLevel.Error });
57
+ res.writeHead(503);
58
+ res.end(JSON.stringify(e));
59
+ }
60
+ });
@@ -0,0 +1,21 @@
1
+ import mongodb from 'mongodb';
2
+
3
+ export const configureGridFSFileUploadModule = ({ db }) => {
4
+ return {
5
+ createWriteStream: async (directoryName, fileId, fileName) => {
6
+ const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
7
+ const bucket = new mongodb.GridFSBucket(db, options);
8
+ return bucket.openUploadStreamWithId(fileId, fileName);
9
+ },
10
+ createReadStream: async (directoryName, fileId) => {
11
+ const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
12
+ const bucket = new mongodb.GridFSBucket(db, options);
13
+ return bucket.openDownloadStream(fileId);
14
+ },
15
+ removeFileFromBucket: async (directoryName, fileId) => {
16
+ const options = { bucketName: `file_uploads_${directoryName}`, chunkSizeBytes: 5 * 1024 * 1024 };
17
+ const bucket = new mongodb.GridFSBucket(db, options);
18
+ return bucket.delete(fileId);
19
+ },
20
+ };
21
+ };
@@ -0,0 +1,19 @@
1
+ export default async (readStream, writeStream) => {
2
+ return new Promise((resolve, reject) => {
3
+ try {
4
+ readStream
5
+ .on('error', (e) => {
6
+ reject(e);
7
+ })
8
+ .pipe(writeStream)
9
+ .on('error', (e) => {
10
+ reject(e);
11
+ })
12
+ .on('finish', async (file) => {
13
+ resolve(file);
14
+ });
15
+ } catch (e) {
16
+ reject(e);
17
+ }
18
+ });
19
+ };
@@ -0,0 +1,16 @@
1
+ import crypto from 'crypto';
2
+
3
+ const { UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET } = process.env;
4
+
5
+ const sign = (directoryName, hash, expiryTimestamp) => {
6
+ if (!UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET)
7
+ throw new Error(
8
+ 'To enable PUT based uploads you have to provide a random UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET environment variable',
9
+ );
10
+ const hmac = crypto.createHmac('sha256', UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET);
11
+ const signature = [directoryName, hash, expiryTimestamp].join(':');
12
+ hmac.update(signature);
13
+ return hmac.digest('hex');
14
+ };
15
+
16
+ export default sign;
@@ -0,0 +1,214 @@
1
+ import { IFileAdapter } from '@unchainedshop/types/files';
2
+ import { FileAdapter, FileDirector } from 'meteor/unchained:file-upload';
3
+ import https from 'https';
4
+ import http, { OutgoingHttpHeaders } from 'http';
5
+ import { log, LogLevel } from '@unchainedshop/logger';
6
+ import mimeType from 'mime-types';
7
+ import Minio from 'minio';
8
+ import { Readable } from 'stream';
9
+ import { URL } from 'url';
10
+ import buildHashedFilename from '../src/buildHashedFilename';
11
+
12
+ const {
13
+ MINIO_ACCESS_KEY,
14
+ MINIO_REGION,
15
+ MINIO_SECRET_KEY,
16
+ MINIO_ENDPOINT,
17
+ MINIO_BUCKET_NAME,
18
+ NODE_ENV,
19
+ AMAZON_S3_SESSION_TOKEN,
20
+ } = process.env;
21
+ const PUT_URL_EXPIRY = 24 * 60 * 60;
22
+
23
+ let client: Minio.Client;
24
+
25
+ export async function connectToMinio(credentialsProvider) {
26
+ if (!MINIO_ENDPOINT || !MINIO_BUCKET_NAME) {
27
+ log(
28
+ 'Please configure Minio/S3 by providing MINIO_ENDPOINT & MINIO_BUCKET_NAME to use upload features',
29
+ { level: LogLevel.Error },
30
+ );
31
+ return null;
32
+ }
33
+
34
+ try {
35
+ const resolvedUrl = new URL(MINIO_ENDPOINT);
36
+ const minioClient = new Minio.Client({
37
+ endPoint: resolvedUrl.hostname,
38
+ useSSL: resolvedUrl.protocol === 'https:',
39
+ port: parseInt(resolvedUrl.port, 10) || undefined,
40
+ region: MINIO_REGION,
41
+ sessionToken: AMAZON_S3_SESSION_TOKEN,
42
+ accessKey: MINIO_ACCESS_KEY,
43
+ secretKey: MINIO_SECRET_KEY,
44
+ });
45
+
46
+ if (NODE_ENV === 'development') minioClient?.traceOn(process.stdout);
47
+
48
+ if (credentialsProvider) {
49
+ await minioClient.setCredentialsProvider(credentialsProvider);
50
+ }
51
+
52
+ client = minioClient;
53
+ } catch (error) {
54
+ log(`Exception while creating Minio client: ${error.message}`, {
55
+ level: LogLevel.Error,
56
+ });
57
+ }
58
+ return null;
59
+ }
60
+
61
+ const generateMinioUrl = (directoryName: string, hashedFilename: string) => {
62
+ return `${MINIO_ENDPOINT}/${MINIO_BUCKET_NAME}/${directoryName}/${hashedFilename}`;
63
+ };
64
+
65
+ const getMimeType = (extension) => {
66
+ return mimeType.lookup(extension);
67
+ };
68
+
69
+ connectToMinio().then((c) => {
70
+ client = c;
71
+ });
72
+
73
+ const createDownloadStream = (
74
+ fileUrl: string,
75
+ headers: OutgoingHttpHeaders,
76
+ ): Promise<http.IncomingMessage> => {
77
+ const { href, protocol } = new URL(fileUrl);
78
+ return new Promise((resolve, reject) => {
79
+ try {
80
+ if (protocol === 'http:') {
81
+ http.get(href, { headers }, resolve);
82
+ } else {
83
+ https.get(href, { headers }, resolve);
84
+ }
85
+ } catch (e) {
86
+ reject(e);
87
+ }
88
+ });
89
+ };
90
+
91
+ const getObjectStats = async (fileName: string) => {
92
+ if (!client) throw new Error('Minio not connected, check env variables');
93
+
94
+ return client.statObject(MINIO_BUCKET_NAME, fileName);
95
+ };
96
+
97
+ const bufferToStream = (buffer: any) => {
98
+ const stream = new Readable();
99
+ stream.push(buffer);
100
+ stream.push(null);
101
+
102
+ return stream;
103
+ };
104
+
105
+ const getExpiryDate = () => new Date(new Date().getTime() + PUT_URL_EXPIRY);
106
+
107
+ export const MinioAdapter: IFileAdapter = {
108
+ key: 'shop.unchained.file-upload-plugin.minio',
109
+ label: 'Uploads files into an S3 bucket using minio',
110
+ version: '1.0',
111
+
112
+ ...FileAdapter,
113
+
114
+ async createSignedURL(directoryName, fileName) {
115
+ if (!client) throw new Error('Minio not connected, check env variables');
116
+
117
+ const expiryDate = getExpiryDate();
118
+ const _id = buildHashedFilename(directoryName, fileName, expiryDate);
119
+
120
+ const url = await client.presignedPutObject(
121
+ MINIO_BUCKET_NAME,
122
+ `${directoryName}/${_id}`,
123
+ PUT_URL_EXPIRY,
124
+ );
125
+
126
+ return {
127
+ _id,
128
+ directoryName,
129
+ expiryDate,
130
+ fileName,
131
+ type: getMimeType(fileName),
132
+ putURL: url,
133
+ url: generateMinioUrl(directoryName, _id),
134
+ };
135
+ },
136
+
137
+ async removeFiles(files) {
138
+ if (!client) throw new Error('Minio not connected, check env variables');
139
+
140
+ const fileIds = files.map(({ path, _id }) => {
141
+ return `${path}/${_id}`;
142
+ });
143
+
144
+ await client.removeObjects(MINIO_BUCKET_NAME, fileIds);
145
+ },
146
+
147
+ async uploadFileFromStream(directoryName: string, rawFile: any) {
148
+ if (!client) throw new Error('Minio not connected, check env variables');
149
+
150
+ let stream;
151
+ let fileName;
152
+ if (rawFile instanceof Promise) {
153
+ const { filename: fname, createReadStream } = await rawFile;
154
+ fileName = fname;
155
+ stream = createReadStream();
156
+ } else {
157
+ fileName = rawFile.filename;
158
+ stream = bufferToStream(Buffer.from(rawFile.buffer, 'base64'));
159
+ }
160
+
161
+ const _id = buildHashedFilename(directoryName, fileName, new Date());
162
+ const type = rawFile?.mimetype || getMimeType(fileName);
163
+
164
+ const metaData = {
165
+ 'Content-Type': type,
166
+ };
167
+
168
+ await client.putObject(MINIO_BUCKET_NAME, `${directoryName}/${_id}`, stream, undefined, metaData);
169
+
170
+ const { size } = await getObjectStats(`${directoryName}/${_id}`);
171
+
172
+ return {
173
+ _id,
174
+ directoryName,
175
+ expiryDate: null,
176
+ fileName,
177
+ size,
178
+ type,
179
+ url: generateMinioUrl(directoryName, _id),
180
+ };
181
+ },
182
+
183
+ async uploadFileFromURL(directoryName: string, { fileLink, fileName: fname, headers }: any) {
184
+ if (!client) throw new Error('Minio not connected, check env variables');
185
+
186
+ const { href } = new URL(fileLink);
187
+ const fileName = fname || href.split('/').pop();
188
+ const _id = buildHashedFilename(directoryName, fileName, new Date());
189
+
190
+ const stream = await createDownloadStream(fileLink, headers);
191
+ const type = stream?.headers?.['content-type'] || getMimeType(fileName);
192
+
193
+ const metaData = {
194
+ 'Content-Type': type,
195
+ };
196
+
197
+ await client.putObject(MINIO_BUCKET_NAME, `${directoryName}/${_id}`, stream, undefined, metaData);
198
+ const { size } = await getObjectStats(`${directoryName}/${_id}`);
199
+
200
+ return {
201
+ _id,
202
+ directoryName,
203
+ expiryDate: null,
204
+ fileName,
205
+ size,
206
+ type,
207
+ url: generateMinioUrl(directoryName, _id),
208
+ };
209
+ },
210
+ };
211
+
212
+ FileDirector.registerAdapter(MinioAdapter);
213
+
214
+ export default MinioAdapter;
@@ -0,0 +1,51 @@
1
+ import { useMiddlewareWithCurrentContext } from 'meteor/unchained:api';
2
+ import { log, LogLevel } from '@unchainedshop/logger';
3
+ import bodyParser from 'body-parser';
4
+
5
+ const { MINIO_WEBHOOK_AUTH_TOKEN } = process.env;
6
+
7
+ const isAuthorized = ({ authorization = '' }) => {
8
+ const [type, token] = authorization.split(' ');
9
+ return type === 'Bearer' && token === MINIO_WEBHOOK_AUTH_TOKEN;
10
+ };
11
+
12
+ useMiddlewareWithCurrentContext(
13
+ '/minio/',
14
+ bodyParser.json({
15
+ strict: false,
16
+ }),
17
+ );
18
+
19
+ useMiddlewareWithCurrentContext('/minio/', async (req, res) => {
20
+ try {
21
+ if (req.method === 'POST' && req.body) {
22
+ const { headers } = req;
23
+ const { Records = [], EventName } = req.body;
24
+ if (EventName === 's3:ObjectCreated:Put' && isAuthorized(headers)) {
25
+ const [{ s3 }] = Records;
26
+ const { object } = s3;
27
+ const { size, contentType: type } = object;
28
+ const [fileId] = object.key.split('.');
29
+ const { services } = req.unchainedContext;
30
+ await services.files.linkFile({ fileId, type, size }, req.unchainedContext);
31
+ res.writeHead(200);
32
+ res.end();
33
+ return;
34
+ }
35
+ }
36
+ res.writeHead(404);
37
+ res.end();
38
+ return;
39
+ } catch (e) {
40
+ log(e.message, { level: LogLevel.Error });
41
+ res.writeHead(503);
42
+ res.end(JSON.stringify(e));
43
+ }
44
+ });
45
+
46
+ /*
47
+
48
+ curl -X PUT -T /Users/pozylon/Desktop/Bildschirmfoto\ 2022-02-17\ um\ 16.47.22.png http://localhost:4010/gridfs/3e0cd78ac07f0028890ce36c391057dd0c5fb46f4c147fb73e54bd8a7ffee389/user-avatars/avatar.png?e=1645883865878&s=e7466b30039593e5790e23cab92878e0144289c3710344a55b78c9a916e2f78c
49
+ curl -X PUT -T /Users/pozylon/Desktop/Bildschirmfoto\ 2022-02-17\ um\ 16.47.22.png https://minio.dev.shared.ucc.dev/unchained-test-bucket/user-avatars/9aedd551499b2850e9483b07cd4c8cff7ad1d710f49933c183861bf40183bc5a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=unchained-test%2F20220225%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220225T140957Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=22807026db1210370170cd5cf49bfbb9e86010425c9d1834a56e049faf39b368
50
+
51
+ */
@@ -0,0 +1,27 @@
1
+ import crypto from 'crypto';
2
+ import { slugify } from '@unchainedshop/utils';
3
+ import baseX from 'base-x';
4
+
5
+ const b62 = baseX('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
6
+
7
+ export default function buildHashedFilename(
8
+ directoryName: string,
9
+ fileName: string,
10
+ expiryDate: Date,
11
+ ): string {
12
+ const hashed = crypto
13
+ .createHash('md5')
14
+ .update(`${directoryName}${fileName}${expiryDate.getTime()}`) // ignore the year, we need
15
+ .digest('hex');
16
+
17
+ const splittedFilename = fileName.split('.');
18
+ const ext = splittedFilename?.pop();
19
+ const fileNameWithoutExtension = splittedFilename.join('.') || '';
20
+ const slugifiedFilenameWithExtension = [slugify(fileNameWithoutExtension), ext]
21
+ .filter(Boolean)
22
+ .join('.');
23
+ const arr = Uint8Array.from(Buffer.from(hashed, 'hex'));
24
+ const b62converted = b62.encode(arr);
25
+
26
+ return `${b62converted}-${slugifiedFilenameWithExtension}`;
27
+ }
@@ -0,0 +1,30 @@
1
+ import { IFileAdapter } from '@unchainedshop/types/files';
2
+ import { log, LogLevel } from '@unchainedshop/logger';
3
+
4
+ export const FileAdapter: Omit<IFileAdapter, 'key' | 'label' | 'version'> = {
5
+ createSignedURL() {
6
+ return new Promise<null>((resolve) => {
7
+ resolve(null);
8
+ });
9
+ },
10
+
11
+ removeFiles() {
12
+ return new Promise<void>((resolve) => {
13
+ resolve();
14
+ });
15
+ },
16
+ uploadFileFromStream() {
17
+ return new Promise<null>((resolve) => {
18
+ resolve(null);
19
+ });
20
+ },
21
+ uploadFileFromURL() {
22
+ return new Promise<null>((resolve) => {
23
+ resolve(null);
24
+ });
25
+ },
26
+
27
+ log(message: string, { level = LogLevel.Debug, ...options } = {}) {
28
+ return log(message, { level, ...options });
29
+ },
30
+ };
@@ -0,0 +1,20 @@
1
+ import { IFileAdapter, IFileDirector, UploadFileCallback } from '@unchainedshop/types/files';
2
+ import { BaseDirector } from '@unchainedshop/utils';
3
+
4
+ const FileUploadRegistry = new Map<string, UploadFileCallback>();
5
+
6
+ const baseDirector = BaseDirector<IFileAdapter>('FileDirector');
7
+
8
+ export const FileDirector: IFileDirector = {
9
+ ...baseDirector,
10
+
11
+ registerFileUploadCallback(directoryName, fn) {
12
+ if (!FileUploadRegistry.has(directoryName)) {
13
+ FileUploadRegistry.set(directoryName, fn);
14
+ }
15
+ },
16
+
17
+ getFileUploadCallback(directoryName) {
18
+ return FileUploadRegistry.get(directoryName);
19
+ },
20
+ };
@@ -0,0 +1,2 @@
1
+ export { FileDirector } from './director/FileDirector';
2
+ export { FileAdapter } from './director/FileAdapter';
@@ -0,0 +1,17 @@
1
+ import { assert } from 'chai';
2
+ import { initDb } from 'meteor/unchained:mongodb';
3
+ import { FileDirector, FileAdapter } from 'meteor/unchained:file-upload';
4
+
5
+ describe('Test exports', () => {
6
+ it('Check expors', () => {
7
+ assert.ok(FileDirector);
8
+ assert.isFunction(FileDirector.registerAdapter);
9
+ assert.isFunction(FileDirector.registerFileUploadCallback);
10
+
11
+ assert.ok(FileAdapter);
12
+ assert.isFunction(FileAdapter.createSignedURL);
13
+ assert.isFunction(FileAdapter.removeFiles);
14
+ assert.isFunction(FileAdapter.uploadFileFromStream);
15
+ assert.isFunction(FileAdapter.uploadFileFromURL);
16
+ });
17
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "allowSyntheticDefaultImports": true,
5
+ "declaration": true,
6
+ "esModuleInterop": true,
7
+ "experimentalDecorators": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "lib": ["esnext"],
10
+ "module": "esnext",
11
+ "moduleResolution": "node",
12
+ "noImplicitReturns": true,
13
+ "noUnusedLocals": false,
14
+ "outDir": "lib",
15
+ "preserveWatchOutput": true,
16
+ "skipLibCheck": true,
17
+ "sourceMap": true,
18
+ "target": "esnext",
19
+ "types": ["node"],
20
+ "baseUrl": ".", // This must be specified if "paths" is.
21
+ "paths": {
22
+ "meteor/unchained:*": ["node_modules/@unchainedshop/types/index.d.ts"]
23
+ }
24
+ },
25
+ "include": ["src"]
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.build.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["node", "mocha"]
6
+ },
7
+ "include": ["src", "tests", "plugins"]
8
+ }