node-server-engine 1.2.9 → 1.3.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 (33) hide show
  1. package/.env.example +4 -1
  2. package/README.md +128 -107
  3. package/dist/entities/Endpoint/Endpoint.d.ts +2 -0
  4. package/dist/entities/Endpoint/Endpoint.js +11 -2
  5. package/dist/entities/Endpoint/Endpoint.js.map +1 -1
  6. package/dist/entities/Endpoint/Endpoint.types.d.ts +3 -0
  7. package/dist/entities/Endpoint/Endpoint.types.js.map +1 -1
  8. package/dist/middleware/fileUploader/fileUploader.js +45 -3
  9. package/dist/middleware/fileUploader/fileUploader.js.map +1 -1
  10. package/dist/middleware/multiPartFileUploader/index.d.ts +1 -0
  11. package/dist/middleware/multiPartFileUploader/index.js +11 -0
  12. package/dist/middleware/multiPartFileUploader/index.js.map +1 -1
  13. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.d.ts +4 -3
  14. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js +169 -105
  15. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js.map +1 -1
  16. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.types.d.ts +20 -0
  17. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.types.js +2 -0
  18. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.types.js.map +1 -0
  19. package/dist/middleware/multiPartFileUploader/validate/index.d.ts +1 -0
  20. package/dist/middleware/multiPartFileUploader/validate/index.js +17 -0
  21. package/dist/middleware/multiPartFileUploader/validate/index.js.map +1 -0
  22. package/dist/middleware/multiPartFileUploader/validate/validate.d.ts +4 -0
  23. package/dist/middleware/multiPartFileUploader/validate/validate.js +28 -0
  24. package/dist/middleware/multiPartFileUploader/validate/validate.js.map +1 -0
  25. package/package.json +3 -1
  26. package/src/entities/Endpoint/Endpoint.ts +22 -3
  27. package/src/entities/Endpoint/Endpoint.types.ts +3 -0
  28. package/src/middleware/fileUploader/fileUploader.ts +51 -2
  29. package/src/middleware/multiPartFileUploader/index.ts +1 -0
  30. package/src/middleware/multiPartFileUploader/multiPartFileUploader.ts +193 -128
  31. package/src/middleware/multiPartFileUploader/multiPartFileUploader.types.ts +23 -0
  32. package/src/middleware/multiPartFileUploader/validate/index.ts +1 -0
  33. package/src/middleware/multiPartFileUploader/validate/validate.ts +27 -0
@@ -9,148 +9,212 @@ var _multer = _interopRequireDefault(require("multer"));
9
9
  var _fsExtra = _interopRequireDefault(require("fs-extra"));
10
10
  var _path = _interopRequireDefault(require("path"));
11
11
  var _utils = require("../../utils");
12
- const TEMP_DIR = process.env.TEMP_CHUNK_FOLDER || './uploads/temp_chunks';
12
+ var _WebError = require("../../entities/WebError");
13
+ var _validate = require("./validate");
13
14
  const namespace = 'engine:middleware:multiPartFileUploader';
15
+ const TEMP_DIR = _path.default.join(process.env.FILE_STORAGE_FOLDER || './uploads', 'temp_chunks');
16
+ const FILE_DIR = _path.default.join(process.env.FILE_STORAGE_FOLDER || './uploads', 'completed_files');
17
+ _fsExtra.default.ensureDirSync(FILE_DIR);
14
18
  _fsExtra.default.ensureDirSync(TEMP_DIR);
15
19
  const storage = _multer.default.memoryStorage();
