@flusys/nestjs-storage 1.0.0-rc → 1.0.1

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 (103) hide show
  1. package/README.md +44 -1
  2. package/cjs/config/index.js +0 -1
  3. package/cjs/config/storage.constants.js +0 -9
  4. package/cjs/controllers/upload.controller.js +12 -17
  5. package/cjs/docs/storage-swagger.config.js +24 -136
  6. package/cjs/dtos/file-manager.dto.js +65 -32
  7. package/cjs/dtos/folder.dto.js +15 -9
  8. package/cjs/dtos/storage-config.dto.js +5 -86
  9. package/cjs/dtos/upload.dto.js +17 -17
  10. package/cjs/entities/file-manager-with-company.entity.js +3 -4
  11. package/cjs/entities/file-manager.entity.js +71 -3
  12. package/cjs/entities/folder-with-company.entity.js +3 -4
  13. package/cjs/entities/folder.entity.js +19 -3
  14. package/cjs/entities/index.js +9 -10
  15. package/cjs/entities/storage-config-with-company.entity.js +3 -4
  16. package/cjs/entities/storage-config.entity.js +73 -3
  17. package/cjs/middlewares/file-serve.middleware.js +107 -100
  18. package/cjs/modules/storage.module.js +82 -136
  19. package/cjs/providers/azure-provider.optional.js +10 -38
  20. package/cjs/providers/local-provider.js +0 -43
  21. package/cjs/providers/s3-provider.optional.js +19 -40
  22. package/cjs/providers/storage-factory.service.js +54 -99
  23. package/cjs/providers/storage-provider.registry.js +8 -18
  24. package/cjs/services/file-manager.service.js +239 -337
  25. package/cjs/services/folder.service.js +3 -3
  26. package/cjs/services/index.js +1 -0
  27. package/cjs/{config → services}/storage-config.service.js +30 -79
  28. package/cjs/services/storage-datasource.provider.js +16 -26
  29. package/cjs/services/storage-provider-config.service.js +3 -3
  30. package/cjs/services/upload.service.js +33 -61
  31. package/cjs/utils/file-validator.util.js +54 -66
  32. package/cjs/utils/image-compressor.util.js +2 -5
  33. package/config/index.d.ts +0 -1
  34. package/config/storage.constants.d.ts +0 -6
  35. package/controllers/upload.controller.d.ts +1 -0
  36. package/dtos/file-manager.dto.d.ts +11 -3
  37. package/dtos/folder.dto.d.ts +3 -1
  38. package/dtos/storage-config.dto.d.ts +7 -11
  39. package/entities/file-manager-with-company.entity.d.ts +2 -2
  40. package/entities/file-manager.entity.d.ts +11 -2
  41. package/entities/folder-with-company.entity.d.ts +2 -2
  42. package/entities/folder.entity.d.ts +4 -2
  43. package/entities/index.d.ts +3 -4
  44. package/entities/storage-config-with-company.entity.d.ts +2 -2
  45. package/entities/storage-config.entity.d.ts +7 -2
  46. package/fesm/config/index.js +0 -1
  47. package/fesm/config/storage.constants.js +0 -6
  48. package/fesm/controllers/upload.controller.js +12 -17
  49. package/fesm/docs/storage-swagger.config.js +27 -142
  50. package/fesm/dtos/file-manager.dto.js +66 -33
  51. package/fesm/dtos/folder.dto.js +16 -10
  52. package/fesm/dtos/storage-config.dto.js +7 -88
  53. package/fesm/dtos/upload.dto.js +17 -18
  54. package/fesm/entities/file-manager-with-company.entity.js +3 -4
  55. package/fesm/entities/file-manager.entity.js +72 -4
  56. package/fesm/entities/folder-with-company.entity.js +3 -4
  57. package/fesm/entities/folder.entity.js +20 -4
  58. package/fesm/entities/index.js +4 -8
  59. package/fesm/entities/storage-config-with-company.entity.js +3 -4
  60. package/fesm/entities/storage-config.entity.js +74 -4
  61. package/fesm/middlewares/file-serve.middleware.js +107 -100
  62. package/fesm/modules/storage.module.js +83 -136
  63. package/fesm/providers/azure-provider.optional.js +14 -45
  64. package/fesm/providers/local-provider.js +0 -43
  65. package/fesm/providers/s3-provider.optional.js +23 -47
  66. package/fesm/providers/storage-factory.service.js +52 -97
  67. package/fesm/providers/storage-provider.registry.js +10 -20
  68. package/fesm/services/file-manager.service.js +237 -335
  69. package/fesm/services/folder.service.js +1 -1
  70. package/fesm/services/index.js +1 -0
  71. package/fesm/{config → services}/storage-config.service.js +30 -79
  72. package/fesm/services/storage-datasource.provider.js +16 -26
  73. package/fesm/services/storage-provider-config.service.js +1 -1
  74. package/fesm/services/upload.service.js +31 -59
  75. package/fesm/utils/file-validator.util.js +54 -66
  76. package/fesm/utils/image-compressor.util.js +2 -5
  77. package/interfaces/storage-config.interface.d.ts +1 -2
  78. package/interfaces/storage-module-options.interface.d.ts +0 -5
  79. package/middlewares/file-serve.middleware.d.ts +9 -1
  80. package/modules/storage.module.d.ts +1 -2
  81. package/package.json +3 -3
  82. package/providers/azure-provider.optional.d.ts +8 -6
  83. package/providers/local-provider.d.ts +0 -7
  84. package/providers/s3-provider.optional.d.ts +9 -7
  85. package/providers/storage-factory.service.d.ts +8 -9
  86. package/providers/storage-provider.registry.d.ts +4 -4
  87. package/services/file-manager.service.d.ts +21 -14
  88. package/services/folder.service.d.ts +4 -4
  89. package/services/index.d.ts +1 -0
  90. package/{config → services}/storage-config.service.d.ts +9 -10
  91. package/services/storage-datasource.provider.d.ts +3 -4
  92. package/services/storage-provider-config.service.d.ts +5 -6
  93. package/services/upload.service.d.ts +5 -5
  94. package/utils/file-validator.util.d.ts +3 -0
  95. package/cjs/entities/file-manager-base.entity.js +0 -115
  96. package/cjs/entities/folder-base.entity.js +0 -55
  97. package/cjs/entities/storage-config-base.entity.js +0 -93
  98. package/entities/file-manager-base.entity.d.ts +0 -13
  99. package/entities/folder-base.entity.d.ts +0 -5
  100. package/entities/storage-config-base.entity.d.ts +0 -9
  101. package/fesm/entities/file-manager-base.entity.js +0 -108
  102. package/fesm/entities/folder-base.entity.js +0 -48
  103. package/fesm/entities/storage-config-base.entity.js +0 -83
