@ind-rcg/backend 246.1008.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE.md +37 -0
  2. package/README.md +7 -0
  3. package/bin/libsqliteExtension.dll +0 -0
  4. package/bin/libsqliteExtension.dylib +0 -0
  5. package/binding.gyp +21 -0
  6. package/configuration.template.json +24 -0
  7. package/log4js.json +11 -0
  8. package/nativeSrc/PriceEngineWrap.cc +157 -0
  9. package/nativeSrc/PriceEngineWrap.h +24 -0
  10. package/nativeSrc/common/DBAccess/SimpleDBConnection.cpp +800 -0
  11. package/nativeSrc/common/DBAccess/SimpleDBConnection.h +54 -0
  12. package/nativeSrc/common/Libs/cJSON/CHANGELOG.md +428 -0
  13. package/nativeSrc/common/Libs/cJSON/LICENSE +20 -0
  14. package/nativeSrc/common/Libs/cJSON/README.md +571 -0
  15. package/nativeSrc/common/Libs/cJSON/cJSON.c +3110 -0
  16. package/nativeSrc/common/Libs/cJSON/cJSON.h +293 -0
  17. package/nativeSrc/common/Libs/sqlcipher/sqlite3.c +241624 -0
  18. package/nativeSrc/common/Libs/sqlcipher/sqlite3.h +12836 -0
  19. package/nativeSrc/common/Libs/sqlcipher/sqlite3ext.h +701 -0
  20. package/nativeSrc/common/LogAdapter/LogAdapter.cpp +25 -0
  21. package/nativeSrc/common/LogAdapter/LogAdapter.h +20 -0
  22. package/nativeSrc/common/PriceEngine/PriceEngine.cpp +251 -0
  23. package/nativeSrc/common/PriceEngine/PriceEngine.h +67 -0
  24. package/nativeSrc/common/Utils/StringFormat.cpp +905 -0
  25. package/nativeSrc/common/Utils/StringFormat.h +116 -0
  26. package/nativeSrc/common/Utils/miniz/timer.cpp +165 -0
  27. package/nativeSrc/common/Utils/miniz/timer.h +40 -0
  28. package/nativeSrc/common/stdngm.h +92 -0
  29. package/nativeSrc/nativeWrapper.cc +15 -0
  30. package/package.json +70 -0
  31. package/src/argsParser.js +73 -0
  32. package/src/bootstrap.js +156 -0
  33. package/src/fsHelper.js +36 -0
  34. package/src/globalConfig.js +23 -0
  35. package/src/local.js +546 -0
  36. package/src/server.js +64 -0
  37. package/src/sfAttachmentsHandler.js +283 -0
  38. package/src/utils.js +91 -0
  39. package/src/zipHandler.js +153 -0