16
- async function multiPartFileUploader(req, res, next) {
17
- try {
18
- const uploadSingle = (0, _multer.default)({
19
- storage
20
- }).single('file');
21
- uploadSingle(req, res, async err => {
22
- if (err) {
23
- (0, _utils.reportError)('Error uploading chunk');
24
- return next(err);
25
- }
26
- const {
27
- filename,
28
- totalChunks,
29
- chunkIndex,
30
- uniqueID
31
- } = req.body;
32
- if (!filename || !totalChunks || chunkIndex === undefined || !uniqueID || !req.file) {
33
- req.body.uploadDetails = {
34
- status: 'failed',
35
- message: 'Missing required fields',
36
- details: {
20
+ function multiPartFileUploader(filesOption, validator) {
21
+ return async (req, res, next) => {
22
+ try {
23
+ (0, _utils.reportDebug)({
24
+ namespace,
25
+ message: 'Starting file upload middleware'
26
+ });
27
+ const uploadSingle = (0, _multer.default)({
28
+ storage
29
+ }).single('file');
30
+ uploadSingle(req, res, async err => {
31
+ if (err) {
32
+ (0, _utils.reportError)('Error uploading chunk');
33
+ return next(new _WebError.WebError({
34
+ statusCode: 500,
35
+ errorCode: 'upload-failed',
36
+ message: 'Error uploading file chunk',
37
+ data: {
38
+ error: err.message
39
+ }
40
+ }));
41
+ }
42
+ try {
43
+ (0, _utils.reportDebug)({
44
+ namespace,
45
+ message: 'Validating request'
46
+ });
47
+ await (0, _validate.validate)(req, validator);
48
+ } catch (error) {
49
+ (0, _utils.reportDebug)({
50
+ namespace,
51
+ message: 'Validation failed',
52
+ data: {
53
+ error
54
+ }
55
+ });
56
+ return next(new _WebError.WebError({
57
+ statusCode: 400,
58
+ errorCode: 'validation-failed',
59
+ message: 'Validation failed for file upload',
60
+ data: {
61
+ error: error.message
62
+ }
63
+ }));
64
+ }
65
+ const {
66
+ filename,
67
+ totalChunks,
68
+ chunkIndex,
69
+ uniqueID
70
+ } = req.body;
71
+ (0, _utils.reportDebug)({
72
+ namespace,
73
+ message: 'Received chunk data',
74
+ data: {
37
75
  filename,
38
76
  totalChunks,
39
77
  chunkIndex,
40
- uniqueID,
41
- receivedChunk: !!req.file
78
+ uniqueID
42
79
  }
43
- };
44
- (0, _utils.reportDebug)({
45
- namespace,
46
- message: `Missing required fields`,
47
- data: {
48
- status: 'failed',
49
- details: {
80
+ });
81
+ if (!filename || !totalChunks || chunkIndex === undefined || !uniqueID || !req.file) {
82
+ return next(new _WebError.WebError({
83
+ statusCode: 400,
84
+ errorCode: 'missing-field',
85
+ message: 'Missing required fields',
86
+ data: {
50
87
  filename,
51
88
  totalChunks,
52
89
  chunkIndex,
53
- uniqueID
90
+ uniqueID,
91
+ receivedChunk: !!req.file
54
92
  }
55
- }
56
- });
57
- return next();
58
- }
59
- const sessionFolder = _path.default.join(TEMP_DIR, uniqueID);
60
- _fsExtra.default.ensureDirSync(sessionFolder);
61
- const chunkPath = _path.default.join(sessionFolder, `${filename}.part${chunkIndex}`);
62
- try {
63
- await _fsExtra.default.writeFile(chunkPath, req.file.buffer);
64
- } catch (writeErr) {
65
- (0, _utils.reportError)(`Failed to write chunk ${chunkIndex}: ${writeErr.message}`);
66
- return next(writeErr);
67
- }
68
- const uploadedChunks = await _fsExtra.default.readdir(sessionFolder);
69
- if (uploadedChunks.length === parseInt(totalChunks, 10)) {
70
- (0, _utils.reportDebug)({
71
- namespace,
72
- message: `All chunks uploaded. Merging file...`
73
- });
74
- try {
75
- await mergeChunks(filename, totalChunks, sessionFolder);
76
- } catch (mergeErr) {
77
- (0, _utils.reportError)(`File merging failed: ${mergeErr.message}`);
78
- return next(mergeErr);
93
+ }));
79
94
  }
80
- const finalFilePath = _path.default.join(sessionFolder, filename);
81
- if (!_fsExtra.default.existsSync(finalFilePath)) {
82
- (0, _utils.reportError)('File merging failed: File does not exist');
83
- return next(new Error('File merging failed'));
95
+ if (filesOption?.maxSize && req.file.size > parseInt(filesOption?.maxSize, 10)) {
96
+ return next(new _WebError.WebError({
97
+ statusCode: 400,
98
+ errorCode: 'chunk-too-large',
99
+ message: 'Uploaded chunk exceeds the maximum allowed size',
100
+ data: {
101
+ maxSize: filesOption.maxSize,
102
+ receivedSize: req.file.size
103
+ }
104
+ }));
84
105
  }
85
- req.body.uploadDetails = {
86
- status: 'completed',
87
- message: 'File uploaded and merged successfully',
88
- details: {
89
- filename,
90
- uniqueID
91
- }
92
- };
106
+ const sessionFolder = _path.default.join(TEMP_DIR, uniqueID);
107
+ _fsExtra.default.ensureDirSync(sessionFolder);
108
+ const chunkPath = _path.default.join(sessionFolder, `${filename}.part${chunkIndex}`);
109
+ try {
110
+ (0, _utils.reportDebug)({
111
+ namespace,
112
+ message: 'Writing chunk to disk',
113
+ data: {
114
+ chunkPath
115
+ }
116
+ });
117
+ await _fsExtra.default.writeFile(chunkPath, req.file.buffer);
118
+ } catch (writeErr) {
119
+ return next(new _WebError.WebError({
120
+ statusCode: 500,
121
+ errorCode: 'chunk-write-failed',
122
+ message: 'Failed to write chunk',
123
+ data: {
124
+ chunkIndex,
125
+ error: writeErr.message
126
+ }
127
+ }));
128
+ }
129
+ const uploadedChunks = await _fsExtra.default.readdir(sessionFolder);
93
130
  (0, _utils.reportDebug)({
94
131
  namespace,
95
- message: `File merged successfully`,
132
+ message: 'Uploaded chunk count',
96
133
  data: {
97
- status: 'completed',
98
- details: {
99
- filename,
100
- uniqueID
101
- }
134
+ uploadedChunks: uploadedChunks.length,
135
+ totalChunks
102
136
  }
103
137
  });
104
- return next();
105
- }
106
- (0, _utils.reportDebug)({
107
- namespace,
108
- message: `Chunk uploaded successfully`,
109
- data: {
110
- filename,
111
- uniqueID,
112
- chunkIndex,
113
- totalChunks
138
+ if (uploadedChunks.length === parseInt(totalChunks, 10)) {
139
+ try {
140
+ const finalFilePath = _path.default.join(FILE_DIR, `${uniqueID}_${filename}`);
141
+ await mergeChunks(filename, totalChunks, sessionFolder, finalFilePath);
142
+ req.multipartFile = {
143
+ isPending: false,
144
+ originalname: filename,
145
+ uniqueID,
146
+ filePath: finalFilePath
147
+ };
148
+ } catch (mergeErr) {
149
+ return next(new _WebError.WebError({
150
+ statusCode: 500,
151
+ errorCode: 'file-merge-failed',
152
+ message: 'Error merging file chunks',
153
+ data: {
154
+ error: mergeErr.message
155
+ }
156
+ }));
157
+ }
158
+ return next();
114
159
  }
115
- });
116
- req.body.uploadDetails = {
117
- status: 'pending',
118
- message: 'Chunk uploaded successfully',
119
- details: {
120
- filename,
160
+ req.multipartFile = {
161
+ isPending: true,
162
+ originalname: filename,
121
163
  uniqueID,
122
164
  chunkIndex,
123
165
  totalChunks
166
+ };
167
+ next();
168
+ });
169
+ } catch (error) {
170
+ next(new _WebError.WebError({
171
+ statusCode: 500,
172
+ errorCode: 'unexpected-error',
173
+ message: 'Unexpected error in file upload',
174
+ data: {
175
+ error: error.message
124
176
  }
125
- };
126
- return next();
127
- });
128
- } catch (error) {
129
- (0, _utils.reportError)(`Failed to upload or process chunk: ${error.message}`);
130
- next(error);
131
- }
177
+ }));
178
+ }
179
+ };
132
180
  }
133
- async function mergeChunks(filename, totalChunks, sessionFolder) {
134
- const finalFilePath = _path.default.join(sessionFolder, filename);
181
+ async function mergeChunks(filename, totalChunks, sessionFolder, finalFilePath) {
135
182
  const writeStream = _fsExtra.default.createWriteStream(finalFilePath);
183
+ (0, _utils.reportDebug)({
184
+ namespace,
185
+ message: 'Merging file chunks',
186
+ data: {
187
+ finalFilePath
188
+ }
189
+ });
136
190
  for (let i = 0; i < parseInt(totalChunks, 10); i++) {
137
191
  const partPath = _path.default.join(sessionFolder, `${filename}.part${i}`);
138
192
  try {
139
193
  const chunkData = await _fsExtra.default.readFile(partPath);
140
194
  writeStream.write(chunkData);
141
195
  } catch (error) {
142
- (0, _utils.reportError)(`Error reading chunk ${i}: ${error.message}`);
143
- throw error;
196
+ throw new _WebError.WebError({
197
+ statusCode: 500,
198
+ errorCode: 'chunk-read-failed',
199
+ message: `Error reading chunk ${i}`,
200
+ data: {
201
+ error: error.message
202
+ }
203
+ });
144
204
  }
145
205
  _fsExtra.default.remove(partPath).catch(err => (0, _utils.reportError)(`Error removing part ${partPath}: ${err.message}`));
146
206
  }
147
207
  writeStream.end();
148
208
  return new Promise((resolve, reject) => {
149
209
  writeStream.on('finish', () => resolve(undefined));
150
- writeStream.on('error', error => {
151
- (0, _utils.reportError)(`Error merging chunks: ${error.message}`);
152
- reject(error);
153
- });
210
+ writeStream.on('error', error => reject(new _WebError.WebError({
211
+ statusCode: 500,
212
+ errorCode: 'file-merge-error',
213
+ message: 'Error merging file chunks',
214
+ data: {
215
+ error: error.message
216
+ }
217
+ })));
154
218
  });
155
219
  }
156
220
  //# sourceMappingURL=multiPartFileUploader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"multiPartFileUploader.js","names":["_multer","_interopRequireDefault","require","_fsExtra","_path","_utils","TEMP_DIR","process","env","TEMP_CHUNK_FOLDER","namespace","fs","ensureDirSync","storage","multer","memoryStorage","multiPartFileUploader","req","res","next","uploadSingle","single","err","reportError","filename","totalChunks","chunkIndex","uniqueID","body","undefined","file","uploadDetails","status","message","details","receivedChunk","reportDebug","data","sessionFolder","path","join","chunkPath","writeFile","buffer","writeErr","uploadedChunks","readdir","length","parseInt","mergeChunks","mergeErr","finalFilePath","existsSync","Error","error","writeStream","createWriteStream","i","partPath","chunkData","readFile","write","remove","catch","end","Promise","resolve","reject","on"],"sources":["../../../src/middleware/multiPartFileUploader/multiPartFileUploader.ts"],"sourcesContent":["import express from 'express';\nimport multer from 'multer';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { reportDebug, reportError } from 'utils';\n\nconst TEMP_DIR: string =\n process.env.TEMP_CHUNK_FOLDER || './uploads/temp_chunks';\n\nconst namespace = 'engine:middleware:multiPartFileUploader';\n\nfs.ensureDirSync(TEMP_DIR);\n\nconst storage = multer.memoryStorage();\n\nasync function multiPartFileUploader(\n req: express.Request,\n res: express.Response,\n next: express.NextFunction\n): Promise<void> {\n try {\n const uploadSingle = multer({ storage }).single('file');\n\n uploadSingle(req, res, async (err) => {\n if (err) {\n reportError('Error uploading chunk');\n return next(err);\n }\n\n const { filename, totalChunks, chunkIndex, uniqueID } = req.body;\n\n if (\n !filename ||\n !totalChunks ||\n chunkIndex === undefined ||\n !uniqueID ||\n !req.file\n ) {\n req.body.uploadDetails = {\n status: 'failed',\n message: 'Missing required fields',\n details: {\n filename,\n totalChunks,\n chunkIndex,\n uniqueID,\n receivedChunk: !!req.file\n }\n };\n reportDebug({\n namespace,\n message: `Missing required fields`,\n data: {\n status: 'failed',\n details: {\n filename,\n totalChunks,\n chunkIndex,\n uniqueID\n }\n }\n });\n return next();\n }\n\n const sessionFolder = path.join(TEMP_DIR, uniqueID);\n fs.ensureDirSync(sessionFolder);\n\n const chunkPath = path.join(\n sessionFolder,\n `${filename}.part${chunkIndex}`\n );\n\n try {\n await fs.writeFile(chunkPath, req.file.buffer);\n } catch (writeErr) {\n reportError(\n `Failed to write chunk ${chunkIndex}: ${(writeErr as Error).message}`\n );\n return next(writeErr);\n }\n\n const uploadedChunks = await fs.readdir(sessionFolder);\n\n if (uploadedChunks.length === parseInt(totalChunks, 10)) {\n reportDebug({\n namespace,\n message: `All chunks uploaded. Merging file...`\n });\n\n try {\n await mergeChunks(filename, totalChunks, sessionFolder);\n } catch (mergeErr) {\n reportError(`File merging failed: ${(mergeErr as Error).message}`);\n return next(mergeErr);\n }\n\n const finalFilePath = path.join(sessionFolder, filename);\n\n if (!fs.existsSync(finalFilePath)) {\n reportError('File merging failed: File does not exist');\n return next(new Error('File merging failed'));\n }\n\n req.body.uploadDetails = {\n status: 'completed',\n message: 'File uploaded and merged successfully',\n details: { filename, uniqueID }\n };\n reportDebug({\n namespace,\n message: `File merged successfully`,\n data: {\n status: 'completed',\n details: { filename, uniqueID }\n }\n });\n\n return next();\n }\n\n reportDebug({\n namespace,\n message: `Chunk uploaded successfully`,\n data: {\n filename,\n uniqueID,\n chunkIndex,\n totalChunks\n }\n });\n\n req.body.uploadDetails = {\n status: 'pending',\n message: 'Chunk uploaded successfully',\n details: { filename, uniqueID, chunkIndex, totalChunks }\n };\n\n return next();\n });\n } catch (error) {\n reportError(\n `Failed to upload or process chunk: ${(error as Error).message}`\n );\n next(error);\n }\n}\n\nasync function mergeChunks(\n filename: string,\n totalChunks: string | number,\n sessionFolder: string\n): Promise<void> {\n const finalFilePath = path.join(sessionFolder, filename);\n const writeStream = fs.createWriteStream(finalFilePath);\n\n for (let i = 0; i < parseInt(totalChunks as string, 10); i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n\n try {\n const chunkData = await fs.readFile(partPath);\n writeStream.write(chunkData);\n } catch (error) {\n reportError(`Error reading chunk ${i}: ${(error as Error).message}`);\n throw error;\n }\n\n fs.remove(partPath).catch((err: Error) =>\n reportError(`Error removing part ${partPath}: ${err.message}`)\n );\n }\n\n writeStream.end();\n\n return new Promise<void>((resolve, reject) => {\n writeStream.on('finish', () => resolve(undefined));\n writeStream.on('error', (error) => {\n reportError(`Error merging chunks: ${(error as Error).message}`);\n reject(error);\n });\n });\n}\n\nexport { multiPartFileUploader };\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"multiPartFileUploader.js","names":["_multer","_interopRequireDefault","require","_fsExtra","_path","_utils","_WebError","_validate","namespace","TEMP_DIR","path","join","process","env","FILE_STORAGE_FOLDER","FILE_DIR","fs","ensureDirSync","storage","multer","memoryStorage","multiPartFileUploader","filesOption","validator","req","res","next","reportDebug","message","uploadSingle","single","err","reportError","WebError","statusCode","errorCode","data","error","validate","filename","totalChunks","chunkIndex","uniqueID","body","undefined","file","receivedChunk","maxSize","size","parseInt","receivedSize","sessionFolder","chunkPath","writeFile","buffer","writeErr","uploadedChunks","readdir","length","finalFilePath","mergeChunks","multipartFile","isPending","originalname","filePath","mergeErr","writeStream","createWriteStream","i","partPath","chunkData","readFile","write","remove","catch","end","Promise","resolve","reject","on"],"sources":["../../../src/middleware/multiPartFileUploader/multiPartFileUploader.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport multer from 'multer';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { Schema } from 'express-validator';\nimport { reportDebug, reportError } from 'utils';\nimport { WebError } from 'entities/WebError';\nimport { validate } from './validate';\nimport { MultiPartFileUploaderConfig } from './multiPartFileUploader.types';\n\nconst namespace = 'engine:middleware:multiPartFileUploader';\n\nconst TEMP_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'temp_chunks'\n);\nconst FILE_DIR: string = path.join(\n process.env.FILE_STORAGE_FOLDER || './uploads',\n 'completed_files'\n);\n\nfs.ensureDirSync(FILE_DIR);\nfs.ensureDirSync(TEMP_DIR);\n\nconst storage = multer.memoryStorage();\n\nexport function multiPartFileUploader(\n filesOption: MultiPartFileUploaderConfig,\n validator: Schema\n) {\n return async (\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> => {\n try {\n reportDebug({ namespace, message: 'Starting file upload middleware' });\n\n const uploadSingle = multer({ storage }).single('file');\n uploadSingle(req, res, async (err) => {\n if (err) {\n reportError('Error uploading chunk');\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'upload-failed',\n message: 'Error uploading file chunk',\n data: { error: err.message }\n })\n );\n }\n\n try {\n reportDebug({ namespace, message: 'Validating request' });\n await validate(req, validator);\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Validation failed',\n data: { error }\n });\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'validation-failed',\n message: 'Validation failed for file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n\n const { filename, totalChunks, chunkIndex, uniqueID } = req.body;\n reportDebug({\n namespace,\n message: 'Received chunk data',\n data: { filename, totalChunks, chunkIndex, uniqueID }\n });\n\n if (\n !filename ||\n !totalChunks ||\n chunkIndex === undefined ||\n !uniqueID ||\n !req.file\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'missing-field',\n message: 'Missing required fields',\n data: {\n filename,\n totalChunks,\n chunkIndex,\n uniqueID,\n receivedChunk: !!req.file\n }\n })\n );\n }\n\n if (\n filesOption?.maxSize &&\n req.file.size > parseInt(filesOption?.maxSize, 10)\n ) {\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'chunk-too-large',\n message: 'Uploaded chunk exceeds the maximum allowed size',\n data: {\n maxSize: filesOption.maxSize,\n receivedSize: req.file.size\n }\n })\n );\n }\n\n const sessionFolder = path.join(TEMP_DIR, uniqueID);\n fs.ensureDirSync(sessionFolder);\n const chunkPath = path.join(\n sessionFolder,\n `${filename}.part${chunkIndex}`\n );\n\n try {\n reportDebug({\n namespace,\n message: 'Writing chunk to disk',\n data: { chunkPath }\n });\n await fs.writeFile(chunkPath, req.file.buffer);\n } catch (writeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'chunk-write-failed',\n message: 'Failed to write chunk',\n data: { chunkIndex, error: (writeErr as Error).message }\n })\n );\n }\n\n const uploadedChunks = await fs.readdir(sessionFolder);\n reportDebug({\n namespace,\n message: 'Uploaded chunk count',\n data: { uploadedChunks: uploadedChunks.length, totalChunks }\n });\n\n if (uploadedChunks.length === parseInt(totalChunks, 10)) {\n try {\n const finalFilePath = path.join(\n FILE_DIR,\n `${uniqueID}_${filename}`\n );\n await mergeChunks(\n filename,\n totalChunks,\n sessionFolder,\n finalFilePath\n );\n req.multipartFile = {\n isPending: false,\n originalname: filename,\n uniqueID,\n filePath: finalFilePath\n };\n } catch (mergeErr) {\n return next(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-failed',\n message: 'Error merging file chunks',\n data: { error: (mergeErr as Error).message }\n })\n );\n }\n return next();\n }\n\n req.multipartFile = {\n isPending: true,\n originalname: filename,\n uniqueID,\n chunkIndex,\n totalChunks\n };\n next();\n });\n } catch (error) {\n next(\n new WebError({\n statusCode: 500,\n errorCode: 'unexpected-error',\n message: 'Unexpected error in file upload',\n data: { error: (error as Error).message }\n })\n );\n }\n };\n}\n\nasync function mergeChunks(\n filename: string,\n totalChunks: string | number,\n sessionFolder: string,\n finalFilePath: string\n): Promise<void> {\n const writeStream = fs.createWriteStream(finalFilePath);\n reportDebug({\n namespace,\n message: 'Merging file chunks',\n data: { finalFilePath }\n });\n\n for (let i = 0; i < parseInt(totalChunks as string, 10); i++) {\n const partPath = path.join(sessionFolder, `${filename}.part${i}`);\n try {\n const chunkData = await fs.readFile(partPath);\n writeStream.write(chunkData);\n } catch (error) {\n throw new WebError({\n statusCode: 500,\n errorCode: 'chunk-read-failed',\n message: `Error reading chunk ${i}`,\n data: { error: (error as Error).message }\n });\n }\n fs.remove(partPath).catch((err: Error) =>\n reportError(`Error removing part ${partPath}: ${err.message}`)\n );\n }\n\n writeStream.end();\n return new Promise<void>((resolve, reject) => {\n writeStream.on('finish', () => resolve(undefined));\n writeStream.on('error', (error) =>\n reject(\n new WebError({\n statusCode: 500,\n errorCode: 'file-merge-error',\n message: 'Error merging file chunks',\n data: { error: (error as Error).message }\n })\n )\n );\n });\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,20 @@
1
+ declare global {
2
+ namespace Express {
3
+ interface Request {
4
+ /** Uploaded files mapped by field name */
5
+ multipartFile?: {
6
+ isPending: boolean;
7
+ originalname: string;
8
+ uniqueID: string;
9
+ filePath?: string;
10
+ chunkIndex?: number;
11
+ totalChunks?: number;
12
+ };
13
+ }
14
+ }
15
+ }
16
+ /** Configuration for Multi part file upload */
17
+ export interface MultiPartFileUploaderConfig {
18
+ required?: boolean;
19
+ maxSize?: string;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=multiPartFileUploader.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiPartFileUploader.types.js","names":[],"sources":["../../../src/middleware/multiPartFileUploader/multiPartFileUploader.types.ts"],"sourcesContent":["// Extend Express request object to include `files`\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** Uploaded files mapped by field name */\n multipartFile?: {\n isPending: boolean;\n originalname: string;\n uniqueID: string;\n filePath?: string;\n chunkIndex?: number;\n totalChunks?: number;\n };\n }\n }\n}\n\n/** Configuration for Multi part file upload */\nexport interface MultiPartFileUploaderConfig {\n required?: boolean;\n maxSize?: string;\n}\n"],"mappings":"","ignoreList":[]}
@@ -0,0 +1 @@
1
+ export * from './validate';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _validate = require("./validate");
7
+ Object.keys(_validate).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _validate[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _validate[key];
14
+ }
15
+ });
16
+ });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_validate","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get"],"sources":["../../../../src/middleware/multiPartFileUploader/validate/index.ts"],"sourcesContent":["export * from './validate';\n"],"mappings":";;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
@@ -0,0 +1,4 @@
1
+ import { Request } from 'express';
2
+ import { Schema } from 'express-validator';
3
+ /** Validate a schema against a request using our validation middleware */
4
+ export declare function validate(request: Request, schema: Schema): Promise<void>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.validate = validate;
7
+ var _validate = require("../../validate");
8
+ /** Validate a schema against a request using our validation middleware */
9
+ async function validate(request, schema) {
10
+ return new Promise((resolve, reject) => {
11
+ // Generate a validation chain using our middleware
12
+ const validationChain = (0, _validate.validate)(schema).flat();
13
+ if (!validationChain.length) resolve();
14
+ // Recursively go through each middleware by imitating the next function from express
15
+ let position = -1;
16
+ const next = error => {
17
+ if (error) reject(error);
18
+ position++;
19
+ if (position === validationChain.length) {
20
+ resolve();
21
+ return;
22
+ }
23
+ validationChain[position](request, undefined, next);
24
+ };
25
+ next();
26
+ });
27
+ }
28
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","names":["_validate","require","validate","request","schema","Promise","resolve","reject","validationChain","validateMiddleware","flat","length","position","next","error","undefined"],"sources":["../../../../src/middleware/multiPartFileUploader/validate/validate.ts"],"sourcesContent":["import { Request } from 'express';\nimport { Schema } from 'express-validator';\nimport { validate as validateMiddleware } from 'middleware/validate';\n\n/** Validate a schema against a request using our validation middleware */\nexport async function validate(\n request: Request,\n schema: Schema\n): Promise<void> {\n return new Promise((resolve, reject) => {\n // Generate a validation chain using our middleware\n const validationChain = validateMiddleware(schema).flat();\n if (!validationChain.length) resolve();\n // Recursively go through each middleware by imitating the next function from express\n let position = -1;\n const next = (error?: unknown): void => {\n if (error) reject(error);\n position++;\n if (position === validationChain.length) {\n resolve();\n return;\n }\n validationChain[position](request, undefined, next);\n };\n next();\n });\n}\n"],"mappings":";;;;;;AAEA,IAAAA,SAAA,GAAAC,OAAA;AAEA;AACO,eAAeC,QAAQA,CAC5BC,OAAgB,EAChBC,MAAc,EACC;EACf,OAAO,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;IACtC;IACA,MAAMC,eAAe,GAAG,IAAAC,kBAAkB,EAACL,MAAM,CAAC,CAACM,IAAI,CAAC,CAAC;IACzD,IAAI,CAACF,eAAe,CAACG,MAAM,EAAEL,OAAO,CAAC,CAAC;IACtC;IACA,IAAIM,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAMC,IAAI,GAAIC,KAAe,IAAW;MACtC,IAAIA,KAAK,EAAEP,MAAM,CAACO,KAAK,CAAC;MACxBF,QAAQ,EAAE;MACV,IAAIA,QAAQ,KAAKJ,eAAe,CAACG,MAAM,EAAE;QACvCL,OAAO,CAAC,CAAC;QACT;MACF;MACAE,eAAe,CAACI,QAAQ,CAAC,CAACT,OAAO,EAAEY,SAAS,EAAEF,IAAI,CAAC;IACrD,CAAC;IACDA,IAAI,CAAC,CAAC;EACR,CAAC,CAAC;AACJ","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-server-engine",
3
- "version": "1.2.9",
3
+ "version": "1.3.0",
4
4
  "description": "Framework used to develop backend services. This package ships with a lot of features to standardize the creation of services, letting you focus on the business logic.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -48,6 +48,7 @@
48
48
  "backend-test-tools": "^1.0.7",
49
49
  "body-parser": "^1.20.3",
50
50
  "busboy": "^1.6.0",
51
+ "bytes": "^3.1.2",
51
52
  "callsite": "^1.0.0",
52
53
  "cors": "^2.8.5",
53
54
  "device": "^0.2.0",
@@ -104,6 +105,7 @@
104
105
  "@faker-js/faker": "^9.3.0",
105
106
  "@google-cloud/precise-date": "^4.0.0",
106
107
  "@types/busboy": "^1.5.4",
108
+ "@types/bytes": "^3.1.5",
107
109
  "@types/callsite": "^1.0.34",
108
110
  "@types/chai": "^5.0.1",
109
111
  "@types/chai-as-promised": "^8.0.1",
@@ -24,8 +24,14 @@ import {
24
24
  EndpointHandler
25
25
  } from './Endpoint.types';
26
26
  import { LogSeverity } from 'const';
27
- import { validate, fileUploader, requestMetrics } from 'middleware';
27
+ import {
28
+ validate,
29
+ fileUploader,
30
+ requestMetrics,
31
+ multiPartFileUploader
32
+ } from 'middleware';
28
33
  import { FileUploaderConfig } from 'middleware/fileUploader';
34
+ import { MultiPartFileUploaderConfig } from 'middleware/multiPartFileUploader';
29
35
  import { reportDebug, reportError, reportInfo } from 'utils/report';
30
36
 
31
37
  const namespace = 'engine:Endpoint';
@@ -48,6 +54,8 @@ export class Endpoint<T extends EndpointAuthType> {
48
54
  private authParams?: EndpointAuthParams<T>;
49
55
  /** Settings to upload files through this endpoint (this will automatically switch the expected request to be a multipart) */
50
56
  private files?: Array<FileUploaderConfig>;
57
+ /** Settings to upload file parts (Chunks) through this endpoint (this will automatically switch the expected request to be a multipart) */
58
+ private multiPartFile?: MultiPartFileUploaderConfig;
51
59
  /** Express request handlers that should run on this endpoint before the business logic, they will run after the global middleware */
52
60
  private middleware?: Array<Middleware<T>>;
53
61
  /** Express error handlers that should be registered for this endpoint. they will run before the global error handler */
@@ -63,7 +71,9 @@ export class Endpoint<T extends EndpointAuthType> {
63
71
  path: config.path,
64
72
  authType: config.authType,
65
73
  authParams: config.authParams,
66
- file: config.file
74
+ file: config.file,
75
+ files: config.files,
76
+ multiPartFile: config.multiPartFile
67
77
  }
68
78
  });
69
79
 
@@ -113,10 +123,19 @@ export class Endpoint<T extends EndpointAuthType> {
113
123
  // Register file upload
114
124
  // Validation is included in the file upload process as this uses multipart/form-data
115
125
  middlewareChain.push(fileUploader(this.files, this.validator));
116
- } else {
126
+ }
127
+
128
+ if (this.multiPartFile) {
129
+ middlewareChain.push(
130
+ multiPartFileUploader(this.multiPartFile, this.validator)
131
+ );
132
+ }
133
+
134
+ if (!this.files && !this.multiPartFile) {
117
135
  // Register validator for regular cases
118
136
  middlewareChain.push(validate(this.validator));
119
137
  }
138
+
120
139
  // Register additional middleware
121
140
  if (this.middleware) {
122
141
  middlewareChain.push(...this.middleware);
@@ -17,6 +17,7 @@ import {
17
17
  authStatic,
18
18
  FileUploaderConfig
19
19
  } from 'middleware';
20
+ import { MultiPartFileUploaderConfig } from 'middleware/multiPartFileUploader/multiPartFileUploader.types';
20
21
 
21
22
  export enum EndpointMethod {
22
23
  GET = 'get',
@@ -129,6 +130,8 @@ export interface EndpointOptions<T extends EndpointAuthType> {
129
130
  file?: FileUploaderConfig;
130
131
  /** Settings to upload files through this endpoint (this will automatically switch the expected request to be a multipart) */
131
132
  files?: Array<FileUploaderConfig>;
133
+ /** Settings to upload file parts (Chunks) through this endpoint (this will automatically switch the expected request to be a multipart) */
134
+ multiPartFile?: MultiPartFileUploaderConfig;
132
135
  /** Express request handlers that should run on this endpoint before the business logic, they will run after the global middleware */
133
136
  middleware?: MiddlewareChainElement<T>;
134
137
  /** Express error handlers that should be registered for this endpoint. they will run before the global error handler */
@@ -5,6 +5,8 @@ import { reportDebug } from 'utils/report';
5
5
  import multer from 'multer';
6
6
  import { FileUploaderConfig } from './fileUploader.types';
7
7
  import { validate } from './validate';
8
+ import path from 'path';
9
+ import bytes from 'bytes';
8
10
 
9
11
  const namespace = 'engine:middleware:fileUploader';
10
12
 
@@ -32,7 +34,7 @@ export function fileUploader(
32
34
  const storage = multer.memoryStorage();
33
35
  const upload = multer({
34
36
  storage,
35
- limits: { fileSize: 10 * 1024 * 1024 } // Limit file size to 10MB
37
+ limits: { fileSize: 32 * 1024 * 1024 } // Default global limit (10MB)
36
38
  }).any();
37
39
 
38
40
  upload(request, response, async (err) => {
@@ -97,7 +99,54 @@ export function fileUploader(
97
99
  );
98
100
  }
99
101
 
100
- request.body.files = uploadedFiles;
102
+ // Validate each uploaded file
103
+ for (const file of uploadedFiles) {
104
+ const fileOption = filesOptions.find(
105
+ (option) => option.key === file.fieldname
106
+ );
107
+
108
+ if (!fileOption) continue;
109
+
110
+ // MIME Type Validation
111
+ if (
112
+ fileOption.mimeTypes &&
113
+ !fileOption.mimeTypes.includes(file.mimetype)
114
+ ) {
115
+ return next(
116
+ new WebError({
117
+ statusCode: 400,
118
+ errorCode: 'invalid-mime-type',
119
+ message: `Invalid file type for ${file.fieldname}`,
120
+ data: { expected: fileOption.mimeTypes, received: file.mimetype }
121
+ })
122
+ );
123
+ }
124
+
125
+ // Max File Size Validation
126
+ if (fileOption.maxSize) {
127
+ const maxSizeInBytes = bytes(fileOption.maxSize);
128
+ if (maxSizeInBytes && file.size > maxSizeInBytes) {
129
+ return next(
130
+ new WebError({
131
+ statusCode: 400,
132
+ errorCode: 'file-too-large',
133
+ message: `File ${file.originalname} exceeds the allowed size`,
134
+ data: { maxSize: fileOption.maxSize, receivedSize: file.size }
135
+ })
136
+ );
137
+ }
138
+ }
139
+
140
+ // No Extension Handling
141
+ if (fileOption.noExtension) {
142
+ file.originalname = path.basename(
143
+ file.originalname,
144
+ path.extname(file.originalname)
145
+ );
146
+ }
147
+ }
148
+
149
+ request.files = uploadedFiles;
101
150
 
102
151
  next();
103
152
  });
@@ -1 +1,2 @@
1
1
  export * from './multiPartFileUploader';
2
+ export * from './multiPartFileUploader.types';