@flusys/nestjs-storage 1.1.0-beta → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -6
- package/cjs/config/index.js +0 -1
- package/cjs/config/storage.constants.js +0 -17
- package/cjs/controllers/file-manager.controller.js +44 -1
- package/cjs/controllers/folder.controller.js +44 -1
- package/cjs/controllers/storage-config.controller.js +44 -1
- package/cjs/controllers/upload.controller.js +18 -29
- package/cjs/docs/storage-swagger.config.js +24 -136
- package/cjs/dtos/file-manager.dto.js +70 -34
- package/cjs/dtos/folder.dto.js +15 -9
- package/cjs/dtos/storage-config.dto.js +4 -85
- package/cjs/dtos/upload.dto.js +24 -17
- package/cjs/entities/file-manager-with-company.entity.js +3 -4
- package/cjs/entities/file-manager.entity.js +71 -3
- package/cjs/entities/folder-with-company.entity.js +3 -4
- package/cjs/entities/folder.entity.js +19 -3
- package/cjs/entities/index.js +9 -10
- package/cjs/entities/storage-config-with-company.entity.js +3 -4
- package/cjs/entities/storage-config.entity.js +74 -3
- package/cjs/interfaces/index.js +0 -1
- package/cjs/middlewares/file-serve.middleware.js +113 -100
- package/cjs/modules/storage.module.js +82 -136
- package/cjs/providers/azure-provider.optional.js +10 -38
- package/cjs/providers/local-provider.js +38 -31
- package/cjs/providers/s3-provider.optional.js +19 -40
- package/cjs/providers/storage-factory.service.js +54 -99
- package/cjs/providers/storage-provider.registry.js +8 -18
- package/cjs/services/file-manager.service.js +238 -323
- package/cjs/services/folder.service.js +8 -11
- package/cjs/services/index.js +1 -0
- package/cjs/{config → services}/storage-config.service.js +32 -76
- package/cjs/services/storage-datasource.provider.js +16 -26
- package/cjs/services/storage-provider-config.service.js +15 -37
- package/cjs/services/upload.service.js +72 -88
- package/cjs/utils/file-validator.util.js +458 -0
- package/cjs/utils/image-compressor.util.js +3 -8
- package/config/index.d.ts +0 -1
- package/config/storage.constants.d.ts +0 -8
- package/controllers/upload.controller.d.ts +3 -6
- package/dtos/file-manager.dto.d.ts +12 -5
- package/dtos/folder.dto.d.ts +5 -5
- package/dtos/storage-config.dto.d.ts +7 -13
- package/entities/file-manager-with-company.entity.d.ts +2 -2
- package/entities/file-manager.entity.d.ts +12 -2
- package/entities/folder-with-company.entity.d.ts +2 -2
- package/entities/folder.entity.d.ts +4 -2
- package/entities/index.d.ts +3 -4
- package/entities/storage-config-with-company.entity.d.ts +2 -2
- package/entities/storage-config.entity.d.ts +8 -2
- package/fesm/config/index.js +0 -1
- package/fesm/config/storage.constants.js +0 -8
- package/fesm/controllers/file-manager.controller.js +45 -2
- package/fesm/controllers/folder.controller.js +45 -2
- package/fesm/controllers/storage-config.controller.js +45 -2
- package/fesm/controllers/upload.controller.js +19 -30
- package/fesm/docs/storage-swagger.config.js +27 -142
- package/fesm/dtos/file-manager.dto.js +71 -35
- package/fesm/dtos/folder.dto.js +16 -10
- package/fesm/dtos/storage-config.dto.js +8 -95
- package/fesm/dtos/upload.dto.js +25 -19
- package/fesm/entities/file-manager-with-company.entity.js +3 -4
- package/fesm/entities/file-manager.entity.js +72 -4
- package/fesm/entities/folder-with-company.entity.js +3 -4
- package/fesm/entities/folder.entity.js +20 -4
- package/fesm/entities/index.js +5 -13
- package/fesm/entities/storage-config-with-company.entity.js +3 -4
- package/fesm/entities/storage-config.entity.js +75 -4
- package/fesm/interfaces/index.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +1 -3
- package/fesm/middlewares/file-serve.middleware.js +114 -101
- package/fesm/modules/storage.module.js +83 -136
- package/fesm/providers/azure-provider.optional.js +11 -42
- package/fesm/providers/local-provider.js +38 -31
- package/fesm/providers/s3-provider.optional.js +20 -44
- package/fesm/providers/storage-factory.service.js +52 -97
- package/fesm/providers/storage-provider.registry.js +10 -20
- package/fesm/services/file-manager.service.js +237 -322
- package/fesm/services/folder.service.js +6 -9
- package/fesm/services/index.js +1 -0
- package/fesm/{config → services}/storage-config.service.js +32 -76
- package/fesm/services/storage-datasource.provider.js +16 -26
- package/fesm/services/storage-provider-config.service.js +13 -35
- package/fesm/services/upload.service.js +71 -87
- package/fesm/utils/file-validator.util.js +451 -0
- package/fesm/utils/image-compressor.util.js +3 -8
- package/interfaces/file-manager.interface.d.ts +7 -4
- package/interfaces/index.d.ts +0 -1
- package/interfaces/storage-config.interface.d.ts +0 -20
- package/interfaces/storage-module-options.interface.d.ts +0 -5
- package/middlewares/file-serve.middleware.d.ts +9 -1
- package/modules/storage.module.d.ts +1 -2
- package/package.json +6 -6
- package/providers/azure-provider.optional.d.ts +8 -6
- package/providers/local-provider.d.ts +2 -7
- package/providers/s3-provider.optional.d.ts +9 -7
- package/providers/storage-factory.service.d.ts +8 -9
- package/providers/storage-provider.registry.d.ts +4 -4
- package/services/file-manager.service.d.ts +23 -16
- package/services/folder.service.d.ts +4 -4
- package/services/index.d.ts +1 -0
- package/services/storage-config.service.d.ts +24 -0
- package/services/storage-datasource.provider.d.ts +3 -4
- package/services/storage-provider-config.service.d.ts +4 -4
- package/services/upload.service.d.ts +3 -2
- package/utils/file-validator.util.d.ts +19 -0
- package/cjs/entities/file-manager-base.entity.js +0 -115
- package/cjs/entities/folder-base.entity.js +0 -55
- package/cjs/entities/storage-config-base.entity.js +0 -93
- package/cjs/interfaces/file-upload-response.interface.js +0 -4
- package/config/storage-config.service.d.ts +0 -22
- package/entities/file-manager-base.entity.d.ts +0 -13
- package/entities/folder-base.entity.d.ts +0 -5
- package/entities/storage-config-base.entity.d.ts +0 -9
- package/fesm/entities/file-manager-base.entity.js +0 -108
- package/fesm/entities/folder-base.entity.js +0 -48
- package/fesm/entities/storage-config-base.entity.js +0 -83
- package/fesm/interfaces/file-upload-response.interface.js +0 -1
- 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
|
|
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
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
127
|
+
// Try to find by storageConfigId
|
|
114
128
|
if (storageConfigId) {
|
|
115
129
|
const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
|
|
116
130
|
if (config) {
|
|
117
|
-
|
|
118
|
-
provider: config.storage,
|
|
119
|
-
config: config.config
|
|
120
|
-
};
|
|
121
|
-
return await this.storageFactory.createProvider(providerConfig);
|
|
131
|
+
return this.createProviderFromConfig(config);
|
|
122
132
|
}
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
145
|
+
// Last resort: Try default config
|
|
151
146
|
const defaultConfig = await this.storageProviderConfigService.getDefaultConfig(user);
|
|
152
147
|
if (defaultConfig) {
|
|
153
|
-
|
|
154
|
-
provider: defaultConfig.storage,
|
|
155
|
-
config: defaultConfig.config
|
|
156
|
-
};
|
|
157
|
-
return await this.storageFactory.createProvider(providerConfig);
|
|
148
|
+
return this.createProviderFromConfig(defaultConfig);
|
|
158
149
|
}
|
|
159
|
-
|
|
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 (
|
|
182
|
-
this.logger
|
|
183
|
-
|
|
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 (
|
|
201
|
-
this.logger
|
|
202
|
-
|
|
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
|
|
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 (
|
|
212
|
-
this.logger
|
|
213
|
-
|
|
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
|
|
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 (
|
|
223
|
-
this.logger
|
|
224
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
272
|
-
this.logger
|
|
273
|
-
|
|
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)(
|
|
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
|
|
282
|
+
typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
|
|
299
283
|
typeof _storageproviderconfigservice.StorageProviderConfigService === "undefined" ? Object : _storageproviderconfigservice.StorageProviderConfigService
|
|
300
284
|
])
|
|
301
285
|
], UploadService);
|