@@ -0,0 +1,283 @@
1
+ /*
2
+ * FILE_HEADER
3
+ */
4
+
5
+ "use strict";
6
+
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const Utils = require("./utils");
10
+ const sharp = require("sharp");
11
+ const md5 = require("md5");
12
+ let log = require('log4js').getLogger("local");
13
+ const _ = require('lodash');
14
+ const GlobalConfig = require('./globalConfig');
15
+
16
+ const SFAttachmentsHandler = new function () {
17
+ const getFilePathNotFoundErrorMessage = (fileName, operationName) => {
18
+ return `The ${fileName} file for the ${operationName} operation either doesn’t exist or couldn’t be found. Check the filename and try again.`;
19
+ };
20
+
21
+ const getGenericErrorMessage = (operationName, error) => {
22
+ return `We couldn’t complete the ${operationName} operation. Try again or contact Salesforce Support and provide the error details: ${error}`;
23
+ };
24
+
25
+ const getInvalidInputErrorResponse = (operationName) => {
26
+ log.error(`Invalid input parameters for ${operationName}. Check the input parameters and try again.`);
27
+ return {
28
+ success: false,
29
+ results: { "error_message": Utils.internalServerError }
30
+ };
31
+ };
32
+
33
+ const generateThumbnail = (fileBuffer, fileType, attachmentFilePath, thumbnailFilePath) => {
34
+ const tFileType = Utils.getThumbnailImageFormat(fileType);
35
+
36
+ if (Utils.isDefined(tFileType)) {
37
+ // thumbnail image applicable
38
+ const sharpInstance = sharp(fileBuffer);
39
+ return sharpInstance.metadata()
40
+ .then((imageMetadata) => {
41
+ if (imageMetadata.width > 480 || imageMetadata.height > 480) {
42
+ // generate thumbnail
43
+ let thumbnailWidth, thumbnailHeight;
44
+
45
+ if (imageMetadata.width > imageMetadata.height) {
46
+ thumbnailWidth = 480;
47
+ thumbnailHeight = Math.round((thumbnailWidth / imageMetadata.width) * imageMetadata.height);
48
+ } else {
49
+ thumbnailHeight = 480;
50
+ thumbnailWidth = Math.round((thumbnailHeight / imageMetadata.height) * imageMetadata.width);
51
+ }
52
+ return sharpInstance.resize(thumbnailWidth, thumbnailHeight).toFile(thumbnailFilePath)
53
+ .then(() => {
54
+ return {
55
+ finalPath: attachmentFilePath,
56
+ previewPath: thumbnailFilePath
57
+ };
58
+ })
59
+ .catch((e) => {
60
+ return Promise.reject(getGenericErrorMessage("saveSFFile", e.message));
61
+ });
62
+ } else {
63
+ // use original image as thumbnail
64
+ fs.writeFileSync(thumbnailFilePath, fileBuffer);
65
+ return {
66
+ finalPath: attachmentFilePath,
67
+ previewPath: thumbnailFilePath
68
+ };
69
+ }
70
+ })
71
+ .catch((e) => {
72
+ return Promise.reject(getGenericErrorMessage("saveSFFile", e.message));
73
+ });
74
+ } else {
75
+ // use fallback thumbnail image
76
+ fs.writeFileSync(thumbnailFilePath, Buffer.from(Utils.fallbackThumbnailImageData, "base64"));
77
+ return Promise.resolve({ finalPath: attachmentFilePath, previewPath: thumbnailFilePath });
78
+ }
79
+ };
80
+
81
+ this.saveSFFile = (id, fileType, content, readFromPath) => {
82
+ try {
83
+
84
+ if (!Utils.isValidParamString(id) || !Utils.isValidParamString(fileType) || !_.isBoolean(readFromPath)) {
85
+ log.error(`Invalid input parameters for saveSFFile. Check the input parameters and try again.`);
86
+ return Promise.reject(Utils.internalServerError);
87
+ }
88
+
89
+ let base64EncodedData;
90
+
91
+ if (readFromPath) {
92
+ // read content data from the local path
93
+ let filePath = content;
94
+
95
+ if (Utils.isFilePathValid(filePath)) {
96
+ // TODO: recheck the file path verification logic
97
+ if (filePath.startsWith(Utils.getAttachmentFolderName() + path.sep)) {
98
+ if (fs.existsSync(filePath)) {
99
+ base64EncodedData = fs.readFileSync(filePath, { encoding: "base64" });
100
+ } else {
101
+ return Promise.reject(getFilePathNotFoundErrorMessage("attachment", "saveSFFile"));
102
+ }
103
+ } else {
104
+ return Promise.reject("This isn’t a supported file path. Specify a valid file path and try again.");
105
+ }
106
+ } else {
107
+ return Promise.reject("Looks like you haven't specified a file path for the saveSFFile operation. Specify a file path and try again.");
108
+ }
109
+ } else {
110
+ base64EncodedData = content;
111
+ }
112
+
113
+ if (Utils.isValidString(base64EncodedData)) {
114
+ const fileBuffer = Buffer.from(base64EncodedData, "base64");
115
+
116
+ // file size in MB
117
+ const fileSize = fileBuffer.length / 1000000;
118
+ if (fileSize > GlobalConfig.MAX_FILE_SIZE) {
119
+ return Promise.reject(`Content data exceeds the size limit of ${GlobalConfig.MAX_FILE_SIZE} for the file ${Utils.getSFFileName(id, fileType)}.`);
120
+ }
121
+
122
+ // get attachment & thumbnail paths
123
+ let attachmentFilePath = Utils.getSFFilePath(id, fileType);
124
+ let thumbnailFilePath = Utils.getSFFileThumbnailPath(id, fileType);
125
+
126
+ // ensure directory for attachment is available
127
+ let attachmentPathDir = path.dirname(attachmentFilePath);
128
+ if (!fs.existsSync(attachmentPathDir)) {
129
+ fs.mkdirSync(attachmentPathDir, { recursive: true });
130
+ }
131
+
132
+ fs.writeFileSync(attachmentFilePath, fileBuffer);
133
+
134
+ // ensure directory for thumbnail is available
135
+ let dirName = path.dirname(thumbnailFilePath);
136
+ if (!fs.existsSync(dirName)) {
137
+ fs.mkdirSync(dirName, { recursive: true });
138
+ }
139
+
140
+ return generateThumbnail(fileBuffer, fileType, attachmentFilePath, thumbnailFilePath);
141
+ } else {
142
+ return Promise.reject(`The attachment data of the ${Utils.getSFFileName(id, fileType)} file is missing in the request for the saveSFFile operation.`);
143
+ }
144
+ } catch (e) {
145
+ return Promise.reject(getGenericErrorMessage("saveSFFile", e.message));
146
+ }
147
+ };
148
+
149
+ this.readSFFile = (id, fileType, returnPath) => {
150
+ const result = { success: false, results: {} };
151
+
152
+ try {
153
+
154
+ if (!Utils.isValidParamString(id) || !Utils.isValidParamString(fileType) || !_.isBoolean(returnPath)) {
155
+ return getInvalidInputErrorResponse("readSFFile");
156
+ }
157
+
158
+ // get attachment & thumbnail paths
159
+ const attachmentFilePath = Utils.getSFFilePath(id, fileType);
160
+ const thumbnailFilePath = Utils.getSFFileThumbnailPath(id, fileType);
161
+
162
+ let errorMessage = "", attachmentPathExists = true, thumbnailPathExists = true;
163
+ if (!fs.existsSync(attachmentFilePath)) {
164
+ attachmentPathExists = false;
165
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(id, fileType) + " attachment", "readSFFile");
166
+ }
167
+ if (!fs.existsSync(thumbnailFilePath)) {
168
+ thumbnailPathExists = false;
169
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(id, fileType) + " thumbnail", "readSFFile");
170
+ }
171
+
172
+ if (!attachmentPathExists || !thumbnailPathExists) {
173
+ result.success = false;
174
+ result.results = { "error_message": errorMessage };
175
+ } else {
176
+ if (returnPath) {
177
+ // return paths
178
+ result.success = true;
179
+ result.results = { "finalPath":Utils.makeRelativeUrl(attachmentFilePath), "previewPath": Utils.makeRelativeUrl(thumbnailFilePath) };
180
+ } else {
181
+ // return data
182
+ const buffer = fs.readFileSync(attachmentFilePath, { encoding: "base64" });
183
+ result.success = true;
184
+ result.results = { "content": buffer, "checksum": md5(buffer) };
185
+ }
186
+ }
187
+ } catch (e) {
188
+ result.success = false;
189
+ result.results = { "error_message": getGenericErrorMessage("readSFFile", e.message) };
190
+ }
191
+
192
+ return result;
193
+ };
194
+
195
+ this.renameSFFile = (oldId, fileType, newId) => {
196
+ const result = { success: false, results: {} };
197
+
198
+ try {
199
+
200
+ if (!Utils.isValidParamString(oldId) || !Utils.isValidParamString(fileType) || !Utils.isValidParamString(newId)) {
201
+ return getInvalidInputErrorResponse("renameSFFile");
202
+ }
203
+
204
+ // get attachment & thumbnail paths
205
+ let oldFilePath = Utils.getSFFilePath(oldId, fileType);
206
+ let oldThumbnailFilePath = Utils.getSFFileThumbnailPath(oldId, fileType);
207
+
208
+ let newFilePath = Utils.getSFFilePath(newId, fileType);
209
+ let newThumbnailFilePath = Utils.getSFFileThumbnailPath(newId, fileType);
210
+ let errorMessage = "", oldFilePathExists = true, oldThumbnailFilePathExists = true;
211
+
212
+ if (!fs.existsSync(oldFilePath)) {
213
+ oldFilePathExists = false;
214
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(oldId, fileType) + " attachment", "renameSFFile");
215
+ }
216
+
217
+ if (!fs.existsSync(oldThumbnailFilePath)) {
218
+ oldThumbnailFilePathExists = false;
219
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(oldId, fileType) + " thumbnail", "renameSFFile");
220
+ }
221
+
222
+ if (oldFilePathExists && oldThumbnailFilePathExists) {
223
+ // rename the attachment and thumbnail file
224
+ fs.renameSync(oldFilePath, newFilePath);
225
+ fs.renameSync(oldThumbnailFilePath, newThumbnailFilePath);
226
+ result.success = true;
227
+ result.results = { "success": true };
228
+ } else {
229
+ result.success = false;
230
+ result.results = { "success": false, "error_message": errorMessage };
231
+ }
232
+ } catch (e) {
233
+ result.success = false;
234
+ result.results = { "success": false, "error_message": getGenericErrorMessage("renameSFFile", e.message) };
235
+ }
236
+
237
+ return result;
238
+ };
239
+
240
+ this.deleteSFFile = (id, fileType) => {
241
+ const result = { success: false, results: {} };
242
+
243
+ try {
244
+
245
+ if (!Utils.isValidParamString(id) || !Utils.isValidParamString(fileType)) {
246
+ return getInvalidInputErrorResponse("deleteSFFile");
247
+ }
248
+
249
+ // get attachment & thumbnail paths
250
+ let filePath = Utils.getSFFilePath(id, fileType);
251
+ let thumbnailFilePath = Utils.getSFFileThumbnailPath(id, fileType);
252
+ let errorMessage = "", filePathExists = true, thumbnailFilePathExists = true;
253
+
254
+ if (!fs.existsSync(filePath)) {
255
+ filePathExists = false;
256
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(id, fileType) + " attachment", "deleteSFFile");
257
+ }
258
+
259
+ if (!fs.existsSync(thumbnailFilePath)) {
260
+ thumbnailFilePathExists = false;
261
+ errorMessage += getFilePathNotFoundErrorMessage(Utils.getSFFileName(id, fileType) + " thumbnail", "deleteSFFile");
262
+ }
263
+
264
+ if (filePathExists && thumbnailFilePathExists) {
265
+ // deleting attachment & thumbnail file
266
+ fs.unlinkSync(filePath);
267
+ fs.unlinkSync(thumbnailFilePath);
268
+ result.success = true;
269
+ result.results = { "success": true };
270
+ } else {
271
+ result.success = false;
272
+ result.results = { "success": false, "error_message": errorMessage };
273
+ }
274
+ } catch (e) {
275
+ result.success = false;
276
+ result.results = { "success": false, "error_message": getGenericErrorMessage("deleteSFFile", e.message) };
277
+ }
278
+
279
+ return result;
280
+ };
281
+ }();
282
+
283
+ module.exports = SFAttachmentsHandler;
package/src/utils.js ADDED
@@ -0,0 +1,91 @@
1
+ /*
2
+ * FILE_HEADER
3
+ */
4
+
5
+ "use strict";
6
+ const _ = require('lodash');
7
+ const path = require("path");
8
+ const GlobalConfig = require("./globalConfig");
9
+
10
+ function Utils() {
11
+ this.fallbackThumbnailImageData = "iVBORw0KGgoAAAANSUhEUgAAAEwAAABMCAYAAADHl1ErAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABWZJREFUeNrsXNtOE0EYnlYEagst0KQ1hLTGRC+4kMQXwCegvAF9gvIGwhvIE8gbAE9gb70gqRfGQEJoRaEcUqpFQUXjfO1ss7R7mJndKbvb/ZPJym734DffP/83/xwICU3IIvf14tbPmzw9LLCSoyVvc0uVlhotFZSJR+PVQANGAUrRQ4GWJVoWaUk5fGSTljItO7RsUwCbgQCMArXCQCooftU2wKPAbfoOMMamVVpKLjBJhnkbtLxRwbpIgIAaCHARF8GCy731AFBGwBUpaNueAIyxaos15F42BIhlp2yLusCqQx+ARdg3HrJvHjxg9MVrjFkp4h9rewP79sG5JH0h2qoVn4v2TeqeReWABQQsadAiQwyWFGiRIQdLGLQoJ1hrAQYLtsIbCCIcYBVYNBwGW7YTuBEOUXroM+ngtFfwxErc2rnk1hCB1dVpUm0Yc8VFvyNwdf2LNK+u20feHoFVbyASVFcEQB+rdXLz+0/33PjoQzKfz5JEbEzaNc0Ytup3sD4cfL0DFgx/4zwH27Q0lb1LMnaV/OyGtdMGuf37z/AazuM6h5UYFrYM8zW7YBfffji6bsUyI8B8zS6XrWQJGBuwUM6uj9UT2p7cKnv+yIOoo+t6ljFMTBm2pBqsL+fNtkvsHZ0qe0d2etLRdStMoj2NvdKhMLBKa3ChjVBUWC4zbSodcB7XBaygb/z1DFM9bkgOjs/vRK+9ozNlLvny2RzJZ6dJKhHr+BY94m+cF3DJPmxGBuWOcMPe6ARdBMYJ1rgQ03IZVx4FbDZ7GaasGwRWHRxfmLZpZprJQ7Z4xyXZxBBl0REs6lXdPGB6qVPOMOoybEFZvoQ27GCRldUb30U6x/dlCwMBjJc9p5ctXwGWU+WKvMyJj495HbCcHrC8Cs1l54p6SyfjXgcsb5XecWxQ8rzRD2JSQht120jOzrQrpgQw/AdEVDzctt5oSbk88lvom8rcLyWKVWgumX4i7hkfHekqczt3B0j69hH3J2KjPNlUbzHMKnnHk8WwCxJoF3f3Pxv+jjOb6h3AeDSXPTvPTAEHoJApVtlUq/s9B5gbnenO4MWJo/vBNM8DZtX9kWGqEfgzkwlu0FRlQjTAqk41V7XecPXD0F3qjXwiWg33cw528FpVD1jNqeZSpeX08gRaTQQ0VKKLcqOmB6wiz4SWssypUeTMTE0Kg+5S5KzodZgUYJ3UzLkrwEA/IRvK04UC00QiIYLAi6ezTjVapcswttBJWA9YhXiZ6Mab2UgnE8IV61BuNLXFYPooWRaNZGhY3TRoOB73npmMS1WIA7lRNpIVO6I1pqrN0piA6Is2sncMU3NLGdAkv7uLTXf2DhtKuuTVXG7LiF5A5vOP2/9+/6nW1neYeYN+ZjIeax/xDbIMx+iR4MDLlDaT5850JwoaJpMV7Gppd/9IeVbg+VyGZKcnlFWO9nwOw1rMZTOlb+uWgxqwQPSFK86mU8qezyk3dky7RmxxZtNpo+xWmgjtGdoqwaF97ud35pDd2kXHTbu+5IbZ3YMeqAAD4JKz6aTSSrGwPiz6pmxaTddEbbjVweY1MAyCUxWztecbsYsYTNvsy7jiBxQ0IPu69xoyoij3YTyZWJdtw2iOa2AnBTvNMBGRScHsh0UyvFY0W9xgtxLkHQnAXH3RbhAF65XZxXDpDKcrWrrkELtm0W4RvW0Plq3uWh8CsNZ5tmoIF5h2jHuBabiEWdUS5oCCpnaRfMBAk9qGQWogl73Iz4FgXQYsaYbpmObVDYqsdBbWdZdlHxBuVjRowHzANm9th2XAtnDDNZ8B558t/UzAg/wIN42UZF24LakDAPPEhxvfhiZo/wUYAL/8G5LiHq3BAAAAAElFTkSuQmCC";
12
+ this.fallbackThumbnailImageFileType = "png";
13
+ this.internalServerError = "Internal Server Error";
14
+
15
+ this.isDefined = value => typeof value !== "undefined" && value !== null;
16
+
17
+ this.isEmptyString = (x) => {
18
+ // returns true if x is null, undefined or a string containing only whitespace
19
+ return x === null || (_.isString(x) && x.trim() === "");
20
+ };
21
+
22
+ this.getThumbnailImageFormat = (fileType) => {
23
+ if (fileType.toUpperCase() === "PNG")
24
+ return "png";
25
+ else if (fileType.toUpperCase() === "JPG")
26
+ return "jpeg";
27
+
28
+ return null;
29
+ };
30
+
31
+ this.getAttachmentFolderName = () => {
32
+ return GlobalConfig.AttachmentsFolder;
33
+ };
34
+
35
+ this.getSFFileName = (id, fileType) => {
36
+ return id + "." + fileType;
37
+ };
38
+
39
+ this.getSFFilePath = (id, fileType) => {
40
+ return path.join(GlobalConfig.AttachmentsFolder, GlobalConfig.SFFilesBlobFolder, this.getSFFileName(id, fileType));
41
+ };
42
+
43
+ this.getSFFileThumbnailPath = (id, fileType) => {
44
+ if (fileType.toUpperCase() === "PNG" || fileType.toUpperCase() === "JPG") {
45
+ // return custom thumbnail image path
46
+ return path.join(GlobalConfig.AttachmentsFolder, GlobalConfig.ThumbnailsFolder, GlobalConfig.SFFilesBlobFolder, this.getSFFileName(id, this.getThumbnailImageFormat(fileType)));
47
+ } else {
48
+ // return fallback thumbnail image path
49
+ return path.join(GlobalConfig.AttachmentsFolder, GlobalConfig.ThumbnailsFolder, GlobalConfig.SFFilesBlobFolder, this.getSFFileName(id, this.fallbackThumbnailImageFileType));
50
+ }
51
+ };
52
+
53
+ this.getParamsMissingErrorMessage = (operation) => {
54
+ return `Some required parameters are missing in the request for the ${operation} operation. Enter values for all the required parameters and try again.`;
55
+ };
56
+
57
+ this.isValidString = (string) => {
58
+ return this.isDefined(string) && _.isString(string) && !this.isEmptyString(string);
59
+ };
60
+
61
+ this.isValidParamString = (string) => {
62
+ return this.isValidString(string) && string.length <= GlobalConfig.MAX_PARAM_STRING_LENGTH;
63
+ };
64
+
65
+ this.isFilePathValid = (string) => {
66
+ return this.isValidString(string) && string.length <= GlobalConfig.MAX_FILE_PATH_LENGTH;
67
+ };
68
+
69
+ // makeRelativeUrl is only needed for Windows filePath to URL conversion
70
+ this.makeRelativeUrl = (filePath) => {
71
+ return filePath.replace(/\\/g, "/");
72
+ };
73
+
74
+ this.sanitizeQuery = (queryStr) => {
75
+ let sanitizeParamsArray = [];
76
+ let params = queryStr.parameters;
77
+ if (_.isArray(params)) {
78
+ params.forEach(param => {
79
+ let evilPattern = /[;/*@]/g;
80
+ if (_.isString(param)) {
81
+ param = param.replace(evilPattern, '');
82
+ }
83
+ sanitizeParamsArray.push(param);
84
+ });
85
+ queryStr.parameters = sanitizeParamsArray;
86
+ }
87
+ return queryStr;
88
+ };
89
+ }
90
+
91
+ module.exports = new Utils();
@@ -0,0 +1,153 @@
1
+ /*
2
+ * FILE_HEADER
3
+ */
4
+
5
+ "use strict";
6
+
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const GlobalConfig = require('./globalConfig');
10
+ const JSZip = require('jszip');
11
+
12
+ const ZipHandler = new function(){
13
+ this.readSFZipFileFromFolder = (filenameId, folder) => {
14
+ const filename = filenameId + '.zip';
15
+ const targetFolder = path.join(folder, GlobalConfig.SFFilesBlobFolder);
16
+ const filePath = path.join(targetFolder, filename);
17
+ let result;
18
+ if (!fs.existsSync(filePath)) {
19
+ // Required file is not in the system
20
+ throw new Error(`Theme attachment with ID ${filenameId} not found.`);
21
+ }
22
+ const stats = fs.statSync(filePath);
23
+ if (stats.size > 10240000){
24
+ // Required file size is bigger than 10 MB
25
+ throw new Error(`Theme attachment with ID ${filenameId} exceeds size.`);
26
+ } else {
27
+ result = fs.readFileSync(filePath, { encoding: 'base64' });
28
+ }
29
+ return result;
30
+ };
31
+
32
+ this.extractZipFiles = (filenameId) => {
33
+ let data;
34
+ try{
35
+ data = this.readSFZipFileFromFolder(filenameId, GlobalConfig.AttachmentsFolder);
36
+ } catch (e) {
37
+ // Not able to read file from folder
38
+ return Promise.reject(e.message);
39
+ }
40
+ const zip = new JSZip();
41
+ return zip.loadAsync(data, { base64: true })
42
+ .then((extractedZip) => extractedZip, () => {
43
+ // Required file is not a zip file
44
+ return Promise.reject(`Theme attachment with ID ${filenameId} is not a valid zip.`);
45
+ });
46
+ };
47
+
48
+ this.createZipFile = (filename, content) => {
49
+ const zip = new JSZip();
50
+
51
+ return zip.file(filename, content).generateAsync({type:"nodebuffer"}).then(fileContent => fileContent, () => {
52
+ return Promise.reject("The zip contains invalid content. Review the zip contents and try again.");
53
+ });
54
+ };
55
+
56
+ this.serializeZipAsJSON = (zip) => {
57
+ const folderFiles = Object.keys(zip.files).map((item) => item.split('/'));
58
+ const fileCache = folderFiles.reduce((result, item) => {
59
+ if (!Object.keys(result).includes(item[0])) result[item[0]] = {};
60
+ return result;
61
+ }, {});
62
+ folderFiles.filter((folderFile) => folderFile.length < 3 && folderFile[1] !== '')
63
+ .forEach((folderFile) => fileCache[folderFile[0]][folderFile[1]] = folderFile[1]);
64
+ return fileCache;
65
+ };
66
+
67
+ this.validateThemeZip = (attachmentId, zip) => {
68
+ const validationResult = { isOk: true };
69
+ const flattenedZip = Object.keys(zip.files).map((item) => item.split('/'));
70
+ const serializedZip = this.serializeZipAsJSON(zip);
71
+ const flattenedFiles = Object.keys(serializedZip).reduce((result, key) => {
72
+ result.push(...Object.keys(serializedZip[key]));
73
+ return result;
74
+ }, []);
75
+
76
+ const zipFailsByStructure = () => {
77
+ return flattenedZip.some((fileRoute) => fileRoute.length > 2) || // Subfolders were found
78
+ !Object.keys(serializedZip).every((key) => GlobalConfig.validThemeFolders.includes(key)) || // Additional top level folders were found
79
+ !GlobalConfig.validThemeFolders.every((key) => Object.keys(serializedZip).includes(key)); // 'Application' or 'Framework' were not found
80
+ };
81
+
82
+ const zipFailsByEmpty = () => {
83
+ const containsValidFiles = flattenedFiles.some((fileItem) => {
84
+ const splittedItem = fileItem.split('.');
85
+ return GlobalConfig.validFileTypesForThemes.includes(splittedItem[1]);
86
+ });
87
+ return Object.keys(serializedZip).every((key) => Object.keys(key).length < 1) || // No Files were found
88
+ !containsValidFiles; // Not valid types were found
89
+ };
90
+
91
+ const zipFailsByInvalidFileTypes = () => flattenedFiles.some((fileItem) => { // Every file in zip should be *.svg, *.png or *.gif
92
+ const splittedItem = fileItem.split('.');
93
+ return !GlobalConfig.validFileTypesForThemes.includes(splittedItem[1]);
94
+ });
95
+
96
+ const zipFailsByRepeatedFileNames = () => { // The file names cannot be repeated using different extensions
97
+ const filesBackup = {};
98
+ return flattenedFiles.some((fileItem) => {
99
+ const splittedItem = fileItem.split('.');
100
+ if (!Object.keys(filesBackup).includes(splittedItem[0])) { // File is not in the backup
101
+ filesBackup[splittedItem[0]] = splittedItem[1];
102
+ return false;
103
+ } else {
104
+ return Object.keys(filesBackup).includes(splittedItem[0]) && filesBackup[splittedItem[0]] !== splittedItem[1]; // File is in the backup, so we check the extension
105
+ }
106
+ });
107
+ };
108
+
109
+ if (zipFailsByStructure()) {
110
+ // Occurs when the mandatory folders "Application" or "Framework" are missing.
111
+ // Or when an additional toplevel folder was found. Or when inside of Application or Framework subfolders were found.
112
+ validationResult.isOk = false;
113
+ validationResult.details = `Theme attachment with ID ${attachmentId} has wrong folder structure.`;
114
+ } else if (zipFailsByEmpty()) {
115
+ // No files in theme zip
116
+ validationResult.isOk = false;
117
+ validationResult.details = `Theme attachment with ID ${attachmentId} is empty.`;
118
+ } else if (zipFailsByInvalidFileTypes()) {
119
+ // Found files other than *.svg, *.png or *.gif
120
+ validationResult.isOk = false;
121
+ validationResult.details = `Theme attachment with ID ${attachmentId} has unsupported file content.`;
122
+ } else if (zipFailsByRepeatedFileNames()) {
123
+ // Found a *.png and *.svg file with the same name e.g. myPic.png and myPic.svg
124
+ validationResult.isOk = false;
125
+ validationResult.details = `Theme attachment with ID ${attachmentId} has files with the same name but different file ending.`;
126
+ }
127
+ return validationResult;
128
+ };
129
+
130
+ this.writeZipFilesOnMemory = (zip) => {
131
+ const promiseResult = [];
132
+ // Remove previous theme zip extraction
133
+ if (fs.existsSync(GlobalConfig.ThemeImagesFolder)) {
134
+ fs.rmdirSync(GlobalConfig.ThemeImagesFolder, { recursive: true, force: true });
135
+ }
136
+ fs.mkdirSync(GlobalConfig.ThemeImagesFolder);
137
+ GlobalConfig.validThemeFolders.forEach((folder) => {
138
+ fs.mkdirSync(path.join(GlobalConfig.ThemeImagesFolder, folder));
139
+ Object.keys(zip.files).filter((entry) => entry.includes(`${folder}/`))
140
+ .forEach((fileRoute) => {
141
+ if(!zip.files[fileRoute].dir) {
142
+ promiseResult.push(zip.files[fileRoute].async('uint8array').then((fileContent) => {
143
+ fs.writeFileSync(path.join(GlobalConfig.ThemeImagesFolder, fileRoute), fileContent);
144
+ return fileContent;
145
+ }));
146
+ }
147
+ });
148
+ });
149
+ return Promise.all(promiseResult);
150
+ };
151
+ }();
152
+
153
+ module.exports = ZipHandler;