@flusys/nestjs-storage 1.1.0-beta → 2.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 (118) hide show
  1. package/README.md +148 -6
  2. package/cjs/config/index.js +0 -1
  3. package/cjs/config/storage.constants.js +0 -17
  4. package/cjs/controllers/file-manager.controller.js +44 -1
  5. package/cjs/controllers/folder.controller.js +44 -1
  6. package/cjs/controllers/storage-config.controller.js +44 -1
  7. package/cjs/controllers/upload.controller.js +18 -29
  8. package/cjs/docs/storage-swagger.config.js +24 -136
  9. package/cjs/dtos/file-manager.dto.js +70 -34
  10. package/cjs/dtos/folder.dto.js +15 -9
  11. package/cjs/dtos/storage-config.dto.js +4 -85
  12. package/cjs/dtos/upload.dto.js +24 -17
  13. package/cjs/entities/file-manager-with-company.entity.js +3 -4
  14. package/cjs/entities/file-manager.entity.js +71 -3
  15. package/cjs/entities/folder-with-company.entity.js +3 -4
  16. package/cjs/entities/folder.entity.js +19 -3
  17. package/cjs/entities/index.js +9 -10
  18. package/cjs/entities/storage-config-with-company.entity.js +3 -4
  19. package/cjs/entities/storage-config.entity.js +74 -3
  20. package/cjs/interfaces/index.js +0 -1
  21. package/cjs/middlewares/file-serve.middleware.js +113 -100
  22. package/cjs/modules/storage.module.js +82 -136
  23. package/cjs/providers/azure-provider.optional.js +10 -38
  24. package/cjs/providers/local-provider.js +38 -31
  25. package/cjs/providers/s3-provider.optional.js +19 -40
  26. package/cjs/providers/storage-factory.service.js +54 -99
  27. package/cjs/providers/storage-provider.registry.js +8 -18
  28. package/cjs/services/file-manager.service.js +238 -323
  29. package/cjs/services/folder.service.js +8 -11
  30. package/cjs/services/index.js +1 -0
  31. package/cjs/{config → services}/storage-config.service.js +32 -76
  32. package/cjs/services/storage-datasource.provider.js +16 -26
  33. package/cjs/services/storage-provider-config.service.js +15 -37
  34. package/cjs/services/upload.service.js +72 -88
  35. package/cjs/utils/file-validator.util.js +458 -0
  36. package/cjs/utils/image-compressor.util.js +3 -8
  37. package/config/index.d.ts +0 -1
  38. package/config/storage.constants.d.ts +0 -8
  39. package/controllers/upload.controller.d.ts +3 -6
  40. package/dtos/file-manager.dto.d.ts +12 -5
  41. package/dtos/folder.dto.d.ts +5 -5
  42. package/dtos/storage-config.dto.d.ts +7 -13
  43. package/entities/file-manager-with-company.entity.d.ts +2 -2
  44. package/entities/file-manager.entity.d.ts +12 -2
  45. package/entities/folder-with-company.entity.d.ts +2 -2
  46. package/entities/folder.entity.d.ts +4 -2
  47. package/entities/index.d.ts +3 -4
  48. package/entities/storage-config-with-company.entity.d.ts +2 -2
  49. package/entities/storage-config.entity.d.ts +8 -2
  50. package/fesm/config/index.js +0 -1
  51. package/fesm/config/storage.constants.js +0 -8
  52. package/fesm/controllers/file-manager.controller.js +45 -2
  53. package/fesm/controllers/folder.controller.js +45 -2
  54. package/fesm/controllers/storage-config.controller.js +45 -2
  55. package/fesm/controllers/upload.controller.js +19 -30
  56. package/fesm/docs/storage-swagger.config.js +27 -142
  57. package/fesm/dtos/file-manager.dto.js +71 -35
  58. package/fesm/dtos/folder.dto.js +16 -10
  59. package/fesm/dtos/storage-config.dto.js +8 -95
  60. package/fesm/dtos/upload.dto.js +25 -19
  61. package/fesm/entities/file-manager-with-company.entity.js +3 -4
  62. package/fesm/entities/file-manager.entity.js +72 -4
  63. package/fesm/entities/folder-with-company.entity.js +3 -4
  64. package/fesm/entities/folder.entity.js +20 -4
  65. package/fesm/entities/index.js +5 -13
  66. package/fesm/entities/storage-config-with-company.entity.js +3 -4
  67. package/fesm/entities/storage-config.entity.js +75 -4
  68. package/fesm/interfaces/index.js +0 -1
  69. package/fesm/interfaces/storage-config.interface.js +1 -3
  70. package/fesm/middlewares/file-serve.middleware.js +114 -101
  71. package/fesm/modules/storage.module.js +83 -136
  72. package/fesm/providers/azure-provider.optional.js +11 -42
  73. package/fesm/providers/local-provider.js +38 -31
  74. package/fesm/providers/s3-provider.optional.js +20 -44
  75. package/fesm/providers/storage-factory.service.js +52 -97
  76. package/fesm/providers/storage-provider.registry.js +10 -20
  77. package/fesm/services/file-manager.service.js +237 -322
  78. package/fesm/services/folder.service.js +6 -9
  79. package/fesm/services/index.js +1 -0
  80. package/fesm/{config → services}/storage-config.service.js +32 -76
  81. package/fesm/services/storage-datasource.provider.js +16 -26
  82. package/fesm/services/storage-provider-config.service.js +13 -35
  83. package/fesm/services/upload.service.js +71 -87
  84. package/fesm/utils/file-validator.util.js +451 -0
  85. package/fesm/utils/image-compressor.util.js +3 -8
  86. package/interfaces/file-manager.interface.d.ts +7 -4
  87. package/interfaces/index.d.ts +0 -1
  88. package/interfaces/storage-config.interface.d.ts +0 -20
  89. package/interfaces/storage-module-options.interface.d.ts +0 -5
  90. package/middlewares/file-serve.middleware.d.ts +9 -1
  91. package/modules/storage.module.d.ts +1 -2
  92. package/package.json +6 -6
  93. package/providers/azure-provider.optional.d.ts +8 -6
  94. package/providers/local-provider.d.ts +2 -7
  95. package/providers/s3-provider.optional.d.ts +9 -7
  96. package/providers/storage-factory.service.d.ts +8 -9
  97. package/providers/storage-provider.registry.d.ts +4 -4
  98. package/services/file-manager.service.d.ts +23 -16
  99. package/services/folder.service.d.ts +4 -4
  100. package/services/index.d.ts +1 -0
  101. package/services/storage-config.service.d.ts +24 -0
  102. package/services/storage-datasource.provider.d.ts +3 -4
  103. package/services/storage-provider-config.service.d.ts +4 -4
  104. package/services/upload.service.d.ts +3 -2
  105. package/utils/file-validator.util.d.ts +19 -0
  106. package/cjs/entities/file-manager-base.entity.js +0 -115
  107. package/cjs/entities/folder-base.entity.js +0 -55
  108. package/cjs/entities/storage-config-base.entity.js +0 -93
  109. package/cjs/interfaces/file-upload-response.interface.js +0 -4
  110. package/config/storage-config.service.d.ts +0 -22
  111. package/entities/file-manager-base.entity.d.ts +0 -13
  112. package/entities/folder-base.entity.d.ts +0 -5
  113. package/entities/storage-config-base.entity.d.ts +0 -9
  114. package/fesm/entities/file-manager-base.entity.js +0 -108
  115. package/fesm/entities/folder-base.entity.js +0 -48
  116. package/fesm/entities/storage-config-base.entity.js +0 -83
  117. package/fesm/interfaces/file-upload-response.interface.js +0 -1
  118. package/interfaces/file-upload-response.interface.d.ts +0 -6