@@ -12,7 +12,7 @@ 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
14
  const _common = require("@nestjs/common");
15
- const _config = require("../config");
15
+ const _storageconfigservice = require("./storage-config.service");
16
16
  const _entities = require("../entities");
17
17
  const _storagedatasourceprovider = require("./storage-datasource.provider");
18
18
  function _define_property(obj, key, value) {
@@ -93,13 +93,13 @@ FolderService = _ts_decorate([
93
93
  }),
94
94
  _ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
95
95
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
96
- _ts_param(2, (0, _common.Inject)(_config.StorageConfigService)),
96
+ _ts_param(2, (0, _common.Inject)(_storageconfigservice.StorageConfigService)),
97
97
  _ts_param(3, (0, _common.Inject)(_storagedatasourceprovider.StorageDataSourceProvider)),
98
98
  _ts_metadata("design:type", Function),
99
99
  _ts_metadata("design:paramtypes", [
100
100
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
101
101
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
102
- typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService,
102
+ typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
103
103
  typeof _storagedatasourceprovider.StorageDataSourceProvider === "undefined" ? Object : _storagedatasourceprovider.StorageDataSourceProvider
104
104
  ])
105
105
  ], FolderService);
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  });
5
5
  _export_star(require("./file-manager.service"), exports);
6
6
  _export_star(require("./folder.service"), exports);
7
+ _export_star(require("./storage-config.service"), exports);
7
8
  _export_star(require("./storage-provider-config.service"), exports);
8
9
  _export_star(require("./storage-datasource.provider"), exports);
9
10
  _export_star(require("./upload.service"), exports);
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "StorageConfigService", {
10
10
  });
11
11
  const _common = require("@nestjs/common");
12
12
  const _interfaces = require("../interfaces");
