@flusys/nestjs-storage 3.0.0 → 4.0.0-rc
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 +1 -1
- package/cjs/config/index.js +1 -0
- package/cjs/config/message-keys.js +90 -0
- package/cjs/controllers/file-manager.controller.js +7 -1
- package/cjs/controllers/folder.controller.js +1 -0
- package/cjs/controllers/storage-config.controller.js +1 -0
- package/cjs/controllers/upload.controller.js +9 -4
- package/cjs/middlewares/file-serve.middleware.js +39 -15
- package/cjs/modules/storage.module.js +2 -2
- package/cjs/providers/azure-provider.optional.js +8 -5
- package/cjs/providers/local-provider.js +16 -14
- package/cjs/providers/s3-provider.optional.js +8 -5
- package/cjs/providers/sftp-provider.optional.js +13 -11
- package/cjs/providers/storage-factory.service.js +19 -10
- package/cjs/services/file-manager.service.js +30 -23
- package/cjs/services/folder.service.js +2 -1
- package/cjs/services/storage-datasource.provider.js +6 -2
- package/cjs/services/storage-provider-config.service.js +2 -1
- package/cjs/services/upload.service.js +129 -79
- package/cjs/utils/file-validator.util.js +4 -22
- package/config/index.d.ts +1 -0
- package/config/message-keys.d.ts +114 -0
- package/controllers/folder.controller.d.ts +7 -7
- package/controllers/storage-config.controller.d.ts +7 -7
- package/controllers/upload.controller.d.ts +1 -1
- package/fesm/config/index.js +1 -0
- package/fesm/config/message-keys.js +64 -0
- package/fesm/controllers/file-manager.controller.js +7 -1
- package/fesm/controllers/folder.controller.js +1 -0
- package/fesm/controllers/storage-config.controller.js +1 -0
- package/fesm/controllers/upload.controller.js +9 -4
- package/fesm/middlewares/file-serve.middleware.js +40 -16
- package/fesm/modules/storage.module.js +2 -2
- package/fesm/providers/azure-provider.optional.js +9 -6
- package/fesm/providers/local-provider.js +28 -26
- package/fesm/providers/s3-provider.optional.js +9 -6
- package/fesm/providers/sftp-provider.optional.js +26 -24
- package/fesm/providers/storage-factory.service.js +20 -11
- package/fesm/services/file-manager.service.js +31 -24
- package/fesm/services/folder.service.js +2 -1
- package/fesm/services/storage-datasource.provider.js +7 -3
- package/fesm/services/storage-provider-config.service.js +2 -1
- package/fesm/services/upload.service.js +131 -81
- package/fesm/utils/file-validator.util.js +3 -21
- package/middlewares/file-serve.middleware.d.ts +0 -1
- package/package.json +3 -3
- package/providers/azure-provider.optional.d.ts +0 -1
- package/providers/local-provider.d.ts +0 -1
- package/providers/s3-provider.optional.d.ts +0 -1
- package/providers/sftp-provider.optional.d.ts +0 -1
- package/providers/storage-factory.service.d.ts +0 -1
- package/services/file-manager.service.d.ts +2 -2
- package/services/storage-datasource.provider.d.ts +0 -2
- package/services/upload.service.d.ts +0 -1
- package/utils/file-validator.util.d.ts +0 -1
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "StorageFactoryService", {
|
|
|
10
10
|
});
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
12
|
const _crypto = /*#__PURE__*/ _interop_require_wildcard(require("crypto"));
|
|
13
|
+
const _constants = require("@flusys/nestjs-shared/constants");
|
|
13
14
|
const _services = require("../services");
|
|
14
15
|
const _storageproviderregistry = require("./storage-provider.registry");
|
|
15
16
|
function _define_property(obj, key, value) {
|
|
@@ -87,17 +88,30 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
87
88
|
if (cached) return cached;
|
|
88
89
|
const ProviderClass = _storageproviderregistry.StorageProviderRegistry.get(config.provider);
|
|
89
90
|
if (!ProviderClass) {
|
|
90
|
-
throw new _common.NotFoundException(
|
|
91
|
+
throw new _common.NotFoundException({
|
|
92
|
+
message: `Storage provider '${config.provider}' not registered. Available: ${_storageproviderregistry.StorageProviderRegistry.getAll().join(', ')}`,
|
|
93
|
+
messageKey: _constants.SYSTEM_MESSAGES.SERVICE_NOT_AVAILABLE,
|
|
94
|
+
messageParams: {
|
|
95
|
+
provider: config.provider,
|
|
96
|
+
available: _storageproviderregistry.StorageProviderRegistry.getAll().join(', ')
|
|
97
|
+
}
|
|
98
|
+
});
|
|
91
99
|
}
|
|
92
100
|
try {
|
|
93
101
|
const instance = new ProviderClass();
|
|
94
102
|
await this.initializeProvider(instance, config);
|
|
95
103
|
this.cache.set(cacheKey, instance);
|
|
96
|
-
this.logger.log(`Created provider: ${config.provider} (${cacheKey})`);
|
|
97
104
|
return instance;
|
|
98
105
|
} catch (error) {
|
|
99
|
-
const
|
|
100
|
-
throw new
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
107
|
+
throw new _common.InternalServerErrorException({
|
|
108
|
+
message: `Failed to initialize '${config.provider}': ${errorMessage}`,
|
|
109
|
+
messageKey: _constants.SYSTEM_MESSAGES.INTERNAL_ERROR,
|
|
110
|
+
messageParams: {
|
|
111
|
+
provider: config.provider,
|
|
112
|
+
error: errorMessage
|
|
113
|
+
}
|
|
114
|
+
});
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
async getProvider(providerName) {
|
|
@@ -117,11 +131,9 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
117
131
|
this.cache.clear();
|
|
118
132
|
}
|
|
119
133
|
async onModuleDestroy() {
|
|
120
|
-
this.
|
|
121
|
-
const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([key, p])=>p.close().then(()=>this.logger.debug(`Closed: ${key}`)).catch((e)=>this.logger.warn(`Failed to close ${key}: ${e.message}`)));
|
|
134
|
+
const closePromises = Array.from(this.cache.entries()).filter(([, p])=>'close' in p && typeof p.close === 'function').map(([, p])=>p.close().catch(()=>{}));
|
|
122
135
|
await Promise.allSettled(closePromises);
|
|
123
136
|
this.cache.clear();
|
|
124
|
-
this.logger.log('Storage provider cleanup complete');
|
|
125
137
|
}
|
|
126
138
|
// ─── Private Helpers ──────────────────────────────────────────────────────────
|
|
127
139
|
generateCacheKey(config) {
|
|
@@ -142,17 +154,14 @@ let StorageFactoryService = class StorageFactoryService {
|
|
|
142
154
|
...config.config,
|
|
143
155
|
baseUrl: appUrl
|
|
144
156
|
};
|
|
145
|
-
this.logger.debug(`Using appUrl as baseUrl: ${appUrl}`);
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
await instance.initialize(initConfig);
|
|
149
160
|
}
|
|
150
161
|
constructor(configService){
|
|
151
162
|
_define_property(this, "configService", void 0);
|
|
152
|
-
_define_property(this, "logger", void 0);
|
|
153
163
|
_define_property(this, "cache", void 0);
|
|
154
164
|
this.configService = configService;
|
|
155
|
-
this.logger = new _common.Logger(StorageFactoryService.name);
|
|
156
165
|
this.cache = new Map();
|
|
157
166
|
}
|
|
158
167
|
};
|
|
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "FileManagerService", {
|
|
|
11
11
|
const _classes = require("@flusys/nestjs-shared/classes");
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
13
|
const _utils = require("@flusys/nestjs-shared/utils");
|
|
14
|
+
const _config = require("../config");
|
|
14
15
|
const _common = require("@nestjs/common");
|
|
15
16
|
const _typeorm = require("typeorm");
|
|
16
17
|
const _storageconfigservice = require("./storage-config.service");
|
|
@@ -118,7 +119,10 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
118
119
|
id: dto.id
|
|
119
120
|
}
|
|
120
121
|
});
|
|
121
|
-
if (!existing) throw new _common.NotFoundException(
|
|
122
|
+
if (!existing) throw new _common.NotFoundException({
|
|
123
|
+
message: 'Entity not found for update',
|
|
124
|
+
messageKey: _config.FILE_MESSAGES.NOT_FOUND
|
|
125
|
+
});
|
|
122
126
|
entity = existing;
|
|
123
127
|
}
|
|
124
128
|
const validatedFolder = dto.folderId ? await this.validateFolder(dto.folderId, user) : null;
|
|
@@ -148,7 +152,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
148
152
|
}
|
|
149
153
|
async getFilterQuery(query, filter, _user) {
|
|
150
154
|
for (const [key, value] of Object.entries(filter)){
|
|
151
|
-
if (key === 'contentType') {
|
|
155
|
+
if (key === 'contentType' && typeof value === 'string') {
|
|
152
156
|
this.applyContentTypeFilter(query, value);
|
|
153
157
|
} else {
|
|
154
158
|
query.andWhere(`${this.entityName}.${key} = :value`, {
|
|
@@ -220,8 +224,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
220
224
|
...item,
|
|
221
225
|
providerName: item.storageConfigId ? nameMap.get(item.storageConfigId) : undefined
|
|
222
226
|
}));
|
|
223
|
-
} catch
|
|
224
|
-
this.logger.warn(`Failed to fetch provider names: ${_utils.ErrorHandler.getErrorMessage(error)}`);
|
|
227
|
+
} catch {
|
|
225
228
|
return items.map((item)=>({
|
|
226
229
|
...item,
|
|
227
230
|
providerName: undefined
|
|
@@ -231,7 +234,10 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
231
234
|
async getFiles(dtos, protocol, host, user) {
|
|
232
235
|
await this.ensureRepositoryInitialized();
|
|
233
236
|
const ids = dtos.map((d)=>d.id).filter(Boolean);
|
|
234
|
-
if (!ids.length) throw new _common.BadRequestException(
|
|
237
|
+
if (!ids.length) throw new _common.BadRequestException({
|
|
238
|
+
message: 'No valid file IDs provided',
|
|
239
|
+
messageKey: _config.UPLOAD_MESSAGES.NO_FILES_PROVIDED
|
|
240
|
+
});
|
|
235
241
|
const files = await this.repository.findBy({
|
|
236
242
|
id: (0, _typeorm.In)(ids)
|
|
237
243
|
});
|
|
@@ -245,17 +251,13 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
245
251
|
if (updatedFiles.length) {
|
|
246
252
|
try {
|
|
247
253
|
await this.repository.save(updatedFiles);
|
|
248
|
-
} catch
|
|
249
|
-
|
|
254
|
+
} catch {
|
|
255
|
+
// Silent failure - URL update is non-critical
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
258
|
return responses;
|
|
253
259
|
}
|
|
254
260
|
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
255
|
-
async getStorageConfigRepository() {
|
|
256
|
-
const entity = this.storageConfig.isCompanyFeatureEnabled() ? (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfigWithCompany : (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfig;
|
|
257
|
-
return this.dataSourceProvider.getRepository(entity);
|
|
258
|
-
}
|
|
259
261
|
buildWhereWithCompany(id, user) {
|
|
260
262
|
const where = {
|
|
261
263
|
id
|
|
@@ -265,6 +267,10 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
265
267
|
}
|
|
266
268
|
return where;
|
|
267
269
|
}
|
|
270
|
+
async getStorageConfigRepository() {
|
|
271
|
+
const entity = this.storageConfig.isCompanyFeatureEnabled() ? (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfigWithCompany : (await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")))).StorageConfig;
|
|
272
|
+
return this.dataSourceProvider.getRepository(entity);
|
|
273
|
+
}
|
|
268
274
|
async getStorageConfigBasePath(configId) {
|
|
269
275
|
try {
|
|
270
276
|
const repo = await this.getStorageConfigRepository();
|
|
@@ -278,8 +284,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
278
284
|
]
|
|
279
285
|
});
|
|
280
286
|
return config?.config?.basePath?.replace(/^\.\//, '') ?? null;
|
|
281
|
-
} catch
|
|
282
|
-
this.logger.warn(`Failed to get basePath for ${configId}: ${_utils.ErrorHandler.getErrorMessage(error)}`);
|
|
287
|
+
} catch {
|
|
283
288
|
return null;
|
|
284
289
|
}
|
|
285
290
|
}
|
|
@@ -290,7 +295,13 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
290
295
|
where: this.buildWhereWithCompany(folderId, user)
|
|
291
296
|
});
|
|
292
297
|
if (!folder) {
|
|
293
|
-
throw new _common.BadRequestException(
|
|
298
|
+
throw new _common.BadRequestException({
|
|
299
|
+
message: `Folder "${folderId}" not found or access denied`,
|
|
300
|
+
messageKey: _config.FOLDER_MESSAGES.NOT_FOUND,
|
|
301
|
+
messageParams: {
|
|
302
|
+
folderId
|
|
303
|
+
}
|
|
304
|
+
});
|
|
294
305
|
}
|
|
295
306
|
return folder;
|
|
296
307
|
}
|
|
@@ -303,8 +314,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
303
314
|
where: this.buildWhereWithCompany(configId, user)
|
|
304
315
|
});
|
|
305
316
|
return config?.storage || dto.location || _filelocationenum.FileLocationEnum.LOCAL;
|
|
306
|
-
} catch
|
|
307
|
-
this.logger.warn(`Failed to resolve storage location: ${_utils.ErrorHandler.getErrorMessage(error)}`);
|
|
317
|
+
} catch {
|
|
308
318
|
return dto.location || _filelocationenum.FileLocationEnum.LOCAL;
|
|
309
319
|
}
|
|
310
320
|
}
|
|
@@ -350,12 +360,10 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
350
360
|
return grouped;
|
|
351
361
|
}
|
|
352
362
|
getFileBaseUrl(protocol, host) {
|
|
353
|
-
|
|
363
|
+
const appUrl = this.storageConfig.getAppUrl()?.replace(/\/$/, '');
|
|
364
|
+
return appUrl || `${protocol}://${host}`;
|
|
354
365
|
}
|
|
355
366
|
async refreshFileUrl(file, protocol, host, now, user) {
|
|
356
|
-
if (!file.storageConfigId) {
|
|
357
|
-
this.logger.warn(`File ${file.id} has no storageConfigId`);
|
|
358
|
-
}
|
|
359
367
|
const isCloudProvider = file.location === _filelocationenum.FileLocationEnum.AWS || file.location === _filelocationenum.FileLocationEnum.AZURE;
|
|
360
368
|
const needsNewUrl = !file.url || isCloudProvider && (typeof file.expiresAt !== 'number' || now >= file.expiresAt);
|
|
361
369
|
if (needsNewUrl && file.storageConfigId) {
|
|
@@ -363,8 +371,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
363
371
|
file.url = await this.uploadService.makeFileUrl(file.key, file.storageConfigId, URL_EXPIRY_SECONDS, user);
|
|
364
372
|
file.expiresAt = now + URL_EXPIRY_SECONDS * 1000;
|
|
365
373
|
return true;
|
|
366
|
-
} catch
|
|
367
|
-
this.logger.error(`Failed to generate URL for ${file.id}: ${_utils.ErrorHandler.getErrorMessage(error)}`);
|
|
374
|
+
} catch {
|
|
368
375
|
file.url = `${this.getFileBaseUrl(protocol, host)}/storage/upload/file/${file.key}`;
|
|
369
376
|
}
|
|
370
377
|
}
|
|
@@ -393,7 +400,7 @@ let FileManagerService = class FileManagerService extends _classes.RequestScoped
|
|
|
393
400
|
};
|
|
394
401
|
}
|
|
395
402
|
constructor(cacheManager, utilsService, uploadService, storageConfig, dataSourceProvider){
|
|
396
|
-
super('file_manager', null, cacheManager, utilsService, FileManagerService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "uploadService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.uploadService = uploadService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
403
|
+
super('file_manager', null, cacheManager, utilsService, FileManagerService.name, true, 'storage'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "uploadService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.uploadService = uploadService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
397
404
|
}
|
|
398
405
|
};
|
|
399
406
|
FileManagerService = _ts_decorate([
|
|
@@ -49,6 +49,7 @@ let FolderService = class FolderService extends _classes.RequestScopedApiService
|
|
|
49
49
|
getDataSourceProvider() {
|
|
50
50
|
return this.dataSourceProvider;
|
|
51
51
|
}
|
|
52
|
+
// ─── Override Methods ───────────────────────────────────────────────────────
|
|
52
53
|
async convertSingleDtoToEntity(dto, user) {
|
|
53
54
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
54
55
|
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
@@ -84,7 +85,7 @@ let FolderService = class FolderService extends _classes.RequestScopedApiService
|
|
|
84
85
|
return result;
|
|
85
86
|
}
|
|
86
87
|
constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
|
|
87
|
-
super('folder', null, cacheManager, utilsService, FolderService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
88
|
+
super('folder', null, cacheManager, utilsService, FolderService.name, true, 'storage'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
FolderService = _ts_decorate([
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "StorageDataSourceProvider", {
|
|
|
10
10
|
});
|
|
11
11
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
12
12
|
const _common = require("@nestjs/common");
|
|
13
|
+
const _constants = require("@flusys/nestjs-shared/constants");
|
|
13
14
|
const _core = require("@nestjs/core");
|
|
14
15
|
const _express = require("express");
|
|
15
16
|
const _storageconfigservice = require("./storage-config.service");
|
|
@@ -141,7 +142,10 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
|
|
|
141
142
|
}
|
|
142
143
|
const config = this.getDefaultDatabaseConfig();
|
|
143
144
|
if (!config) {
|
|
144
|
-
throw new
|
|
145
|
+
throw new _common.InternalServerErrorException({
|
|
146
|
+
message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
|
|
147
|
+
messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
|
|
148
|
+
});
|
|
145
149
|
}
|
|
146
150
|
// Create connection with lock to prevent race conditions
|
|
147
151
|
const connectionPromise = this.createDataSourceFromConfig(config);
|
|
@@ -180,7 +184,7 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
|
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
constructor(configService, request){
|
|
183
|
-
super(StorageDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0),
|
|
187
|
+
super(StorageDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
|
|
184
188
|
}
|
|
185
189
|
};
|
|
186
190
|
// Override parent's static properties to have Storage-specific cache
|
|
@@ -49,6 +49,7 @@ let StorageProviderConfigService = class StorageProviderConfigService extends _c
|
|
|
49
49
|
getDataSourceProvider() {
|
|
50
50
|
return this.dataSourceProvider;
|
|
51
51
|
}
|
|
52
|
+
// ─── Override Methods ───────────────────────────────────────────────────────
|
|
52
53
|
async convertSingleDtoToEntity(dto, user) {
|
|
53
54
|
const entity = await super.convertSingleDtoToEntity(dto, user);
|
|
54
55
|
if (this.storageConfig.isCompanyFeatureEnabled()) {
|
|
@@ -130,7 +131,7 @@ let StorageProviderConfigService = class StorageProviderConfigService extends _c
|
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
constructor(cacheManager, utilsService, storageConfig, dataSourceProvider){
|
|
133
|
-
super('storageConfig', null, cacheManager, utilsService, StorageProviderConfigService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
134
|
+
super('storageConfig', null, cacheManager, utilsService, StorageProviderConfigService.name, true, 'storage'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "storageConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.storageConfig = storageConfig, this.dataSourceProvider = dataSourceProvider;
|
|
134
135
|
}
|
|
135
136
|
};
|
|
136
137
|
StorageProviderConfigService = _ts_decorate([
|
|
@@ -8,9 +8,13 @@ Object.defineProperty(exports, "UploadService", {
|
|
|
8
8
|
return UploadService;
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
+
const _nestjsshared = require("@flusys/nestjs-shared");
|
|
12
|
+
const _config = require("../config");
|
|
13
|
+
const _interfaces = require("@flusys/nestjs-shared/interfaces");
|
|
11
14
|
const _utils = require("@flusys/nestjs-shared/utils");
|
|
12
15
|
const _common = require("@nestjs/common");
|
|
13
16
|
const _storageconfigservice = require("./storage-config.service");
|
|
17
|
+
const _uploaddto = require("../dtos/upload.dto");
|
|
14
18
|
const _filelocationenum = require("../enums/file-location.enum");
|
|
15
19
|
const _storagefactoryservice = require("../providers/storage-factory.service");
|
|
16
20
|
const _storageproviderconfigservice = require("./storage-provider-config.service");
|
|
@@ -50,23 +54,28 @@ let UploadService = class UploadService {
|
|
|
50
54
|
// Validate file size
|
|
51
55
|
const sizeValidation = this.storageConfigService.validateFileSize(file.size);
|
|
52
56
|
if (!sizeValidation.valid) {
|
|
53
|
-
throw new _common.BadRequestException(
|
|
57
|
+
throw new _common.BadRequestException({
|
|
58
|
+
message: sizeValidation.message,
|
|
59
|
+
messageKey: _config.UPLOAD_MESSAGES.FILE_TOO_LARGE
|
|
60
|
+
});
|
|
54
61
|
}
|
|
55
62
|
// Validate declared file type (MIME)
|
|
56
63
|
const typeValidation = this.storageConfigService.validateFileType(file.mimetype);
|
|
57
64
|
if (!typeValidation.valid) {
|
|
58
|
-
throw new _common.BadRequestException(
|
|
65
|
+
throw new _common.BadRequestException({
|
|
66
|
+
message: typeValidation.message,
|
|
67
|
+
messageKey: _config.UPLOAD_MESSAGES.INVALID_TYPE
|
|
68
|
+
});
|
|
59
69
|
}
|
|
60
70
|
// Validate file content matches declared type (magic bytes check)
|
|
61
71
|
// This prevents MIME type spoofing attacks
|
|
62
72
|
const allowedTypes = this.storageConfigService.getAllowedFileTypes();
|
|
63
73
|
const contentValidation = _filevalidatorutil.FileValidator.validateFileContent(file.buffer, file.mimetype, allowedTypes);
|
|
64
74
|
if (!contentValidation.valid) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
throw new _common.BadRequestException({
|
|
76
|
+
message: contentValidation.message || 'File content validation failed',
|
|
77
|
+
messageKey: _config.UPLOAD_MESSAGES.INVALID_TYPE
|
|
68
78
|
});
|
|
69
|
-
throw new _common.BadRequestException(contentValidation.message || 'File content validation failed');
|
|
70
79
|
}
|
|
71
80
|
// Sanitize filename to prevent path traversal attacks
|
|
72
81
|
file.originalname = _filevalidatorutil.FileValidator.sanitizeFilename(file.originalname);
|
|
@@ -82,7 +91,6 @@ let UploadService = class UploadService {
|
|
|
82
91
|
/**
|
|
83
92
|
* Create fallback local provider
|
|
84
93
|
*/ async createFallbackLocalProvider() {
|
|
85
|
-
this.logger.warn('No storage config found, using fallback local provider');
|
|
86
94
|
return this.storageFactory.createProvider({
|
|
87
95
|
provider: _filelocationenum.FileLocationEnum.LOCAL,
|
|
88
96
|
config: {
|
|
@@ -100,7 +108,10 @@ let UploadService = class UploadService {
|
|
|
100
108
|
// Use direct lookup (bypasses company filtering, returns null instead of throwing)
|
|
101
109
|
const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
|
|
102
110
|
if (!config) {
|
|
103
|
-
throw new _common.NotFoundException(
|
|
111
|
+
throw new _common.NotFoundException({
|
|
112
|
+
message: 'Storage configuration not found',
|
|
113
|
+
messageKey: _config.UPLOAD_MESSAGES.CONFIG_NOT_FOUND
|
|
114
|
+
});
|
|
104
115
|
}
|
|
105
116
|
// Validate company ownership using shared utility
|
|
106
117
|
(0, _utils.validateCompanyOwnership)(config, user, this.storageConfigService.isCompanyFeatureEnabled(), 'Storage configuration');
|
|
@@ -109,7 +120,10 @@ let UploadService = class UploadService {
|
|
|
109
120
|
// Use default config (scoped to user's company/branch)
|
|
110
121
|
const defaultConfig = await this.storageProviderConfigService.getDefaultConfig(user);
|
|
111
122
|
if (!defaultConfig) {
|
|
112
|
-
throw new _common.NotFoundException(
|
|
123
|
+
throw new _common.NotFoundException({
|
|
124
|
+
message: 'No default storage configuration found for your company/branch. Please create one.',
|
|
125
|
+
messageKey: _config.UPLOAD_MESSAGES.CONFIG_NOT_FOUND
|
|
126
|
+
});
|
|
113
127
|
}
|
|
114
128
|
storageConfig = defaultConfig;
|
|
115
129
|
}
|
|
@@ -130,7 +144,6 @@ let UploadService = class UploadService {
|
|
|
130
144
|
if (config) {
|
|
131
145
|
return this.createProviderFromConfig(config);
|
|
132
146
|
}
|
|
133
|
-
this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback`);
|
|
134
147
|
}
|
|
135
148
|
// Fallback: Use locationHint to find a matching config
|
|
136
149
|
if (locationHint) {
|
|
@@ -150,62 +163,44 @@ let UploadService = class UploadService {
|
|
|
150
163
|
return this.createFallbackLocalProvider();
|
|
151
164
|
}
|
|
152
165
|
async uploadSingleFile(file, options, user) {
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
this.validateFile(file);
|
|
167
|
+
const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
|
|
168
|
+
const result = await provider.uploadFile(file, options);
|
|
169
|
+
return {
|
|
170
|
+
...result,
|
|
171
|
+
location,
|
|
172
|
+
storageConfigId: configId
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async uploadMultipleFiles(files, options, user) {
|
|
176
|
+
for (const file of files){
|
|
155
177
|
this.validateFile(file);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
178
|
+
}
|
|
179
|
+
const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
|
|
180
|
+
const results = await provider.uploadMultipleFiles(files, options);
|
|
181
|
+
return results.map((result)=>({
|
|
160
182
|
...result,
|
|
161
183
|
location,
|
|
162
184
|
storageConfigId: configId
|
|
163
|
-
};
|
|
164
|
-
} catch (error) {
|
|
165
|
-
_utils.ErrorHandler.logError(this.logger, error, 'uploadSingleFile');
|
|
166
|
-
_utils.ErrorHandler.rethrowError(error);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
async uploadMultipleFiles(files, options, user) {
|
|
170
|
-
try {
|
|
171
|
-
// Validate each file before upload
|
|
172
|
-
for (const file of files){
|
|
173
|
-
this.validateFile(file);
|
|
174
|
-
}
|
|
175
|
-
const { provider, location, configId } = await this.getStorageProviderWithConfig(options.storageConfigId, user);
|
|
176
|
-
const results = await provider.uploadMultipleFiles(files, options);
|
|
177
|
-
// Enrich results with storage info
|
|
178
|
-
return results.map((result)=>({
|
|
179
|
-
...result,
|
|
180
|
-
location,
|
|
181
|
-
storageConfigId: configId
|
|
182
|
-
}));
|
|
183
|
-
} catch (error) {
|
|
184
|
-
_utils.ErrorHandler.logError(this.logger, error, 'uploadMultipleFiles');
|
|
185
|
-
_utils.ErrorHandler.rethrowError(error);
|
|
186
|
-
}
|
|
185
|
+
}));
|
|
187
186
|
}
|
|
188
187
|
async deleteSingleFile(key, storageConfigId, user, locationHint) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
_utils.ErrorHandler.rethrowError(error);
|
|
197
|
-
}
|
|
188
|
+
if (!key) throw new _common.BadRequestException({
|
|
189
|
+
message: 'No file path provided',
|
|
190
|
+
messageKey: _config.UPLOAD_MESSAGES.NO_FILE_PATH
|
|
191
|
+
});
|
|
192
|
+
const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
|
|
193
|
+
await provider.deleteFile(key);
|
|
194
|
+
return true;
|
|
198
195
|
}
|
|
199
196
|
async deleteMultipleFile(keys, storageConfigId, user, locationHint) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
_utils.ErrorHandler.rethrowError(error);
|
|
208
|
-
}
|
|
197
|
+
if (!keys || !keys.length) throw new _common.BadRequestException({
|
|
198
|
+
message: 'No file paths provided',
|
|
199
|
+
messageKey: _config.UPLOAD_MESSAGES.NO_FILE_PATH
|
|
200
|
+
});
|
|
201
|
+
const provider = await this.getStorageProviderForDelete(storageConfigId, user, locationHint);
|
|
202
|
+
await provider.deleteMultipleFiles(keys);
|
|
203
|
+
return true;
|
|
209
204
|
}
|
|
210
205
|
bytesToKb(bytes) {
|
|
211
206
|
return Number((bytes * 0.001).toFixed(2));
|
|
@@ -233,42 +228,97 @@ let UploadService = class UploadService {
|
|
|
233
228
|
return this.storageFactory.getLocalProviderBasePath();
|
|
234
229
|
}
|
|
235
230
|
return null;
|
|
236
|
-
} catch
|
|
237
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
238
|
-
this.logger.warn(`Failed to get local storage basePath: ${errorMessage}`);
|
|
231
|
+
} catch {
|
|
239
232
|
return null;
|
|
240
233
|
}
|
|
241
234
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
|
|
249
|
-
// Check if provider supports presigned URLs
|
|
250
|
-
if (provider.generatePresignedUrl) {
|
|
251
|
-
return await provider.generatePresignedUrl(key, expiresIn);
|
|
252
|
-
}
|
|
253
|
-
// For SFTP or other providers without presigned URLs
|
|
254
|
-
return key;
|
|
255
|
-
} catch (error) {
|
|
256
|
-
_utils.ErrorHandler.logError(this.logger, error, 'makeFileUrl');
|
|
257
|
-
_utils.ErrorHandler.rethrowError(error);
|
|
235
|
+
async makeFileUrl(key, storageConfigId, expiresIn = 3600, user) {
|
|
236
|
+
const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
|
|
237
|
+
// Check if provider supports presigned URLs
|
|
238
|
+
if (provider.generatePresignedUrl) {
|
|
239
|
+
return await provider.generatePresignedUrl(key, expiresIn);
|
|
258
240
|
}
|
|
241
|
+
// For SFTP or other providers without presigned URLs
|
|
242
|
+
return key;
|
|
259
243
|
}
|
|
260
244
|
// NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
|
|
261
245
|
constructor(storageFactory, storageConfigService, storageProviderConfigService){
|
|
262
246
|
_define_property(this, "storageFactory", void 0);
|
|
263
247
|
_define_property(this, "storageConfigService", void 0);
|
|
264
248
|
_define_property(this, "storageProviderConfigService", void 0);
|
|
265
|
-
_define_property(this, "logger", void 0);
|
|
266
249
|
this.storageFactory = storageFactory;
|
|
267
250
|
this.storageConfigService = storageConfigService;
|
|
268
251
|
this.storageProviderConfigService = storageProviderConfigService;
|
|
269
|
-
this.logger = new _common.Logger(UploadService.name);
|
|
270
252
|
}
|
|
271
253
|
};
|
|
254
|
+
_ts_decorate([
|
|
255
|
+
(0, _nestjsshared.LogAction)({
|
|
256
|
+
action: 'storage.uploadSingle',
|
|
257
|
+
module: 'storage'
|
|
258
|
+
}),
|
|
259
|
+
_ts_metadata("design:type", Function),
|
|
260
|
+
_ts_metadata("design:paramtypes", [
|
|
261
|
+
typeof Express === "undefined" || typeof Express.Multer === "undefined" || typeof Express.Multer.File === "undefined" ? Object : Express.Multer.File,
|
|
262
|
+
typeof _uploaddto.UploadOptionsDto === "undefined" ? Object : _uploaddto.UploadOptionsDto,
|
|
263
|
+
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
|
|
264
|
+
]),
|
|
265
|
+
_ts_metadata("design:returntype", Promise)
|
|
266
|
+
], UploadService.prototype, "uploadSingleFile", null);
|
|
267
|
+
_ts_decorate([
|
|
268
|
+
(0, _nestjsshared.LogAction)({
|
|
269
|
+
action: 'storage.uploadMultiple',
|
|
270
|
+
module: 'storage'
|
|
271
|
+
}),
|
|
272
|
+
_ts_metadata("design:type", Function),
|
|
273
|
+
_ts_metadata("design:paramtypes", [
|
|
274
|
+
Array,
|
|
275
|
+
typeof _uploaddto.UploadOptionsDto === "undefined" ? Object : _uploaddto.UploadOptionsDto,
|
|
276
|
+
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
|
|
277
|
+
]),
|
|
278
|
+
_ts_metadata("design:returntype", Promise)
|
|
279
|
+
], UploadService.prototype, "uploadMultipleFiles", null);
|
|
280
|
+
_ts_decorate([
|
|
281
|
+
(0, _nestjsshared.LogAction)({
|
|
282
|
+
action: 'storage.deleteSingle',
|
|
283
|
+
module: 'storage'
|
|
284
|
+
}),
|
|
285
|
+
_ts_metadata("design:type", Function),
|
|
286
|
+
_ts_metadata("design:paramtypes", [
|
|
287
|
+
String,
|
|
288
|
+
String,
|
|
289
|
+
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo,
|
|
290
|
+
String
|
|
291
|
+
]),
|
|
292
|
+
_ts_metadata("design:returntype", Promise)
|
|
293
|
+
], UploadService.prototype, "deleteSingleFile", null);
|
|
294
|
+
_ts_decorate([
|
|
295
|
+
(0, _nestjsshared.LogAction)({
|
|
296
|
+
action: 'storage.deleteMultiple',
|
|
297
|
+
module: 'storage'
|
|
298
|
+
}),
|
|
299
|
+
_ts_metadata("design:type", Function),
|
|
300
|
+
_ts_metadata("design:paramtypes", [
|
|
301
|
+
Array,
|
|
302
|
+
String,
|
|
303
|
+
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo,
|
|
304
|
+
String
|
|
305
|
+
]),
|
|
306
|
+
_ts_metadata("design:returntype", Promise)
|
|
307
|
+
], UploadService.prototype, "deleteMultipleFile", null);
|
|
308
|
+
_ts_decorate([
|
|
309
|
+
(0, _nestjsshared.LogAction)({
|
|
310
|
+
action: 'storage.makeFileUrl',
|
|
311
|
+
module: 'storage'
|
|
312
|
+
}),
|
|
313
|
+
_ts_metadata("design:type", Function),
|
|
314
|
+
_ts_metadata("design:paramtypes", [
|
|
315
|
+
String,
|
|
316
|
+
String,
|
|
317
|
+
Number,
|
|
318
|
+
typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
|
|
319
|
+
]),
|
|
320
|
+
_ts_metadata("design:returntype", Promise)
|
|
321
|
+
], UploadService.prototype, "makeFileUrl", null);
|
|
272
322
|
UploadService = _ts_decorate([
|
|
273
323
|
(0, _common.Injectable)({
|
|
274
324
|
scope: _common.Scope.REQUEST
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Result of file content validation.
|
|
3
|
+
*/ "use strict";
|
|
2
4
|
Object.defineProperty(exports, "__esModule", {
|
|
3
5
|
value: true
|
|
4
6
|
});
|
|
@@ -8,20 +10,6 @@ Object.defineProperty(exports, "FileValidator", {
|
|
|
8
10
|
return FileValidator;
|
|
9
11
|
}
|
|
10
12
|
});
|
|
11
|
-
const _common = require("@nestjs/common");
|
|
12
|
-
function _define_property(obj, key, value) {
|
|
13
|
-
if (key in obj) {
|
|
14
|
-
Object.defineProperty(obj, key, {
|
|
15
|
-
value: value,
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true
|
|
19
|
-
});
|
|
20
|
-
} else {
|
|
21
|
-
obj[key] = value;
|
|
22
|
-
}
|
|
23
|
-
return obj;
|
|
24
|
-
}
|
|
25
13
|
/**
|
|
26
14
|
* Magic byte signatures for common file types.
|
|
27
15
|
* Each entry maps a hex signature pattern to its MIME type.
|
|
@@ -408,7 +396,6 @@ let FileValidator = class FileValidator {
|
|
|
408
396
|
}
|
|
409
397
|
// Verify detected type matches declared type
|
|
410
398
|
if (!this.mimeTypesMatch(detectedType, declaredMimeType)) {
|
|
411
|
-
this.logger.warn(`MIME type mismatch: declared=${declaredMimeType}, detected=${detectedType}`);
|
|
412
399
|
return this.failureResult(`File content does not match declared type. Detected: ${detectedType}, Declared: ${declaredMimeType}`, detectedType, declaredMimeType);
|
|
413
400
|
}
|
|
414
401
|
// Verify type is in allowed list
|
|
@@ -416,8 +403,7 @@ let FileValidator = class FileValidator {
|
|
|
416
403
|
return this.failureResult(`File type "${detectedType}" is not allowed`, detectedType, declaredMimeType);
|
|
417
404
|
}
|
|
418
405
|
return this.successResult(detectedType, declaredMimeType);
|
|
419
|
-
} catch
|
|
420
|
-
this.logger.error('File validation error:', error);
|
|
406
|
+
} catch {
|
|
421
407
|
return this.failureResult('File validation failed');
|
|
422
408
|
}
|
|
423
409
|
}
|
|
@@ -428,10 +414,8 @@ let FileValidator = class FileValidator {
|
|
|
428
414
|
if (this.isDangerousTextType(declaredMimeType)) {
|
|
429
415
|
const explicitlyAllowed = allowedTypes.some((t)=>t === declaredMimeType && t !== '*/*' && !t.endsWith('/*'));
|
|
430
416
|
if (!explicitlyAllowed) {
|
|
431
|
-
this.logger.warn(`Blocked dangerous file type: ${declaredMimeType} - requires explicit allowlisting`);
|
|
432
417
|
return this.failureResult(`File type "${declaredMimeType}" is potentially dangerous and not explicitly allowed`, declaredMimeType, declaredMimeType);
|
|
433
418
|
}
|
|
434
|
-
this.logger.warn(`Allowing explicitly permitted dangerous file type: ${declaredMimeType}`);
|
|
435
419
|
}
|
|
436
420
|
// Safe text-based files don't have magic bytes, trust declared type
|
|
437
421
|
if (this.isTextBasedType(declaredMimeType)) {
|
|
@@ -439,7 +423,6 @@ let FileValidator = class FileValidator {
|
|
|
439
423
|
return isAllowed ? this.successResult(declaredMimeType, declaredMimeType) : this.failureResult(`File type "${declaredMimeType}" is not allowed`, declaredMimeType, declaredMimeType);
|
|
440
424
|
}
|
|
441
425
|
// Binary files without recognized signatures - be cautious
|
|
442
|
-
this.logger.warn(`Unable to detect file type for declared type: ${declaredMimeType}`);
|
|
443
426
|
return this.failureResult('Unable to verify file type. File may be corrupted or unsupported.', undefined, declaredMimeType);
|
|
444
427
|
}
|
|
445
428
|
/**
|
|
@@ -455,4 +438,3 @@ let FileValidator = class FileValidator {
|
|
|
455
438
|
.substring(0, 255);
|
|
456
439
|
}
|
|
457
440
|
};
|
|
458
|
-
_define_property(FileValidator, "logger", new _common.Logger(FileValidator.name));
|