@@ -8,11 +8,13 @@ Object.defineProperty(exports, "UploadService", {
8
8
  return UploadService;
9
9
  }
10
10
  });
11
+ const _utils = require("@flusys/nestjs-shared/utils");
11
12
  const _common = require("@nestjs/common");
12
- const _config = require("../config");
13
+ const _storageconfigservice = require("./storage-config.service");
13
14
  const _filelocationenum = require("../enums/file-location.enum");
14
15
  const _storagefactoryservice = require("../providers/storage-factory.service");
15
16
  const _storageproviderconfigservice = require("./storage-provider-config.service");
17
+ const _filevalidatorutil = require("../utils/file-validator.util");
16
18
  function _define_property(obj, key, value) {
17
19
  if (key in obj) {
18
20
  Object.defineProperty(obj, key, {
@@ -42,18 +44,51 @@ function _ts_param(paramIndex, decorator) {
42
44
  }
43
45
  let UploadService = class UploadService {
44
46
  /**
45
- * Validate file before upload
47
+ * Validate file before upload - includes size, type, and content validation.
48
+ * Uses magic bytes to verify file content matches declared MIME type.
46
49
  */ validateFile(file) {
47
50
  // Validate file size
48
51
  const sizeValidation = this.storageConfigService.validateFileSize(file.size);
49
52
  if (!sizeValidation.valid) {
50
53
  throw new _common.BadRequestException(sizeValidation.message);
51
54
  }
52
- // Validate file type
55
+ // Validate declared file type (MIME)
53
56
  const typeValidation = this.storageConfigService.validateFileType(file.mimetype);
54
57
  if (!typeValidation.valid) {
55
58
  throw new _common.BadRequestException(typeValidation.message);
56
59
  }
60
+ // Validate file content matches declared type (magic bytes check)
61
+ // This prevents MIME type spoofing attacks
62
+ const allowedTypes = this.storageConfigService.getAllowedFileTypes();
63
+ const contentValidation = _filevalidatorutil.FileValidator.validateFileContent(file.buffer, file.mimetype, allowedTypes);
64
+ if (!contentValidation.valid) {
65
+ this.logger.warn(`File content validation failed: ${contentValidation.message}`, {
66
+ declaredType: file.mimetype,
67
+ detectedType: contentValidation.detectedType
68
+ });
69
+ throw new _common.BadRequestException(contentValidation.message || 'File content validation failed');
70
+ }
71
+ // Sanitize filename to prevent path traversal attacks
72
+ file.originalname = _filevalidatorutil.FileValidator.sanitizeFilename(file.originalname);
73
+ }
74
+ /**
75
+ * Create provider from storage config entity
76
+ */ async createProviderFromConfig(config) {
77
+ return this.storageFactory.createProvider({
78
+ provider: config.storage,
79
+ config: config.config
80
+ });
81
+ }
82
+ /**
83
+ * Create fallback local provider
84
+ */ async createFallbackLocalProvider() {
85
+ this.logger.warn('No storage config found, using fallback local provider');
86
+ return this.storageFactory.createProvider({
87
+ provider: _filelocationenum.FileLocationEnum.LOCAL,
88
+ config: {
89
+ basePath: this.storageConfigService.getDefaultLocalStoragePath()
90
+ }
91
+ });
57
92
  }
58
93
  /**
59
94
  * Get storage provider and config info based on storage config ID
@@ -67,13 +102,8 @@ let UploadService = class UploadService {
67
102
  if (!config) {
68
103
  throw new _common.NotFoundException('Storage configuration not found');
69
104
  }
70
- // Validate company ownership if company feature enabled
71
- if (this.storageConfigService.isCompanyFeatureEnabled() && user?.companyId) {
72
- const configWithCompany = config;
73
- if (configWithCompany.companyId && configWithCompany.companyId !== user.companyId) {
74
- throw new _common.BadRequestException('Storage configuration belongs to another company');
75
- }
76
- }
105
+ // Validate company ownership using shared utility
106
+ (0, _utils.validateCompanyOwnership)(config, user, this.storageConfigService.isCompanyFeatureEnabled(), 'Storage configuration');
77
107
  storageConfig = config;
78
108
  } else {
79
109
  // Use default config (scoped to user's company/branch)
@@ -83,13 +113,7 @@ let UploadService = class UploadService {
83
113
  }
84
114
  storageConfig = defaultConfig;
85
115
  }
86
- // Create provider config
87
- const providerConfig = {
88
- provider: storageConfig.storage,
89
- config: storageConfig.config
90
- };
91
- // Get or create provider instance
92
- const provider = await this.storageFactory.createProvider(providerConfig);
116
+ const provider = await this.createProviderFromConfig(storageConfig);
93
117
  return {
94
118
  provider,
95
119
  location: storageConfig.storage,
@@ -97,74 +121,33 @@ let UploadService = class UploadService {
97
121
  };
98
122
  }
99
123
  /**
100
- * Get storage provider based on storage config ID (convenience method)
101
- */ async getStorageProvider(storageConfigId, user) {
102
- const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
103
- return provider;
104
- }
105
- /**
106
124
  * Get storage provider for delete operations with fallback
107
125
  * If the original storage config doesn't exist, falls back based on locationHint
108
- * This ensures files can still be deleted even if storage config was removed
109
- * @param storageConfigId - The storage config ID to look up
110
- * @param user - User context for company filtering
111
- * @param locationHint - The file's original location type (used for fallback)
112
126
  */ async getStorageProviderForDelete(storageConfigId, user, locationHint) {
113
- // If storageConfigId provided, try to find it
127
+ // Try to find by storageConfigId
114
128
  if (storageConfigId) {
115
129
  const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
116
130
  if (config) {
117
- const providerConfig = {
118
- provider: config.storage,
119
- config: config.config
120
- };
121
- return await this.storageFactory.createProvider(providerConfig);
131
+ return this.createProviderFromConfig(config);
122
132
  }
123
- // Config not found, log warning and try fallback
124
- this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback for delete`);
133
+ this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback`);
125
134
  }
126
- // Fallback: Use locationHint to find a matching config or create provider
135
+ // Fallback: Use locationHint to find a matching config
127
136
  if (locationHint) {
128
- // Try to find a config matching the file's original location type
129
137
  const matchingConfigs = await this.storageProviderConfigService.getConfigByType(locationHint, user);
130
138
  if (matchingConfigs.length > 0) {
131
- const providerConfig = {
132
- provider: matchingConfigs[0].storage,
133
- config: matchingConfigs[0].config
134
- };
135
- this.logger.debug(`Using matching ${locationHint} config for delete fallback`);
136
- return await this.storageFactory.createProvider(providerConfig);
139
+ return this.createProviderFromConfig(matchingConfigs[0]);
137
140
  }
138
- // No matching config found, create a basic provider based on locationHint
139
141
  if (locationHint === _filelocationenum.FileLocationEnum.LOCAL) {
140
- this.logger.warn('No local config found, using fallback local provider for delete');
141
- const localProviderConfig = {
142
- provider: _filelocationenum.FileLocationEnum.LOCAL,
143
- config: {
144
- basePath: this.storageConfigService.getDefaultLocalStoragePath()
145
- }
146
- };
147
- return await this.storageFactory.createProvider(localProviderConfig);
142
+ return this.createFallbackLocalProvider();
148
143
  }
149
144
  }
150
- // Last resort: Try default config (might be different provider type)
145
+ // Last resort: Try default config
151
146
  const defaultConfig = await this.storageProviderConfigService.getDefaultConfig(user);
152
147
  if (defaultConfig) {
153
- const providerConfig = {
154
- provider: defaultConfig.storage,
155
- config: defaultConfig.config
156
- };
157
- return await this.storageFactory.createProvider(providerConfig);
148
+ return this.createProviderFromConfig(defaultConfig);
158
149
  }
159
- // Final fallback: Create a basic local provider
160
- this.logger.warn('No storage config found, using fallback local provider for delete');
161
- const localProviderConfig = {
162
- provider: _filelocationenum.FileLocationEnum.LOCAL,
163
- config: {
164
- basePath: this.storageConfigService.getDefaultLocalStoragePath()
165
- }
166
- };
167
- return await this.storageFactory.createProvider(localProviderConfig);
150
+ return this.createFallbackLocalProvider();
168
151
  }
169
152
  async uploadSingleFile(file, options, user) {
170
153
  try {
@@ -178,9 +161,9 @@ let UploadService = class UploadService {
178
161
  location,
179
162
  storageConfigId: configId
180
163
  };
181
- } catch (err) {
182
- this.logger.error('Single file upload failed', err);
183
- throw new _common.InternalServerErrorException(err?.message || 'Single file upload failed');
164
+ } catch (error) {
165
+ _utils.ErrorHandler.logError(this.logger, error, 'uploadSingleFile');
166
+ _utils.ErrorHandler.rethrowError(error);
184
167
  }
185
168
  }
186
169
  async uploadMultipleFiles(files, options, user) {
@@ -197,31 +180,31 @@ let UploadService = class UploadService {
197
180
  location,
198
181
  storageConfigId: configId
199
182
  }));
200
- } catch (err) {
201
- this.logger.error('Multiple files upload failed', err);
202
- throw new _common.InternalServerErrorException(err?.message || 'Multiple files upload failed');
183
+ } catch (error) {
184
+ _utils.ErrorHandler.logError(this.logger, error, 'uploadMultipleFiles');
185
+ _utils.ErrorHandler.rethrowError(error);
203
186
  }
204
187
  }
205
188
  async deleteSingleFile(key, storageConfigId, user, locationHint) {
206
189
  try {
207
- if (!key) throw new Error('No file path provided');
190
+ if (!key) throw new _common.BadRequestException('No file path provided');
208
191
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
209
192
  await provider.deleteFile(key);
210
193
  return true;
211
- } catch (err) {
212
- this.logger.error('Delete single file failed', err);
213
- throw new _common.InternalServerErrorException(err?.message || 'Delete single file failed');
194
+ } catch (error) {
195
+ _utils.ErrorHandler.logError(this.logger, error, 'deleteSingleFile');
196
+ _utils.ErrorHandler.rethrowError(error);
214
197
  }
215
198
  }
216
199
  async deleteMultipleFile(keys, storageConfigId, user, locationHint) {
217
200
  try {
218
- if (!keys || !keys.length) throw new Error('No file paths provided');
201
+ if (!keys || !keys.length) throw new _common.BadRequestException('No file paths provided');
219
202
  const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
220
203
  await provider.deleteMultipleFiles(keys);
221
204
  return true;
222
- } catch (err) {
223
- this.logger.error('Delete multiple files failed', err);
224
- throw new _common.InternalServerErrorException(err?.message || 'Delete multiple files failed');
205
+ } catch (error) {
206
+ _utils.ErrorHandler.logError(this.logger, error, 'deleteMultipleFiles');
207
+ _utils.ErrorHandler.rethrowError(error);
225
208
  }
226
209
  }
227
210
  bytesToKb(bytes) {
@@ -251,7 +234,8 @@ let UploadService = class UploadService {
251
234
  }
252
235
  return null;
253
236
  } catch (error) {
254
- this.logger.warn(`Failed to get local storage basePath: ${error.message}`);
237
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
238
+ this.logger.warn(`Failed to get local storage basePath: ${errorMessage}`);
255
239
  return null;
256
240
  }
257
241
  }