13
- const _storageconstants = require("./storage.constants");
13
+ const _storageconstants = require("../config/storage.constants");
14
14
  function _define_property(obj, key, value) {
15
15
  if (key in obj) {
16
16
  Object.defineProperty(obj, key, {
@@ -38,119 +38,70 @@ function _ts_param(paramIndex, decorator) {
38
38
  decorator(target, key, paramIndex);
39
39
  };
40
40
  }
41
+ const BYTES_PER_MB = 1024 * 1024;
42
+ const DEFAULT_LOCAL_PATH = './uploads';
43
+ const DEFAULT_PORT = '3000';
41
44
  let StorageConfigService = class StorageConfigService {
42
- /**
43
- * Check if company feature is enabled
44
- */ isCompanyFeatureEnabled() {
45
+ // ─── IModuleConfigService Implementation ────────────────────────────────────
46
+ isCompanyFeatureEnabled() {
45
47
  return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
46
48
  }
47
- /**
48
- * Get database mode
49
- */ getDatabaseMode() {
49
+ getDatabaseMode() {
50
50
  return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
51
51
  }
52
- /**
53
- * Check if running in multi-tenant mode
54
- */ isMultiTenant() {
52
+ isMultiTenant() {
55
53
  return this.getDatabaseMode() === 'multi-tenant';
56
54
  }
57
- /**
58
- * Get maximum file size in bytes
59
- */ getMaxFileSize() {
55
+ // ─── Config Getters ─────────────────────────────────────────────────────────
56
+ getMaxFileSize() {
60
57
  return this.options.config?.maxFileSize ?? _storageconstants.DEFAULT_MAX_FILE_SIZE;
61
58
  }
62
- /**
63
- * Get allowed file types (MIME types or patterns)
64
- */ getAllowedFileTypes() {
59
+ getAllowedFileTypes() {
65
60
  return this.options.config?.allowedFileTypes ?? _storageconstants.DEFAULT_ALLOWED_FILE_TYPES;
66
61
  }
67
- /**
68
- * Check if file type is allowed
69
- */ isFileTypeAllowed(mimeType) {
62
+ getOptions() {
63
+ return this.options;
64
+ }
65
+ getDefaultLocalStoragePath() {
66
+ return this.options.config?.localStoragePath ?? DEFAULT_LOCAL_PATH;
67
+ }
68
+ getAppUrl() {
69
+ return this.options.config?.appUrl ?? process.env.APP_URL ?? `http://localhost:${process.env.PORT ?? DEFAULT_PORT}`;
70
+ }
71
+ // ─── Validation Methods ─────────────────────────────────────────────────────
72
+ isFileTypeAllowed(mimeType) {
70
73
  const allowedTypes = this.getAllowedFileTypes();
71
- // If wildcard, allow all
72
74
  if (allowedTypes.includes('*/*')) {
73
75
  return true;
74
76
  }
75
- // Check exact match or wildcard pattern
76
- return allowedTypes.some((allowedType)=>{
77
- if (allowedType.endsWith('/*')) {
78
- const prefix = allowedType.slice(0, -2);
79
- return mimeType.startsWith(prefix);
80
- }
81
- return allowedType === mimeType;
82
- });
77
+ return allowedTypes.some((type)=>type.endsWith('/*') ? mimeType.startsWith(type.slice(0, -2)) : type === mimeType);
83
78
  }
84
- /**
85
- * Validate file size
86
- */ validateFileSize(fileSize) {
79
+ validateFileSize(fileSize) {
87
80
  const maxSize = this.getMaxFileSize();
88
81
  if (fileSize > maxSize) {
89
- const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);
90
- const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
91
82
  return {
92
83
  valid: false,
93
- message: `File size (${fileSizeMB}MB) exceeds the maximum allowed size of ${maxSizeMB}MB`
84
+ message: `File size (${this.toMB(fileSize)}MB) exceeds maximum ${this.toMB(maxSize)}MB`
94
85
  };
95
86
  }
96
87
  return {
97
88
  valid: true
98
89
  };
99
90
  }
100
- /**
101
- * Validate file type
102
- */ validateFileType(mimeType) {
91
+ validateFileType(mimeType) {
103
92
  if (!this.isFileTypeAllowed(mimeType)) {
104
- const allowedTypes = this.getAllowedFileTypes();
105
93
  return {
106
94
  valid: false,
107
- message: `File type "${mimeType}" is not allowed. Allowed types: ${allowedTypes.join(', ')}`
95
+ message: `File type "${mimeType}" not allowed. Allowed: ${this.getAllowedFileTypes().join(', ')}`
108
96
  };
109
97
  }
110
98
  return {
111
99
  valid: true
112
100
  };
113
101
  }
114
- /**
115
- * Get default database config
116
- */ getDefaultDatabaseConfig() {
117
- return this.options.config?.defaultDatabaseConfig;
118
- }
119
- /**
120
- * Get all options
121
- */ getOptions() {
122
- return this.options;
123
- }
124
- /**
125
- * Get default local storage base path
126
- * Used for serving local files without database lookup
127
- * Falls back to './uploads' if not configured
128
- */ getDefaultLocalStoragePath() {
129
- // Check if localStoragePath is configured in module options
130
- const configuredPath = this.options.config?.localStoragePath;
131
- if (configuredPath) {
132
- return configuredPath;
133
- }
134
- // Default to ./uploads in the project root
135
- return './uploads';
136
- }
137
- /**
138
- * Get application base URL for generating file URLs
139
- * - First tries module config
140
- * - Falls back to APP_URL env var
141
- * - Finally constructs from PORT env var
142
- */ getAppUrl() {
143
- // Try from module config first
144
- if (this.options.config?.appUrl) {
145
- return this.options.config.appUrl;
146
- }
147
- // Fallback: read directly from environment
148
- if (process.env.APP_URL) {
149
- return process.env.APP_URL;
150
- }
151
- // Last resort: construct from PORT
152
- const port = process.env.PORT || '3000';
153
- return `http://localhost:${port}`;
102
+ // ─── Private Helpers ────────────────────────────────────────────────────────
103
+ toMB(bytes) {
104
+ return (bytes / BYTES_PER_MB).toFixed(2);
154
105
  }
155
106
  constructor(options){
156
107
  _define_property(this, "options", void 0);
@@ -12,8 +12,7 @@ const _modules = require("@flusys/nestjs-shared/modules");
12
12
  const _common = require("@nestjs/common");
13
13
  const _core = require("@nestjs/core");
14
14
  const _express = require("express");
15
- const _interfaces = require("../interfaces");
16
- const _storageconstants = require("../config/storage.constants");
15
+ const _storageconfigservice = require("./storage-config.service");
17
16
  function _define_property(obj, key, value) {
18
17
  if (key in obj) {
19
18
  Object.defineProperty(obj, key, {
@@ -93,14 +92,11 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
93
92
  };
94
93
  }
95
94
  // Feature Flags
96
- /** Get global enable company feature flag */ getEnableCompanyFeature() {
97
- return this.storageOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
98
- }
99
95
  /**
100
96
  * Get enable company feature for specific tenant
101
97
  * Falls back to global setting if not specified per-tenant
102
98
  */ getEnableCompanyFeatureForTenant(tenant) {
103
- return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
99
+ return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
104
100
  }
105
101
  /**
106
102
  * Get enable company feature for current request context
@@ -113,22 +109,16 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
113
109
  * For TypeORM repositories, we always use the base entities (FileManager, etc.)
114
110
  * but for migrations, we need the correct entity based on the feature flag.
115
111
  */ async getStorageEntities(enableCompanyFeature) {
116
- const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
117
- const { FileManager, Folder, StorageConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
118
- // For migrations and schema sync, return the appropriate entity
119
- // Both versions map to the same table, but with different columns
120
- if (enable) {
121
- const { FileManagerWithCompany, FolderWithCompany, StorageConfigWithCompany } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
122
- return [
123
- FileManagerWithCompany,
124
- FolderWithCompany,
125
- StorageConfigWithCompany
126
- ];
127
- }
128
- return [
129
- FileManager,
130
- Folder,
131
- StorageConfig
112
+ const enable = enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
113
+ const entities = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
114
+ return enable ? [
115
+ entities.FileManagerWithCompany,
116
+ entities.FolderWithCompany,
117
+ entities.StorageConfigWithCompany
118
+ ] : [
119
+ entities.FileManager,
120
+ entities.Folder,
121
+ entities.StorageConfig
132
122
  ];
133
123
  }
134
124
  // Overrides
@@ -189,8 +179,8 @@ let StorageDataSourceProvider = class StorageDataSourceProvider extends _modules
189
179
  StorageDataSourceProvider.connectionLocks.delete(tenant.id);
190
180
  }
191
181
  }
192
- constructor(storageOptions, request){
193
- super(StorageDataSourceProvider.buildParentOptions(storageOptions), request), _define_property(this, "storageOptions", void 0), _define_property(this, "logger", void 0), this.storageOptions = storageOptions, this.logger = new _common.Logger(StorageDataSourceProvider.name);
182
+ constructor(configService, request){
183
+ super(StorageDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(StorageDataSourceProvider.name);
194
184
  }
195
185
  };
196
186
  // Override parent's static properties to have Storage-specific cache
@@ -204,12 +194,12 @@ StorageDataSourceProvider = _ts_decorate([
204
194
  (0, _common.Injectable)({
205
195
  scope: _common.Scope.REQUEST
206
196
  }),
207
- _ts_param(0, (0, _common.Inject)(_storageconstants.STORAGE_MODULE_OPTIONS)),
197
+ _ts_param(0, (0, _common.Inject)(_storageconfigservice.StorageConfigService)),
208
198
  _ts_param(1, (0, _common.Optional)()),
209
199
  _ts_param(1, (0, _common.Inject)(_core.REQUEST)),
210
200
  _ts_metadata("design:type", Function),
211
201
  _ts_metadata("design:paramtypes", [
212
- typeof _interfaces.StorageModuleOptions === "undefined" ? Object : _interfaces.StorageModuleOptions,
202
+ typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
213
203
  typeof _express.Request === "undefined" ? Object : _express.Request
214
204
  ])
215
205
  ], StorageDataSourceProvider);
@@ -12,7 +12,7 @@ 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
14
  const _common = require("@nestjs/common");
15
- const _config = require("../config");
15
+ const _storageconfigservice = require("./storage-config.service");
16
16
  const _entities = require("../entities");
17
17
  const _storagedatasourceprovider = require("./storage-datasource.provider");
18
18
  function _define_property(obj, key, value) {
@@ -139,13 +139,13 @@ StorageProviderConfigService = _ts_decorate([
139
139
  }),
140
140
  _ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
141
141
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
142
- _ts_param(2, (0, _common.Inject)(_config.StorageConfigService)),
142
+ _ts_param(2, (0, _common.Inject)(_storageconfigservice.StorageConfigService)),
143
143
  _ts_param(3, (0, _common.Inject)(_storagedatasourceprovider.StorageDataSourceProvider)),
144
144
  _ts_metadata("design:type", Function),
145
145
  _ts_metadata("design:paramtypes", [
146
146
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
147
147
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
148
- typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService,
148
+ typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
149
149
  typeof _storagedatasourceprovider.StorageDataSourceProvider === "undefined" ? Object : _storagedatasourceprovider.StorageDataSourceProvider
150
150
  ])
151
151
  ], StorageProviderConfigService);
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "UploadService", {
10
10
  });
11
11
  const _utils = require("@flusys/nestjs-shared/utils");
12
12
  const _common = require("@nestjs/common");
13
- const _config = require("../config");
13
+ const _storageconfigservice = require("./storage-config.service");
14
14
  const _filelocationenum = require("../enums/file-location.enum");
15
15
  const _storagefactoryservice = require("../providers/storage-factory.service");
16
16
  const _storageproviderconfigservice = require("./storage-provider-config.service");
@@ -72,6 +72,25 @@ let UploadService = class UploadService {
72
72
  file.originalname = _filevalidatorutil.FileValidator.sanitizeFilename(file.originalname);
73
73
  }
74
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
+ });
92
+ }
93
+ /**
75
94
  * Get storage provider and config info based on storage config ID
76
95
  * Validates company ownership when company feature is enabled
77
96
  */ async getStorageProviderWithConfig(storageConfigId, user) {
@@ -94,13 +113,7 @@ let UploadService = class UploadService {
94
113
  }
95
114
  storageConfig = defaultConfig;
96
115
  }
97
- // Create provider config
98
- const providerConfig = {
99
- provider: storageConfig.storage,
100
- config: storageConfig.config
101
- };
102
- // Get or create provider instance
103
- const provider = await this.storageFactory.createProvider(providerConfig);
116
+ const provider = await this.createProviderFromConfig(storageConfig);
104
117
  return {
105
118
  provider,
106
119
  location: storageConfig.storage,
@@ -108,74 +121,33 @@ let UploadService = class UploadService {
108
121
  };
109
122
  }
110
123
  /**
111
- * Get storage provider based on storage config ID (convenience method)
112
- */ async getStorageProvider(storageConfigId, user) {
113
- const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
114
- return provider;
115
- }
116
- /**
117
124
  * Get storage provider for delete operations with fallback
118
125
  * If the original storage config doesn't exist, falls back based on locationHint
119
- * This ensures files can still be deleted even if storage config was removed
120
- * @param storageConfigId - The storage config ID to look up
121
- * @param user - User context for company filtering
122
- * @param locationHint - The file's original location type (used for fallback)
123
126
  */ async getStorageProviderForDelete(storageConfigId, user, locationHint) {
124
- // If storageConfigId provided, try to find it
127
+ // Try to find by storageConfigId
125
128
  if (storageConfigId) {
126
129
  const config = await this.storageProviderConfigService.findByIdDirect(storageConfigId);
127
130
  if (config) {
128
- const providerConfig = {
129
- provider: config.storage,
130
- config: config.config
131
- };
132
- return await this.storageFactory.createProvider(providerConfig);
131
+ return this.createProviderFromConfig(config);
133
132
  }
134
- // Config not found, log warning and try fallback
135
- this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback for delete`);
133
+ this.logger.warn(`Storage config ${storageConfigId} not found, trying fallback`);
136
134
  }
137
- // Fallback: Use locationHint to find a matching config or create provider
135
+ // Fallback: Use locationHint to find a matching config
138
136
  if (locationHint) {
139
- // Try to find a config matching the file's original location type
140
137
  const matchingConfigs = await this.storageProviderConfigService.getConfigByType(locationHint, user);
141
138
  if (matchingConfigs.length > 0) {
142
- const providerConfig = {
143
- provider: matchingConfigs[0].storage,
144
- config: matchingConfigs[0].config
145
- };
146
- this.logger.debug(`Using matching ${locationHint} config for delete fallback`);
147
- return await this.storageFactory.createProvider(providerConfig);
139
+ return this.createProviderFromConfig(matchingConfigs[0]);
148
140
  }
149
- // No matching config found, create a basic provider based on locationHint
150
141
  if (locationHint === _filelocationenum.FileLocationEnum.LOCAL) {
151
- this.logger.warn('No local config found, using fallback local provider for delete');
152
- const localProviderConfig = {
153
- provider: _filelocationenum.FileLocationEnum.LOCAL,
154
- config: {
155
- basePath: this.storageConfigService.getDefaultLocalStoragePath()
156
- }
157
- };
158
- return await this.storageFactory.createProvider(localProviderConfig);
142
+ return this.createFallbackLocalProvider();
159
143
  }
160
144
  }
161
- // Last resort: Try default config (might be different provider type)
145
+ // Last resort: Try default config
162
146
  const defaultConfig = await this.storageProviderConfigService.getDefaultConfig(user);
163
147
  if (defaultConfig) {
164
- const providerConfig = {
165
- provider: defaultConfig.storage,
166
- config: defaultConfig.config
167
- };
168
- return await this.storageFactory.createProvider(providerConfig);
148
+ return this.createProviderFromConfig(defaultConfig);
169
149
  }
170
- // Final fallback: Create a basic local provider
171
- this.logger.warn('No storage config found, using fallback local provider for delete');
172
- const localProviderConfig = {
173
- provider: _filelocationenum.FileLocationEnum.LOCAL,
174
- config: {
175
- basePath: this.storageConfigService.getDefaultLocalStoragePath()
176
- }
177
- };
178
- return await this.storageFactory.createProvider(localProviderConfig);
150
+ return this.createFallbackLocalProvider();
179
151
  }
180
152
  async uploadSingleFile(file, options, user) {
181
153
  try {
@@ -273,7 +245,7 @@ let UploadService = class UploadService {
273
245
  * For Local/SFTP: returns direct path
274
246
  */ async makeFileUrl(key, storageConfigId, expiresIn = 3600, user) {
275
247
  try {
276
- const provider = await this.getStorageProvider(storageConfigId, user);
248
+ const { provider } = await this.getStorageProviderWithConfig(storageConfigId, user);
277
249
  // Check if provider supports presigned URLs
278
250
  if (provider.generatePresignedUrl) {
279
251
  return await provider.generatePresignedUrl(key, expiresIn);
@@ -302,12 +274,12 @@ UploadService = _ts_decorate([
302
274
  scope: _common.Scope.REQUEST
303
275
  }),
304
276
  _ts_param(0, (0, _common.Inject)(_storagefactoryservice.StorageFactoryService)),
305
- _ts_param(1, (0, _common.Inject)(_config.StorageConfigService)),
277
+ _ts_param(1, (0, _common.Inject)(_storageconfigservice.StorageConfigService)),
306
278
  _ts_param(2, (0, _common.Inject)(_storageproviderconfigservice.StorageProviderConfigService)),
307
279
  _ts_metadata("design:type", Function),
308
280
  _ts_metadata("design:paramtypes", [
309
281
  typeof _storagefactoryservice.StorageFactoryService === "undefined" ? Object : _storagefactoryservice.StorageFactoryService,
310
- typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService,
282
+ typeof _storageconfigservice.StorageConfigService === "undefined" ? Object : _storageconfigservice.StorageConfigService,
311
283
  typeof _storageproviderconfigservice.StorageProviderConfigService === "undefined" ? Object : _storageproviderconfigservice.StorageProviderConfigService
312
284
  ])
313
285
  ], UploadService);
@@ -303,7 +303,30 @@ function _define_property(obj, key, value) {
303
303
  'image/svg+xml',
304
304
  'application/xhtml+xml'
305
305
  ];
306
+ /**
307
+ * ZIP-based format prefixes that are valid when detected as application/zip.
308
+ */ const ZIP_VARIANT_PREFIXES = [
309
+ 'application/vnd.openxmlformats-officedocument',
310
+ 'application/x-zip',
311
+ 'application/x-compressed'
312
+ ];
306
313
  let FileValidator = class FileValidator {
314
+ // Result Helpers
315
+ static failureResult(message, detectedType, declaredType) {
316
+ return {
317
+ valid: false,
318
+ detectedType,
319
+ declaredType,
320
+ message
321
+ };
322
+ }
323
+ static successResult(detectedType, declaredType) {
324
+ return {
325
+ valid: true,
326
+ detectedType,
327
+ declaredType
328
+ };
329
+ }
307
330
  /**
308
331
  * Detect file type from buffer using magic bytes.
309
332
  * @param buffer - File buffer to analyze
@@ -349,15 +372,8 @@ let FileValidator = class FileValidator {
349
372
  const detectedCategory = detected.split('/')[0];
350
373
  const declaredCategory = declared.split('/')[0];
351
374
  // For ZIP-based formats, allow any ZIP-detected file if declared is a ZIP variant
352
- if (detected === 'application/zip') {
353
- const zipVariants = [
354
- 'application/vnd.openxmlformats-officedocument',
355
- 'application/x-zip',
356
- 'application/x-compressed'
357
- ];
358
- if (zipVariants.some((v)=>declared.startsWith(v))) {
359
- return true;
360
- }
375
+ if (detected === 'application/zip' && ZIP_VARIANT_PREFIXES.some((v)=>declared.startsWith(v))) {
376
+ return true;
361
377
  }
362
378
  return detectedCategory === declaredCategory;
363
379
  }
@@ -385,74 +401,46 @@ let FileValidator = class FileValidator {
385
401
  '*/*'
386
402
  ]) {
387
403
  try {
388
- // Detect actual file type from magic bytes
389
404
  const detectedType = this.detectFileType(buffer);
390
- // If no type detected, check if it's a text-based type
405
+ // No magic bytes detected - handle text-based types
391
406
  if (!detectedType) {
392
- // Check for dangerous text types first (HTML, JS, SVG)
393
- if (this.isDangerousTextType(declaredMimeType)) {
394
- // Only allow dangerous types if explicitly in allowedTypes (not via wildcard)
395
- const explicitlyAllowed = allowedTypes.some((t)=>t === declaredMimeType && t !== '*/*' && !t.endsWith('/*'));
396
- if (!explicitlyAllowed) {
397
- this.logger.warn(`Blocked dangerous file type: ${declaredMimeType} - requires explicit allowlisting`);
398
- return {
399
- valid: false,
400
- detectedType: declaredMimeType,
401
- declaredType: declaredMimeType,
402
- message: `File type "${declaredMimeType}" is potentially dangerous and not explicitly allowed`
403
- };
404
- }
405
- this.logger.warn(`Allowing explicitly permitted dangerous file type: ${declaredMimeType}`);
406
- }
407
- if (this.isTextBasedType(declaredMimeType)) {
408
- // Safe text-based files don't have magic bytes, trust the declared type
409
- const isAllowed = this.isTypeAllowed(declaredMimeType, allowedTypes);
410
- return {
411
- valid: isAllowed,
412
- detectedType: declaredMimeType,
413
- declaredType: declaredMimeType,
414
- message: isAllowed ? undefined : `File type "${declaredMimeType}" is not allowed`
415
- };
416
- }
417
- // For binary files without recognized signatures, be cautious
418
- this.logger.warn(`Unable to detect file type for declared type: ${declaredMimeType}`);
419
- return {
420
- valid: false,
421
- declaredType: declaredMimeType,
422
- message: 'Unable to verify file type. File may be corrupted or unsupported.'
423
- };
407
+ return this.validateUndetectedType(declaredMimeType, allowedTypes);
424
408
  }
425
- // Check if detected type matches declared type
409
+ // Verify detected type matches declared type
426
410
  if (!this.mimeTypesMatch(detectedType, declaredMimeType)) {
427
411
  this.logger.warn(`MIME type mismatch: declared=${declaredMimeType}, detected=${detectedType}`);
428
- return {
429
- valid: false,
430
- detectedType,
431
- declaredType: declaredMimeType,
432
- message: `File content does not match declared type. Detected: ${detectedType}, Declared: ${declaredMimeType}`
433
- };
412
+ return this.failureResult(`File content does not match declared type. Detected: ${detectedType}, Declared: ${declaredMimeType}`, detectedType, declaredMimeType);
434
413
  }
435
- // Check if detected type is allowed
414
+ // Verify type is in allowed list
436
415
  if (!this.isTypeAllowed(detectedType, allowedTypes)) {
437
- return {
438
- valid: false,
439
- detectedType,
440
- declaredType: declaredMimeType,
441
- message: `File type "${detectedType}" is not allowed`
442
- };
416
+ return this.failureResult(`File type "${detectedType}" is not allowed`, detectedType, declaredMimeType);
443
417
  }
444
- return {
445
- valid: true,
446
- detectedType,
447
- declaredType: declaredMimeType
448
- };
418
+ return this.successResult(detectedType, declaredMimeType);
449
419
  } catch (error) {
450
420
  this.logger.error('File validation error:', error);
451
- return {
452
- valid: false,
453
- message: 'File validation failed'
454
- };
421
+ return this.failureResult('File validation failed');
422
+ }
423
+ }
424
+ /**
425
+ * Handle validation for files without detectable magic bytes.
426
+ */ static validateUndetectedType(declaredMimeType, allowedTypes) {
427
+ // Check for dangerous text types first (HTML, JS, SVG)
428
+ if (this.isDangerousTextType(declaredMimeType)) {
429
+ const explicitlyAllowed = allowedTypes.some((t)=>t === declaredMimeType && t !== '*/*' && !t.endsWith('/*'));
430
+ if (!explicitlyAllowed) {
431
+ this.logger.warn(`Blocked dangerous file type: ${declaredMimeType} - requires explicit allowlisting`);
432
+ return this.failureResult(`File type "${declaredMimeType}" is potentially dangerous and not explicitly allowed`, declaredMimeType, declaredMimeType);
433
+ }
434
+ this.logger.warn(`Allowing explicitly permitted dangerous file type: ${declaredMimeType}`);
435
+ }
436
+ // Safe text-based files don't have magic bytes, trust declared type
437
+ if (this.isTextBasedType(declaredMimeType)) {
438
+ const isAllowed = this.isTypeAllowed(declaredMimeType, allowedTypes);
439
+ return isAllowed ? this.successResult(declaredMimeType, declaredMimeType) : this.failureResult(`File type "${declaredMimeType}" is not allowed`, declaredMimeType, declaredMimeType);
455
440
  }
441
+ // Binary files without recognized signatures - be cautious
442
+ this.logger.warn(`Unable to detect file type for declared type: ${declaredMimeType}`);
443
+ return this.failureResult('Unable to verify file type. File may be corrupted or unsupported.', undefined, declaredMimeType);
456
444
  }
457
445
  /**
458
446
  * Sanitize filename to prevent path traversal and special character issues.
@@ -147,12 +147,9 @@ let ImageCompressor = class ImageCompressor {
147
147
  break;
148
148
  }
149
149
  try {
150
- // Process main image
151
- const { data, info } = await image.toBuffer({
152
- resolveWithObject: true
153
- });
150
+ const compressedBuffer = await image.toBuffer();
154
151
  return {
155
- buffer: data,
152
+ buffer: compressedBuffer,
156
153
  format: `image/${targetFormat}`
157
154
  };
158
155
  } catch {
package/config/index.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export * from './storage.constants';
2
- export * from './storage-config.service';
@@ -1,9 +1,3 @@
1
1
  export declare const STORAGE_MODULE_OPTIONS = "STORAGE_MODULE_OPTIONS";
2
2
  export declare const DEFAULT_MAX_FILE_SIZE: number;
3
3
  export declare const DEFAULT_ALLOWED_FILE_TYPES: string[];
4
- export declare const FILE_VALIDATION_MESSAGES: {
5
- readonly FILE_TOO_LARGE: "File size exceeds the maximum allowed size";
6
- readonly INVALID_FILE_TYPE: "File type is not allowed";
7
- readonly NO_FILE_PROVIDED: "No file was provided";
8
- readonly UPLOAD_FAILED: "File upload failed";
9
- };
@@ -5,6 +5,7 @@ import { UploadService } from '../services/upload.service';
5
5
  export declare class UploadController {
6
6
  private readonly uploadService;
7
7
  constructor(uploadService: UploadService);
8
+ private toFileUploadResponse;
8
9
  uploadSingleFile(file: Express.Multer.File, options: UploadOptionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<FileUploadResponsePayloadDto>>;
9
10
  uploadMultipleFiles(files: Express.Multer.File[], options: UploadOptionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<FileUploadResponsePayloadDto[]>>;
10
11
  deleteSingleFile(dto: DeleteSingleFileDto, user: ILoggedUserInfo): Promise<SingleResponseDto<boolean>>;