@flusys/nestjs-storage 7.1.0 → 8.0.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 (202) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/file-manager.controller.d.ts +4 -4
  3. package/controllers/folder.controller.d.ts +2 -2
  4. package/controllers/index.d.ts +4 -4
  5. package/controllers/storage-config.controller.d.ts +2 -2
  6. package/controllers/upload.controller.d.ts +3 -3
  7. package/docs/index.d.ts +1 -1
  8. package/docs/storage-swagger.config.d.ts +2 -2
  9. package/dtos/index.d.ts +4 -4
  10. package/entities/file-manager-with-company.entity.d.ts +2 -2
  11. package/entities/file-manager.entity.d.ts +1 -1
  12. package/entities/folder-with-company.entity.d.ts +1 -1
  13. package/entities/index.d.ts +15 -15
  14. package/entities/storage-config-with-company.entity.d.ts +1 -1
  15. package/enums/index.d.ts +1 -1
  16. package/fesm/493.js +2768 -0
  17. package/fesm/537.js +1157 -0
  18. package/fesm/562.js +786 -0
  19. package/fesm/{providers/azure-provider.optional.js → chunks/190.js} +33 -30
  20. package/fesm/chunks/252.js +12 -0
  21. package/fesm/chunks/327.js +12 -0
  22. package/fesm/chunks/385.js +12 -0
  23. package/fesm/{providers/s3-provider.optional.js → chunks/591.js} +37 -34
  24. package/fesm/chunks/746.js +12 -0
  25. package/fesm/chunks/798.js +12 -0
  26. package/fesm/{providers/sftp-provider.optional.js → chunks/918.js} +35 -31
  27. package/fesm/config/index.js +119 -3
  28. package/fesm/controllers/index.js +16 -4
  29. package/fesm/docs/index.js +206 -1
  30. package/fesm/dtos/index.js +879 -4
  31. package/fesm/entities/index.js +463 -18
  32. package/fesm/enums/index.js +36 -1
  33. package/fesm/index.js +398 -12
  34. package/fesm/interfaces/index.js +19 -5
  35. package/fesm/modules/index.js +16 -1
  36. package/fesm/providers/index.js +364 -3
  37. package/fesm/runtime.js +252 -0
  38. package/fesm/services/index.js +16 -6
  39. package/fesm/utils/index.js +149 -1
  40. package/index.d.ts +11 -11
  41. package/interfaces/file-manager.interface.d.ts +1 -1
  42. package/interfaces/folder.interface.d.ts +1 -1
  43. package/interfaces/index.d.ts +5 -5
  44. package/interfaces/storage-config.interface.d.ts +1 -1
  45. package/interfaces/storage-module-options.interface.d.ts +3 -3
  46. package/interfaces/storage-provider.interface.d.ts +1 -1
  47. package/middlewares/file-serve.middleware.d.ts +4 -3
  48. package/middlewares/index.d.ts +1 -1
  49. package/modules/index.d.ts +1 -1
  50. package/modules/storage.module.d.ts +2 -2
  51. package/package.json +30 -41
  52. package/providers/azure-provider.optional.d.ts +2 -2
  53. package/providers/index.d.ts +3 -3
  54. package/providers/local-provider.d.ts +2 -2
  55. package/providers/s3-provider.optional.d.ts +2 -2
  56. package/providers/sftp-provider.optional.d.ts +2 -2
  57. package/providers/storage-factory.service.d.ts +3 -3
  58. package/providers/storage-provider.registry.d.ts +2 -2
  59. package/services/file-manager.service.d.ts +9 -8
  60. package/services/folder.service.d.ts +6 -6
  61. package/services/index.d.ts +6 -6
  62. package/services/storage-config.service.d.ts +3 -3
  63. package/services/storage-datasource.provider.d.ts +3 -3
  64. package/services/storage-provider-config.service.d.ts +7 -7
  65. package/services/upload.service.d.ts +6 -6
  66. package/utils/image-compressor.util.d.ts +1 -1
  67. package/utils/index.d.ts +1 -1
  68. package/cjs/config/event-actions.js +0 -34
  69. package/cjs/config/index.js +0 -20
  70. package/cjs/config/message-keys.js +0 -46
  71. package/cjs/config/storage.constants.js +0 -28
  72. package/cjs/controllers/file-manager.controller.js +0 -152
  73. package/cjs/controllers/file-manager.controller.spec.js +0 -135
  74. package/cjs/controllers/folder.controller.js +0 -102
  75. package/cjs/controllers/folder.controller.spec.js +0 -86
  76. package/cjs/controllers/index.js +0 -21
  77. package/cjs/controllers/storage-config.controller.js +0 -102
  78. package/cjs/controllers/storage-config.controller.spec.js +0 -85
  79. package/cjs/controllers/upload.controller.js +0 -234
  80. package/cjs/controllers/upload.controller.spec.js +0 -132
  81. package/cjs/docs/index.js +0 -11
  82. package/cjs/docs/storage-swagger.config.js +0 -188
  83. package/cjs/docs/storage-swagger.config.spec.js +0 -72
  84. package/cjs/dtos/file-manager.dto.js +0 -327
  85. package/cjs/dtos/folder.dto.js +0 -105
  86. package/cjs/dtos/index.js +0 -21
  87. package/cjs/dtos/storage-config.dto.js +0 -170
  88. package/cjs/dtos/upload.dto.js +0 -272
  89. package/cjs/entities/file-manager-with-company.entity.js +0 -61
  90. package/cjs/entities/file-manager.entity.js +0 -122
  91. package/cjs/entities/folder-with-company.entity.js +0 -55
  92. package/cjs/entities/folder.entity.js +0 -64
  93. package/cjs/entities/index.js +0 -63
  94. package/cjs/entities/index.spec.js +0 -18
  95. package/cjs/entities/storage-config-with-company.entity.js +0 -51
  96. package/cjs/entities/storage-config.entity.js +0 -96
  97. package/cjs/enums/file-location.enum.js +0 -17
  98. package/cjs/enums/index.js +0 -18
  99. package/cjs/index.js +0 -29
  100. package/cjs/interfaces/file-manager.interface.js +0 -4
  101. package/cjs/interfaces/folder.interface.js +0 -4
  102. package/cjs/interfaces/index.js +0 -22
  103. package/cjs/interfaces/storage-config.interface.js +0 -4
  104. package/cjs/interfaces/storage-module-options.interface.js +0 -4
  105. package/cjs/interfaces/storage-provider.interface.js +0 -4
  106. package/cjs/middlewares/file-serve.middleware.js +0 -241
  107. package/cjs/middlewares/file-serve.middleware.spec.js +0 -211
  108. package/cjs/middlewares/index.js +0 -18
  109. package/cjs/modules/index.js +0 -18
  110. package/cjs/modules/storage.module.js +0 -165
  111. package/cjs/modules/storage.module.spec.js +0 -184
  112. package/cjs/providers/azure-provider.optional.js +0 -155
  113. package/cjs/providers/azure-provider.optional.spec.js +0 -228
  114. package/cjs/providers/index.js +0 -26
  115. package/cjs/providers/local-provider.js +0 -230
  116. package/cjs/providers/local-provider.spec.js +0 -239
  117. package/cjs/providers/s3-provider.optional.js +0 -175
  118. package/cjs/providers/s3-provider.optional.spec.js +0 -243
  119. package/cjs/providers/sftp-provider.optional.js +0 -203
  120. package/cjs/providers/sftp-provider.optional.spec.js +0 -217
  121. package/cjs/providers/storage-factory.service.js +0 -175
  122. package/cjs/providers/storage-factory.service.spec.js +0 -253
  123. package/cjs/providers/storage-provider.registry.js +0 -41
  124. package/cjs/providers/storage-provider.registry.spec.js +0 -67
  125. package/cjs/services/file-manager.service.js +0 -387
  126. package/cjs/services/file-manager.service.spec.js +0 -580
  127. package/cjs/services/folder.service.js +0 -101
  128. package/cjs/services/folder.service.spec.js +0 -167
  129. package/cjs/services/index.js +0 -23
  130. package/cjs/services/storage-config.service.js +0 -129
  131. package/cjs/services/storage-config.service.spec.js +0 -257
  132. package/cjs/services/storage-datasource.provider.js +0 -137
  133. package/cjs/services/storage-datasource.provider.spec.js +0 -217
  134. package/cjs/services/storage-provider-config.service.js +0 -171
  135. package/cjs/services/storage-provider-config.service.spec.js +0 -265
  136. package/cjs/services/upload.service.js +0 -376
  137. package/cjs/services/upload.service.spec.js +0 -426
  138. package/cjs/utils/file-validator.util.js +0 -440
  139. package/cjs/utils/file-validator.util.spec.js +0 -259
  140. package/cjs/utils/image-compressor.util.js +0 -162
  141. package/cjs/utils/image-compressor.util.spec.js +0 -166
  142. package/cjs/utils/index.js +0 -18
  143. package/fesm/config/event-actions.js +0 -13
  144. package/fesm/config/message-keys.js +0 -22
  145. package/fesm/config/storage.constants.js +0 -7
  146. package/fesm/controllers/file-manager.controller.js +0 -142
  147. package/fesm/controllers/file-manager.controller.spec.js +0 -131
  148. package/fesm/controllers/folder.controller.js +0 -92
  149. package/fesm/controllers/folder.controller.spec.js +0 -82
  150. package/fesm/controllers/storage-config.controller.js +0 -92
  151. package/fesm/controllers/storage-config.controller.spec.js +0 -81
  152. package/fesm/controllers/upload.controller.js +0 -224
  153. package/fesm/controllers/upload.controller.spec.js +0 -128
  154. package/fesm/docs/storage-swagger.config.js +0 -181
  155. package/fesm/docs/storage-swagger.config.spec.js +0 -68
  156. package/fesm/dtos/file-manager.dto.js +0 -300
  157. package/fesm/dtos/folder.dto.js +0 -84
  158. package/fesm/dtos/storage-config.dto.js +0 -149
  159. package/fesm/dtos/upload.dto.js +0 -245
  160. package/fesm/entities/file-manager-with-company.entity.js +0 -51
  161. package/fesm/entities/file-manager.entity.js +0 -112
  162. package/fesm/entities/folder-with-company.entity.js +0 -45
  163. package/fesm/entities/folder.entity.js +0 -54
  164. package/fesm/entities/index.spec.js +0 -14
  165. package/fesm/entities/storage-config-with-company.entity.js +0 -41
  166. package/fesm/entities/storage-config.entity.js +0 -86
  167. package/fesm/enums/file-location.enum.js +0 -7
  168. package/fesm/interfaces/file-manager.interface.js +0 -1
  169. package/fesm/interfaces/folder.interface.js +0 -1
  170. package/fesm/interfaces/storage-config.interface.js +0 -1
  171. package/fesm/interfaces/storage-module-options.interface.js +0 -3
  172. package/fesm/interfaces/storage-provider.interface.js +0 -4
  173. package/fesm/middlewares/file-serve.middleware.js +0 -190
  174. package/fesm/middlewares/file-serve.middleware.spec.js +0 -207
  175. package/fesm/middlewares/index.js +0 -1
  176. package/fesm/modules/storage.module.js +0 -156
  177. package/fesm/modules/storage.module.spec.js +0 -180
  178. package/fesm/providers/azure-provider.optional.spec.js +0 -224
  179. package/fesm/providers/local-provider.js +0 -182
  180. package/fesm/providers/local-provider.spec.js +0 -194
  181. package/fesm/providers/s3-provider.optional.spec.js +0 -239
  182. package/fesm/providers/sftp-provider.optional.spec.js +0 -213
  183. package/fesm/providers/storage-factory.service.js +0 -124
  184. package/fesm/providers/storage-factory.service.spec.js +0 -249
  185. package/fesm/providers/storage-provider.registry.js +0 -34
  186. package/fesm/providers/storage-provider.registry.spec.js +0 -63
  187. package/fesm/services/file-manager.service.js +0 -336
  188. package/fesm/services/file-manager.service.spec.js +0 -576
  189. package/fesm/services/folder.service.js +0 -91
  190. package/fesm/services/folder.service.spec.js +0 -163
  191. package/fesm/services/storage-config.service.js +0 -119
  192. package/fesm/services/storage-config.service.spec.js +0 -253
  193. package/fesm/services/storage-datasource.provider.js +0 -86
  194. package/fesm/services/storage-datasource.provider.spec.js +0 -172
  195. package/fesm/services/storage-provider-config.service.js +0 -161
  196. package/fesm/services/storage-provider-config.service.spec.js +0 -261
  197. package/fesm/services/upload.service.js +0 -366
  198. package/fesm/services/upload.service.spec.js +0 -422
  199. package/fesm/utils/file-validator.util.js +0 -433
  200. package/fesm/utils/file-validator.util.spec.js +0 -255
  201. package/fesm/utils/image-compressor.util.js +0 -114
  202. package/fesm/utils/image-compressor.util.spec.js +0 -162
