@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.
- package/LICENSE.md +37 -0
- package/README.md +7 -0
- package/bin/libsqliteExtension.dll +0 -0
- package/bin/libsqliteExtension.dylib +0 -0
- package/binding.gyp +21 -0
- package/configuration.template.json +24 -0
- package/log4js.json +11 -0
- package/nativeSrc/PriceEngineWrap.cc +157 -0
- package/nativeSrc/PriceEngineWrap.h +24 -0
- package/nativeSrc/common/DBAccess/SimpleDBConnection.cpp +800 -0
- package/nativeSrc/common/DBAccess/SimpleDBConnection.h +54 -0
- package/nativeSrc/common/Libs/cJSON/CHANGELOG.md +428 -0
- package/nativeSrc/common/Libs/cJSON/LICENSE +20 -0
- package/nativeSrc/common/Libs/cJSON/README.md +571 -0
- package/nativeSrc/common/Libs/cJSON/cJSON.c +3110 -0
- package/nativeSrc/common/Libs/cJSON/cJSON.h +293 -0
- package/nativeSrc/common/Libs/sqlcipher/sqlite3.c +241624 -0
- package/nativeSrc/common/Libs/sqlcipher/sqlite3.h +12836 -0
- package/nativeSrc/common/Libs/sqlcipher/sqlite3ext.h +701 -0
- package/nativeSrc/common/LogAdapter/LogAdapter.cpp +25 -0
- package/nativeSrc/common/LogAdapter/LogAdapter.h +20 -0
- package/nativeSrc/common/PriceEngine/PriceEngine.cpp +251 -0
- package/nativeSrc/common/PriceEngine/PriceEngine.h +67 -0
- package/nativeSrc/common/Utils/StringFormat.cpp +905 -0
- package/nativeSrc/common/Utils/StringFormat.h +116 -0
- package/nativeSrc/common/Utils/miniz/timer.cpp +165 -0
- package/nativeSrc/common/Utils/miniz/timer.h +40 -0
- package/nativeSrc/common/stdngm.h +92 -0
- package/nativeSrc/nativeWrapper.cc +15 -0
- package/package.json +70 -0
- package/src/argsParser.js +73 -0
- package/src/bootstrap.js +156 -0
- package/src/fsHelper.js +36 -0
- package/src/globalConfig.js +23 -0
- package/src/local.js +546 -0
- package/src/server.js +64 -0
- package/src/sfAttachmentsHandler.js +283 -0
- package/src/utils.js +91 -0
- 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;
|