@@ -261,16 +245,16 @@ let UploadService = class UploadService {
261
245
  * For Local/SFTP: returns direct path
262
246
  */ async makeFileUrl(key, storageConfigId, expiresIn = 3600, user) {
263
247
  try {
264
- const provider = await this.getStorageProvider(storageConfigId, user);
248
+ const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
265
249
  // Check if provider supports presigned URLs
266
250
  if (provider.generatePresignedUrl) {
267
251
  return await provider.generatePresignedUrl(key, expiresIn);
268
252
  }
269
253
  // For SFTP or other providers without presigned URLs
270
254
  return key;
271
- } catch (err) {
272
- this.logger.error('Generate file URL failed', err);
273
- throw new _common.InternalServerErrorException(err?.message || 'Generate file URL failed');
255
+ } catch (error) {
256
+ _utils.ErrorHandler.logError(this.logger, error, 'makeFileUrl');
257
+ _utils.ErrorHandler.rethrowError(error);
274
258
  }
275
259
  }
276
260
  // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
@@ -290,12 +274,12 @@ UploadService = _ts_decorate([
290
274
  scope: _common.Scope.REQUEST
291
275
  }),
292
276
  _ts_param(0, (0, _common.Inject)(_storagefactoryservice.StorageFactoryService)),
293
- _ts_param(1, (0, _common.Inject)(_config.StorageConfigService)),
277
+ _ts_param(1, (0, _common.Inject)(_storageconfigservice.StorageConfigService)),
294
278
  _ts_param(2, (0, _common.Inject)(_storageproviderconfigservice.StorageProviderConfigService)),
295
279
  _ts_metadata("design:type", Function),
296
280
  _ts_metadata("design:paramtypes", [
297
281
  typeof _storagefactoryservice.StorageFactoryService === "undefined" ? Object : _storagefactoryservice.StorageFactoryService,
298
- typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService,
282
+ typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
299
283
  typeof _storageproviderconfigservice.StorageProviderConfigService === "undefined" ? Object : _storageproviderconfigservice.StorageProviderConfigService
300
284
  ])
301
285
  ], UploadService);