@@ -1,230 +0,0 @@
1
- /**
2
- * LOCAL File System Provider
3
- *
4
- * This provider uses Node.js built-in 'fs' module - NO external dependencies required!
5
- * Perfect for same-server storage scenarios.
6
- *
7
- * Usage:
8
- * import { LocalProvider } from '@flusys/nestjs-storage/providers/local-provider';
9
- * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
10
- *
11
- * StorageProviderRegistry.register(FileLocationEnum.LOCAL, LocalProvider);
12
- */ "use strict";
13
- Object.defineProperty(exports, "__esModule", {
14
- value: true
15
- });
16
- Object.defineProperty(exports, "LocalProvider", {
17
- enumerable: true,
18
- get: function() {
19
- return LocalProvider;
20
- }
21
- });
22
- const _common = require("@nestjs/common");
23
- const _constants = require("@flusys/nestjs-shared/constants");
24
- const _imagecompressorutil = require("../utils/image-compressor.util");
25
- const _promises = /*#__PURE__*/ _interop_require_wildcard(require("fs/promises"));
26
- const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
27
- const _uuid = require("uuid");
28
- function _define_property(obj, key, value) {
29
- if (key in obj) {
30
- Object.defineProperty(obj, key, {
31
- value: value,
32
- enumerable: true,
33
- configurable: true,
34
- writable: true
35
- });
36
- } else {
37
- obj[key] = value;
38
- }
39
- return obj;
40
- }
41
- function _getRequireWildcardCache(nodeInterop) {
42
- if (typeof WeakMap !== "function") return null;
43
- var cacheBabelInterop = new WeakMap();
44
- var cacheNodeInterop = new WeakMap();
45
- return (_getRequireWildcardCache = function(nodeInterop) {
46
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
47
- })(nodeInterop);
48
- }
49
- function _interop_require_wildcard(obj, nodeInterop) {
50
- if (!nodeInterop && obj && obj.__esModule) {
51
- return obj;
52
- }
53
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
54
- return {
55
- default: obj
56
- };
57
- }
58
- var cache = _getRequireWildcardCache(nodeInterop);
59
- if (cache && cache.has(obj)) {
60
- return cache.get(obj);
61
- }
62
- var newObj = {
63
- __proto__: null
64
- };
65
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
66
- for(var key in obj){
67
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
68
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
69
- if (desc && (desc.get || desc.set)) {
70
- Object.defineProperty(newObj, key, desc);
71
- } else {
72
- newObj[key] = obj[key];
73
- }
74
- }
75
- }
76
- newObj.default = obj;
77
- if (cache) {
78
- cache.set(obj, newObj);
79
- }
80
- return newObj;
81
- }
82
- let LocalProvider = class LocalProvider {
83
- /**
84
- * SECURITY: Validates that a target path does not escape the base directory
85
- * Prevents path traversal attacks using ../ sequences
86
- * @throws Error if path traversal is detected
87
- */ validatePathWithinBase(targetPath) {
88
- const normalizedBasePath = _path.resolve(this.basePath);
89
- const normalizedTargetPath = _path.resolve(targetPath);
90
- if (!normalizedTargetPath.startsWith(normalizedBasePath + _path.sep) && normalizedTargetPath !== normalizedBasePath) {
91
- throw new _common.BadRequestException({
92
- message: 'Invalid path: Path traversal attempt detected',
93
- messageKey: _constants.SYSTEM_MESSAGES.PATH_TRAVERSAL_DETECTED
94
- });
95
- }
96
- }
97
- /**
98
- * SECURITY: Validates file key format to prevent malicious input
99
- * @throws Error if key contains suspicious patterns
100
- */ validateKeyFormat(key) {
101
- if (!key || typeof key !== 'string' || key.trim().length === 0) {
102
- throw new _common.BadRequestException({
103
- message: 'Invalid file key: empty or invalid',
104
- messageKey: _constants.SYSTEM_MESSAGES.INVALID_FILE_KEY
105
- });
106
- }
107
- if (key.includes('\0')) {
108
- throw new _common.BadRequestException({
109
- message: 'Invalid file key: contains null bytes',
110
- messageKey: _constants.SYSTEM_MESSAGES.INVALID_FILE_KEY
111
- });
112
- }
113
- }
114
- /**
115
- * Initialize Local File System provider with configuration
116
- * @param config.basePath - Base path for file storage (default: './uploads')
117
- * @param config.baseUrl - Optional base URL for generating file URLs
118
- */ async initialize(config) {
119
- // Store original relative path for key generation
120
- this.relativeBasePath = config?.basePath || './uploads';
121
- // Resolve to absolute path for file operations
122
- this.basePath = _path.resolve(this.relativeBasePath);
123
- this.baseUrl = config?.baseUrl || '';
124
- // Ensure base directory exists
125
- await _promises.mkdir(this.basePath, {
126
- recursive: true
127
- });
128
- }
129
- async uploadFile(file, options) {
130
- let processedBuffer = file.buffer;
131
- let contentType = file.mimetype;
132
- const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
133
- // Compress image if needed
134
- if (options.compress && file.mimetype.startsWith('image/')) {
135
- const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
136
- maxWidth: options.maxWidth,
137
- maxHeight: options.maxHeight,
138
- quality: options.quality,
139
- format: options.format
140
- });
141
- processedBuffer = result.buffer;
142
- contentType = result.format;
143
- }
144
- // Build file path
145
- const folderPath = options.folderPath ? _path.join(this.basePath, options.folderPath) : this.basePath;
146
- // SECURITY: Validate path does not escape base directory
147
- this.validatePathWithinBase(folderPath);
148
- const filePath = _path.join(folderPath, fileName);
149
- // SECURITY: Double-check final file path
150
- this.validatePathWithinBase(filePath);
151
- // Ensure directory exists
152
- await _promises.mkdir(folderPath, {
153
- recursive: true
154
- });
155
- // Write file to disk
156
- await _promises.writeFile(filePath, processedBuffer);
157
- // Generate key that includes the base path (relative to cwd)
158
- // This makes the key self-contained for serving files
159
- const relativeKey = _path.join(this.relativeBasePath, options.folderPath || '', fileName).replace(/\\/g, '/'); // Normalize path separators
160
- return {
161
- name: fileName,
162
- key: relativeKey,
163
- size: processedBuffer.length,
164
- contentType
165
- };
166
- }
167
- async uploadMultipleFiles(files, options) {
168
- return Promise.all(files.map((file)=>this.uploadFile(file, options)));
169
- }
170
- async deleteFile(key) {
171
- try {
172
- // SECURITY: Validate key format first
173
- this.validateKeyFormat(key);
174
- // Key now includes the basePath, resolve from cwd
175
- let filePath = _path.resolve(key);
176
- // SECURITY: Validate resolved path is within base directory
177
- this.validatePathWithinBase(filePath);
178
- // Check if file exists at the resolved path
179
- try {
180
- await _promises.access(filePath);
181
- } catch {
182
- // Fallback: try with basePath prefix (for old keys without basePath)
183
- const fallbackPath = _path.join(this.basePath, key);
184
- // SECURITY: Validate fallback path as well
185
- this.validatePathWithinBase(fallbackPath);
186
- try {
187
- await _promises.access(fallbackPath);
188
- filePath = fallbackPath;
189
- } catch {
190
- // File doesn't exist at either location - silent return
191
- return;
192
- }
193
- }
194
- await _promises.unlink(filePath);
195
- } catch (error) {
196
- // Re-throw security errors
197
- if (error instanceof Error && error.message.includes('Invalid')) {
198
- throw error;
199
- }
200
- // Silent failure for other errors
201
- }
202
- }
203
- async deleteMultipleFiles(keys) {
204
- await Promise.all(keys.map((key)=>this.deleteFile(key)));
205
- }
206
- async generatePresignedUrl(key, _expiresInSeconds) {
207
- // Return public URL or relative path
208
- // Note: Local storage doesn't support true presigned URLs
209
- // If baseUrl is provided, return full URL with file serving endpoint, otherwise return relative path
210
- if (this.baseUrl) {
211
- // Construct URL with the file serving endpoint
212
- const baseUrlWithoutTrailingSlash = this.baseUrl.replace(/\/$/, '');
213
- return `${baseUrlWithoutTrailingSlash}/storage/upload/file/${key}`;
214
- }
215
- return `/storage/upload/file/${key}`;
216
- }
217
- async healthCheck() {
218
- try {
219
- await _promises.access(this.basePath);
220
- return true;
221
- } catch {
222
- return false;
223
- }
224
- }
225
- constructor(){
226
- _define_property(this, "basePath", '');
227
- _define_property(this, "baseUrl", '');
228
- _define_property(this, "relativeBasePath", ''); // Path relative to cwd for key generation
229
- }
230
- };
@@ -1,239 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- const _path = /*#__PURE__*/ _interop_require_wildcard(require("path"));
6
- const _localprovider = require("./local-provider");
7
- const _imagecompressorutil = require("../utils/image-compressor.util");
8
- function _getRequireWildcardCache(nodeInterop) {
9
- if (typeof WeakMap !== "function") return null;
10
- var cacheBabelInterop = new WeakMap();
11
- var cacheNodeInterop = new WeakMap();
12
- return (_getRequireWildcardCache = function(nodeInterop) {
13
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
14
- })(nodeInterop);
15
- }
16
- function _interop_require_wildcard(obj, nodeInterop) {
17
- if (!nodeInterop && obj && obj.__esModule) {
18
- return obj;
19
- }
20
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
21
- return {
22
- default: obj
23
- };
24
- }
25
- var cache = _getRequireWildcardCache(nodeInterop);
26
- if (cache && cache.has(obj)) {
27
- return cache.get(obj);
28
- }
29
- var newObj = {
30
- __proto__: null
31
- };
32
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
33
- for(var key in obj){
34
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
35
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
36
- if (desc && (desc.get || desc.set)) {
37
- Object.defineProperty(newObj, key, desc);
38
- } else {
39
- newObj[key] = obj[key];
40
- }
41
- }
42
- }
43
- newObj.default = obj;
44
- if (cache) {
45
- cache.set(obj, newObj);
46
- }
47
- return newObj;
48
- }
49
- const mockMkdir = jest.fn();
50
- const mockWriteFile = jest.fn();
51
- const mockAccess = jest.fn();
52
- const mockUnlink = jest.fn();
53
- jest.mock('fs/promises', ()=>({
54
- mkdir: (...args)=>mockMkdir(...args),
55
- writeFile: (...args)=>mockWriteFile(...args),
56
- access: (...args)=>mockAccess(...args),
57
- unlink: (...args)=>mockUnlink(...args)
58
- }));
59
- jest.mock('../utils/image-compressor.util', ()=>({
60
- ImageCompressor: {
61
- compress: jest.fn()
62
- }
63
- }));
64
- function buildMulterFile(overrides = {}) {
65
- return {
66
- fieldname: 'file',
67
- originalname: 'photo.jpg',
68
- encoding: '7bit',
69
- mimetype: 'image/jpeg',
70
- buffer: Buffer.from('file-bytes'),
71
- size: 10,
72
- stream: undefined,
73
- destination: '',
74
- filename: '',
75
- path: '',
76
- ...overrides
77
- };
78
- }
79
- describe('LocalProvider', ()=>{
80
- let provider;
81
- beforeEach(async ()=>{
82
- jest.clearAllMocks();
83
- mockMkdir.mockResolvedValue(undefined);
84
- mockWriteFile.mockResolvedValue(undefined);
85
- mockAccess.mockResolvedValue(undefined);
86
- mockUnlink.mockResolvedValue(undefined);
87
- provider = new _localprovider.LocalProvider();
88
- await provider.initialize({
89
- basePath: './uploads-test',
90
- baseUrl: 'http://localhost:2002'
91
- });
92
- });
93
- describe('initialize', ()=>{
94
- it('resolves the base path and ensures the directory exists', async ()=>{
95
- expect(mockMkdir).toHaveBeenCalledWith(_path.resolve('./uploads-test'), expect.objectContaining({
96
- recursive: true
97
- }));
98
- });
99
- it('defaults basePath to ./uploads when not provided', async ()=>{
100
- mockMkdir.mockClear();
101
- const defaultProvider = new _localprovider.LocalProvider();
102
- await defaultProvider.initialize({});
103
- expect(mockMkdir).toHaveBeenCalledWith(_path.resolve('./uploads'), expect.objectContaining({
104
- recursive: true
105
- }));
106
- });
107
- });
108
- describe('uploadFile', ()=>{
109
- it('writes the file to disk and returns metadata with a self-contained key', async ()=>{
110
- const file = buildMulterFile();
111
- const result = await provider.uploadFile(file, {});
112
- expect(mockWriteFile).toHaveBeenCalled();
113
- expect(result.name).toMatch(/photo\.jpg$/);
114
- expect(result.key).toContain('uploads-test');
115
- expect(result.size).toBe(file.buffer.length);
116
- expect(result.contentType).toBe('image/jpeg');
117
- });
118
- it('nests the file under folderPath when provided', async ()=>{
119
- const file = buildMulterFile();
120
- const result = await provider.uploadFile(file, {
121
- folderPath: 'avatars'
122
- });
123
- expect(result.key).toContain('avatars');
124
- expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('avatars'), expect.objectContaining({
125
- recursive: true
126
- }));
127
- });
128
- it('compresses image files when options.compress is true', async ()=>{
129
- _imagecompressorutil.ImageCompressor.compress.mockResolvedValue({
130
- buffer: Buffer.from('compressed'),
131
- format: 'image/webp'
132
- });
133
- const file = buildMulterFile();
134
- const result = await provider.uploadFile(file, {
135
- compress: true,
136
- maxWidth: 800,
137
- maxHeight: 600,
138
- quality: 70
139
- });
140
- expect(_imagecompressorutil.ImageCompressor.compress).toHaveBeenCalledWith(file.buffer, 'image/jpeg', expect.objectContaining({
141
- maxWidth: 800,
142
- maxHeight: 600,
143
- quality: 70
144
- }));
145
- expect(result.contentType).toBe('image/webp');
146
- expect(result.size).toBe(Buffer.from('compressed').length);
147
- });
148
- it('does not compress non-image mimetypes even when compress=true', async ()=>{
149
- const file = buildMulterFile({
150
- mimetype: 'application/pdf',
151
- originalname: 'doc.pdf'
152
- });
153
- await provider.uploadFile(file, {
154
- compress: true
155
- });
156
- expect(_imagecompressorutil.ImageCompressor.compress).not.toHaveBeenCalled();
157
- });
158
- it('rejects a folderPath that attempts to escape the base directory via path traversal', async ()=>{
159
- const file = buildMulterFile();
160
- await expect(provider.uploadFile(file, {
161
- folderPath: '../../../etc'
162
- })).rejects.toThrow();
163
- });
164
- });
165
- describe('uploadMultipleFiles', ()=>{
166
- it('uploads all files and returns their metadata', async ()=>{
167
- const files = [
168
- buildMulterFile({
169
- originalname: 'a.jpg'
170
- }),
171
- buildMulterFile({
172
- originalname: 'b.jpg'
173
- })
174
- ];
175
- const results = await provider.uploadMultipleFiles(files, {});
176
- expect(results).toHaveLength(2);
177
- expect(mockWriteFile).toHaveBeenCalledTimes(2);
178
- });
179
- });
180
- describe('deleteFile', ()=>{
181
- it('deletes a file that exists at the resolved key path', async ()=>{
182
- const key = _path.join('uploads-test', 'some-uuid-photo.jpg');
183
- await provider.deleteFile(key);
184
- expect(mockUnlink).toHaveBeenCalled();
185
- });
186
- it('falls back to basePath-prefixed lookup when the direct key path does not exist', async ()=>{
187
- mockAccess.mockRejectedValueOnce(new Error('ENOENT')).mockResolvedValueOnce(undefined);
188
- await provider.deleteFile('uploads-test/some-uuid-photo.jpg');
189
- expect(mockUnlink).toHaveBeenCalled();
190
- });
191
- it('silently returns (no throw, no unlink) when the file does not exist at either location', async ()=>{
192
- mockAccess.mockRejectedValue(new Error('ENOENT'));
193
- await expect(provider.deleteFile('uploads-test/missing-file.jpg')).resolves.toBeUndefined();
194
- expect(mockUnlink).not.toHaveBeenCalled();
195
- });
196
- it('rejects an empty key', async ()=>{
197
- await expect(provider.deleteFile('')).rejects.toThrow();
198
- });
199
- it('rejects a key containing a null byte', async ()=>{
200
- await expect(provider.deleteFile('evil\0.txt')).rejects.toThrow();
201
- });
202
- it('re-throws path traversal security errors rather than silently swallowing them', async ()=>{
203
- await expect(provider.deleteFile('../../../etc/passwd')).rejects.toThrow(/Invalid/);
204
- });
205
- });
206
- describe('deleteMultipleFiles', ()=>{
207
- it('deletes every provided key', async ()=>{
208
- await provider.deleteMultipleFiles([
209
- 'uploads-test/a.jpg',
210
- 'uploads-test/b.jpg'
211
- ]);
212
- expect(mockUnlink).toHaveBeenCalledTimes(2);
213
- });
214
- });
215
- describe('generatePresignedUrl', ()=>{
216
- it('builds a full URL using the configured baseUrl', async ()=>{
217
- const url = await provider.generatePresignedUrl('uploads-test/photo.jpg');
218
- expect(url).toBe('http://localhost:2002/storage/upload/file/uploads-test/photo.jpg');
219
- });
220
- it('returns a relative path when no baseUrl was configured', async ()=>{
221
- const noUrlProvider = new _localprovider.LocalProvider();
222
- await noUrlProvider.initialize({
223
- basePath: './uploads-test2'
224
- });
225
- const url = await noUrlProvider.generatePresignedUrl('uploads-test2/photo.jpg');
226
- expect(url).toBe('/storage/upload/file/uploads-test2/photo.jpg');
227
- });
228
- });
229
- describe('healthCheck', ()=>{
230
- it('returns true when the base path is accessible', async ()=>{
231
- mockAccess.mockResolvedValue(undefined);
232
- await expect(provider.healthCheck()).resolves.toBe(true);
233
- });
234
- it('returns false when the base path is not accessible', async ()=>{
235
- mockAccess.mockRejectedValue(new Error('ENOENT'));
236
- await expect(provider.healthCheck()).resolves.toBe(false);
237
- });
238
- });
239
- });
@@ -1,175 +0,0 @@
1
- /**
2
- * Optional AWS S3 Storage Provider
3
- * Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
4
- */ "use strict";
5
- Object.defineProperty(exports, "__esModule", {
6
- value: true
7
- });
8
- Object.defineProperty(exports, "S3Provider", {
9
- enumerable: true,
10
- get: function() {
11
- return S3Provider;
12
- }
13
- });
14
- const _common = require("@nestjs/common");
15
- const _constants = require("@flusys/nestjs-shared/constants");
16
- const _uuid = require("uuid");
17
- const _imagecompressorutil = require("../utils/image-compressor.util");
18
- function _define_property(obj, key, value) {
19
- if (key in obj) {
20
- Object.defineProperty(obj, key, {
21
- value: value,
22
- enumerable: true,
23
- configurable: true,
24
- writable: true
25
- });
26
- } else {
27
- obj[key] = value;
28
- }
29
- return obj;
30
- }
31
- function _getRequireWildcardCache(nodeInterop) {
32
- if (typeof WeakMap !== "function") return null;
33
- var cacheBabelInterop = new WeakMap();
34
- var cacheNodeInterop = new WeakMap();
35
- return (_getRequireWildcardCache = function(nodeInterop) {
36
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
37
- })(nodeInterop);
38
- }
39
- function _interop_require_wildcard(obj, nodeInterop) {
40
- if (!nodeInterop && obj && obj.__esModule) {
41
- return obj;
42
- }
43
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
44
- return {
45
- default: obj
46
- };
47
- }
48
- var cache = _getRequireWildcardCache(nodeInterop);
49
- if (cache && cache.has(obj)) {
50
- return cache.get(obj);
51
- }
52
- var newObj = {
53
- __proto__: null
54
- };
55
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
56
- for(var key in obj){
57
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
58
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
59
- if (desc && (desc.get || desc.set)) {
60
- Object.defineProperty(newObj, key, desc);
61
- } else {
62
- newObj[key] = obj[key];
63
- }
64
- }
65
- }
66
- newObj.default = obj;
67
- if (cache) {
68
- cache.set(obj, newObj);
69
- }
70
- return newObj;
71
- }
72
- let S3Provider = class S3Provider {
73
- async initialize(config) {
74
- try {
75
- const { S3Client } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
76
- this.region = config.region;
77
- this.bucketName = config.bucket;
78
- this.s3 = new S3Client({
79
- region: config.region,
80
- endpoint: config.endpoint,
81
- credentials: config.accessKeyId && config.secretAccessKey ? {
82
- accessKeyId: config.accessKeyId,
83
- secretAccessKey: config.secretAccessKey
84
- } : undefined
85
- });
86
- } catch {
87
- throw new _common.InternalServerErrorException({
88
- message: 'AWS SDK not found',
89
- messageKey: _constants.SYSTEM_MESSAGES.SDK_NOT_INSTALLED,
90
- messageVariables: {
91
- sdk: '@aws-sdk/client-s3'
92
- }
93
- });
94
- }
95
- }
96
- async uploadFile(file, options) {
97
- const { Upload } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/lib-storage")));
98
- let processedBuffer = file.buffer;
99
- let contentType = file.mimetype;
100
- const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
101
- if (options.compress && file.mimetype.startsWith('image/')) {
102
- const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
103
- maxWidth: options.maxWidth,
104
- maxHeight: options.maxHeight,
105
- quality: options.quality,
106
- format: options.format
107
- });
108
- processedBuffer = result.buffer;
109
- contentType = result.format;
110
- }
111
- const key = options.folderPath ? `${options.folderPath}/${fileName}` : fileName;
112
- await new Upload({
113
- client: this.s3,
114
- params: {
115
- Bucket: this.bucketName,
116
- Key: key,
117
- Body: processedBuffer,
118
- ContentType: contentType
119
- }
120
- }).done();
121
- return {
122
- name: fileName,
123
- key,
124
- size: processedBuffer.length,
125
- contentType
126
- };
127
- }
128
- async uploadMultipleFiles(files, options) {
129
- return Promise.all(files.map((file)=>this.uploadFile(file, options)));
130
- }
131
- async deleteFile(key) {
132
- const { DeleteObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
133
- await this.s3.send(new DeleteObjectCommand({
134
- Bucket: this.bucketName,
135
- Key: key
136
- }));
137
- }
138
- async deleteMultipleFiles(keys) {
139
- const { DeleteObjectsCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
140
- await this.s3.send(new DeleteObjectsCommand({
141
- Bucket: this.bucketName,
142
- Delete: {
143
- Objects: keys.map((key)=>({
144
- Key: key
145
- }))
146
- }
147
- }));
148
- }
149
- async generatePresignedUrl(key, expiresInSeconds = 3600) {
150
- const { GetObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
151
- const { getSignedUrl } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/s3-request-presigner")));
152
- return getSignedUrl(this.s3, new GetObjectCommand({
153
- Bucket: this.bucketName,
154
- Key: key
155
- }), {
156
- expiresIn: expiresInSeconds
157
- });
158
- }
159
- async healthCheck() {
160
- try {
161
- const { HeadBucketCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
162
- await this.s3.send(new HeadBucketCommand({
163
- Bucket: this.bucketName
164
- }));
165
- return true;
166
- } catch {
167
- return false;
168
- }
169
- }
170
- constructor(){
171
- _define_property(this, "s3", void 0);
172
- _define_property(this, "bucketName", '');
173
- _define_property(this, "region", '');
174
- }
175
- };