@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.
Files changed (118) hide show
  1. package/README.md +148 -6
  2. package/cjs/config/index.js +0 -1
  3. package/cjs/config/storage.constants.js +0 -17
  4. package/cjs/controllers/file-manager.controller.js +44 -1
  5. package/cjs/controllers/folder.controller.js +44 -1
  6. package/cjs/controllers/storage-config.controller.js +44 -1
  7. package/cjs/controllers/upload.controller.js +18 -29
  8. package/cjs/docs/storage-swagger.config.js +24 -136
  9. package/cjs/dtos/file-manager.dto.js +70 -34
  10. package/cjs/dtos/folder.dto.js +15 -9
  11. package/cjs/dtos/storage-config.dto.js +4 -85
  12. package/cjs/dtos/upload.dto.js +24 -17
  13. package/cjs/entities/file-manager-with-company.entity.js +3 -4
  14. package/cjs/entities/file-manager.entity.js +71 -3
  15. package/cjs/entities/folder-with-company.entity.js +3 -4
  16. package/cjs/entities/folder.entity.js +19 -3
  17. package/cjs/entities/index.js +9 -10
  18. package/cjs/entities/storage-config-with-company.entity.js +3 -4
  19. package/cjs/entities/storage-config.entity.js +74 -3
  20. package/cjs/interfaces/index.js +0 -1
  21. package/cjs/middlewares/file-serve.middleware.js +113 -100
  22. package/cjs/modules/storage.module.js +82 -136
  23. package/cjs/providers/azure-provider.optional.js +10 -38
  24. package/cjs/providers/local-provider.js +38 -31
  25. package/cjs/providers/s3-provider.optional.js +19 -40
  26. package/cjs/providers/storage-factory.service.js +54 -99
  27. package/cjs/providers/storage-provider.registry.js +8 -18
  28. package/cjs/services/file-manager.service.js +238 -323
  29. package/cjs/services/folder.service.js +8 -11
  30. package/cjs/services/index.js +1 -0
  31. package/cjs/{config → services}/storage-config.service.js +32 -76
  32. package/cjs/services/storage-datasource.provider.js +16 -26
  33. package/cjs/services/storage-provider-config.service.js +15 -37
  34. package/cjs/services/upload.service.js +72 -88
  35. package/cjs/utils/file-validator.util.js +458 -0
  36. package/cjs/utils/image-compressor.util.js +3 -8
  37. package/config/index.d.ts +0 -1
  38. package/config/storage.constants.d.ts +0 -8
  39. package/controllers/upload.controller.d.ts +3 -6
  40. package/dtos/file-manager.dto.d.ts +12 -5
  41. package/dtos/folder.dto.d.ts +5 -5
  42. package/dtos/storage-config.dto.d.ts +7 -13
  43. package/entities/file-manager-with-company.entity.d.ts +2 -2
  44. package/entities/file-manager.entity.d.ts +12 -2
  45. package/entities/folder-with-company.entity.d.ts +2 -2
  46. package/entities/folder.entity.d.ts +4 -2
  47. package/entities/index.d.ts +3 -4
  48. package/entities/storage-config-with-company.entity.d.ts +2 -2
  49. package/entities/storage-config.entity.d.ts +8 -2
  50. package/fesm/config/index.js +0 -1
  51. package/fesm/config/storage.constants.js +0 -8
  52. package/fesm/controllers/file-manager.controller.js +45 -2
  53. package/fesm/controllers/folder.controller.js +45 -2
  54. package/fesm/controllers/storage-config.controller.js +45 -2
  55. package/fesm/controllers/upload.controller.js +19 -30
  56. package/fesm/docs/storage-swagger.config.js +27 -142
  57. package/fesm/dtos/file-manager.dto.js +71 -35
  58. package/fesm/dtos/folder.dto.js +16 -10
  59. package/fesm/dtos/storage-config.dto.js +8 -95
  60. package/fesm/dtos/upload.dto.js +25 -19
  61. package/fesm/entities/file-manager-with-company.entity.js +3 -4
  62. package/fesm/entities/file-manager.entity.js +72 -4
  63. package/fesm/entities/folder-with-company.entity.js +3 -4
  64. package/fesm/entities/folder.entity.js +20 -4
  65. package/fesm/entities/index.js +5 -13
  66. package/fesm/entities/storage-config-with-company.entity.js +3 -4
  67. package/fesm/entities/storage-config.entity.js +75 -4
  68. package/fesm/interfaces/index.js +0 -1
  69. package/fesm/interfaces/storage-config.interface.js +1 -3
  70. package/fesm/middlewares/file-serve.middleware.js +114 -101
  71. package/fesm/modules/storage.module.js +83 -136
  72. package/fesm/providers/azure-provider.optional.js +11 -42
  73. package/fesm/providers/local-provider.js +38 -31
  74. package/fesm/providers/s3-provider.optional.js +20 -44
  75. package/fesm/providers/storage-factory.service.js +52 -97
  76. package/fesm/providers/storage-provider.registry.js +10 -20
  77. package/fesm/services/file-manager.service.js +237 -322
  78. package/fesm/services/folder.service.js +6 -9
  79. package/fesm/services/index.js +1 -0
  80. package/fesm/{config → services}/storage-config.service.js +32 -76
  81. package/fesm/services/storage-datasource.provider.js +16 -26
  82. package/fesm/services/storage-provider-config.service.js +13 -35
  83. package/fesm/services/upload.service.js +71 -87
  84. package/fesm/utils/file-validator.util.js +451 -0
  85. package/fesm/utils/image-compressor.util.js +3 -8
  86. package/interfaces/file-manager.interface.d.ts +7 -4
  87. package/interfaces/index.d.ts +0 -1
  88. package/interfaces/storage-config.interface.d.ts +0 -20
  89. package/interfaces/storage-module-options.interface.d.ts +0 -5
  90. package/middlewares/file-serve.middleware.d.ts +9 -1
  91. package/modules/storage.module.d.ts +1 -2
  92. package/package.json +6 -6
  93. package/providers/azure-provider.optional.d.ts +8 -6
  94. package/providers/local-provider.d.ts +2 -7
  95. package/providers/s3-provider.optional.d.ts +9 -7
  96. package/providers/storage-factory.service.d.ts +8 -9
  97. package/providers/storage-provider.registry.d.ts +4 -4
  98. package/services/file-manager.service.d.ts +23 -16
  99. package/services/folder.service.d.ts +4 -4
  100. package/services/index.d.ts +1 -0
  101. package/services/storage-config.service.d.ts +24 -0
  102. package/services/storage-datasource.provider.d.ts +3 -4
  103. package/services/storage-provider-config.service.d.ts +4 -4
  104. package/services/upload.service.d.ts +3 -2
  105. package/utils/file-validator.util.d.ts +19 -0
  106. package/cjs/entities/file-manager-base.entity.js +0 -115
  107. package/cjs/entities/folder-base.entity.js +0 -55
  108. package/cjs/entities/storage-config-base.entity.js +0 -93
  109. package/cjs/interfaces/file-upload-response.interface.js +0 -4
  110. package/config/storage-config.service.d.ts +0 -22
  111. package/entities/file-manager-base.entity.d.ts +0 -13
  112. package/entities/folder-base.entity.d.ts +0 -5
  113. package/entities/storage-config-base.entity.d.ts +0 -9
  114. package/fesm/entities/file-manager-base.entity.js +0 -108
  115. package/fesm/entities/folder-base.entity.js +0 -48
  116. package/fesm/entities/storage-config-base.entity.js +0 -83
  117. package/fesm/interfaces/file-upload-response.interface.js +0 -1
  118. package/interfaces/file-upload-response.interface.d.ts +0 -6
@@ -80,6 +80,29 @@ function _interop_require_wildcard(obj, nodeInterop) {
80
80
  }
81
81
  let LocalProvider = class LocalProvider {
82
82
  /**
83
+ * SECURITY: Validates that a target path does not escape the base directory
84
+ * Prevents path traversal attacks using ../ sequences
85
+ * @throws Error if path traversal is detected
86
+ */ validatePathWithinBase(targetPath) {
87
+ const normalizedBasePath = _path.resolve(this.basePath);
88
+ const normalizedTargetPath = _path.resolve(targetPath);
89
+ if (!normalizedTargetPath.startsWith(normalizedBasePath + _path.sep) && normalizedTargetPath !== normalizedBasePath) {
90
+ this.logger.warn(`Path traversal attempt detected: ${targetPath}`);
91
+ throw new Error('Invalid path: Path traversal attempt detected');
92
+ }
93
+ }
94
+ /**
95
+ * SECURITY: Validates file key format to prevent malicious input
96
+ * @throws Error if key contains suspicious patterns
97
+ */ validateKeyFormat(key) {
98
+ if (!key || typeof key !== 'string' || key.trim().length === 0) {
99
+ throw new Error('Invalid file key: empty or invalid');
100
+ }
101
+ if (key.includes('\0')) {
102
+ throw new Error('Invalid file key: contains null bytes');
103
+ }
104
+ }
105
+ /**
83
106
  * Initialize Local File System provider with configuration
84
107
  * @param config.basePath - Base path for file storage (default: './uploads')
85
108
  * @param config.baseUrl - Optional base URL for generating file URLs
@@ -112,7 +135,11 @@ let LocalProvider = class LocalProvider {
112
135
  }
113
136
  // Build file path
114
137
  const folderPath = options.folderPath ? _path.join(this.basePath, options.folderPath) : this.basePath;
138
+ // SECURITY: Validate path does not escape base directory
139
+ this.validatePathWithinBase(folderPath);
115
140
  const filePath = _path.join(folderPath, fileName);
141
+ // SECURITY: Double-check final file path
142
+ this.validatePathWithinBase(filePath);
116
143
  // Ensure directory exists
117
144
  await _promises.mkdir(folderPath, {
118
145
  recursive: true
@@ -135,14 +162,20 @@ let LocalProvider = class LocalProvider {
135
162
  }
136
163
  async deleteFile(key) {
137
164
  try {
165
+ // SECURITY: Validate key format first
166
+ this.validateKeyFormat(key);
138
167
  // Key now includes the basePath, resolve from cwd
139
168
  let filePath = _path.resolve(key);
169
+ // SECURITY: Validate resolved path is within base directory
170
+ this.validatePathWithinBase(filePath);
140
171
  // Check if file exists at the resolved path
141
172
  try {
142
173
  await _promises.access(filePath);
143
174
  } catch {
144
175
  // Fallback: try with basePath prefix (for old keys without basePath)
145
176
  const fallbackPath = _path.join(this.basePath, key);
177
+ // SECURITY: Validate fallback path as well
178
+ this.validatePathWithinBase(fallbackPath);
146
179
  try {
147
180
  await _promises.access(fallbackPath);
148
181
  filePath = fallbackPath;
@@ -155,7 +188,11 @@ let LocalProvider = class LocalProvider {
155
188
  }
156
189
  await _promises.unlink(filePath);
157
190
  this.logger.log(`Deleted file from local storage: ${key}`);
158
- } catch (_error) {
191
+ } catch (error) {
192
+ // Re-throw security errors
193
+ if (error instanceof Error && error.message.includes('Invalid')) {
194
+ throw error;
195
+ }
159
196
  this.logger.warn(`Failed to delete file from local storage: ${key}`);
160
197
  }
161
198
  }
@@ -182,36 +219,6 @@ let LocalProvider = class LocalProvider {
182
219
  return false;
183
220
  }
184
221
  }
185
- /**
186
- * Get the absolute path for a file key
187
- * Key now includes basePath, so resolve from cwd
188
- */ getAbsolutePath(key) {
189
- return _path.resolve(key);
190
- }
191
- /**
192
- * Check if a file exists
193
- */ async fileExists(key) {
194
- try {
195
- // Key now includes basePath, resolve from cwd
196
- const filePath = _path.resolve(key);
197
- await _promises.access(filePath);
198
- return true;
199
- } catch {
200
- return false;
201
- }
202
- }
203
- /**
204
- * Get file stats
205
- */ async getFileStats(key) {
206
- // Key now includes basePath, resolve from cwd
207
- const filePath = _path.resolve(key);
208
- const stats = await _promises.stat(filePath);
209
- return {
210
- size: stats.size,
211
- createdAt: stats.birthtime,
212
- modifiedAt: stats.mtime
213
- };
214
- }
215
222
  constructor(){
216
223
  _define_property(this, "logger", new _common.Logger(LocalProvider.name));
217
224
  _define_property(this, "basePath", '');
@@ -1,17 +1,6 @@
1
1
  /**
2
- * OPTIONAL S3 Provider
3
- *
4
- * This provider requires @aws-sdk packages to be installed.
5
- * Only import this if you need AWS S3 storage.
6
- *
7
- * Installation:
8
- * npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
9
- *
10
- * Usage:
11
- * import { S3Provider } from '@flusys/nestjs-storage/providers/s3-provider.optional';
12
- * import { StorageProviderRegistry, FileLocationEnum } from '@flusys/nestjs-storage';
13
- *
14
- * StorageProviderRegistry.register(FileLocationEnum.AWS, S3Provider);
2
+ * Optional AWS S3 Storage Provider
3
+ * Requires: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner
15
4
  */ "use strict";
16
5
  Object.defineProperty(exports, "__esModule", {
17
6
  value: true
@@ -22,9 +11,9 @@ Object.defineProperty(exports, "S3Provider", {
22
11
  return S3Provider;
23
12
  }
24
13
  });
25
- const _imagecompressorutil = require("../utils/image-compressor.util");
26
14
  const _common = require("@nestjs/common");
27
15
  const _uuid = require("uuid");
16
+ const _imagecompressorutil = require("../utils/image-compressor.util");
28
17
  function _define_property(obj, key, value) {
29
18
  if (key in obj) {
30
19
  Object.defineProperty(obj, key, {
@@ -80,26 +69,22 @@ function _interop_require_wildcard(obj, nodeInterop) {
80
69
  return newObj;
81
70
  }
82
71
  let S3Provider = class S3Provider {
83
- /**
84
- * Initialize S3 provider with configuration
85
- */ async initialize(config) {
72
+ async initialize(config) {
86
73
  try {
87
- // Dynamic import - only loads AWS SDK if this provider is used
88
74
  const { S3Client } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
89
75
  this.region = config.region;
90
76
  this.bucketName = config.bucket;
91
77
  this.s3 = new S3Client({
92
78
  region: config.region,
79
+ endpoint: config.endpoint,
93
80
  credentials: config.accessKeyId && config.secretAccessKey ? {
94
81
  accessKeyId: config.accessKeyId,
95
82
  secretAccessKey: config.secretAccessKey
96
- } : undefined,
97
- endpoint: config.endpoint
83
+ } : undefined
98
84
  });
99
85
  this.logger.log(`S3 Provider initialized: region=${config.region}, bucket=${config.bucket}`);
100
- } catch (_error) {
101
- this.logger.error('Failed to initialize S3Provider. Make sure @aws-sdk packages are installed.');
102
- throw new Error('AWS SDK not found. Install it with: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner');
86
+ } catch {
87
+ throw new Error('AWS SDK not found. Install: npm install @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner');
103
88
  }
104
89
  }
105
90
  async uploadFile(file, options) {
@@ -107,7 +92,6 @@ let S3Provider = class S3Provider {
107
92
  let processedBuffer = file.buffer;
108
93
  let contentType = file.mimetype;
109
94
  const fileName = `${(0, _uuid.v4)()}-${file.originalname}`;
110
- // Compress image if needed
111
95
  if (options.compress && file.mimetype.startsWith('image/')) {
112
96
  const result = await _imagecompressorutil.ImageCompressor.compress(file.buffer, file.mimetype, {
113
97
  maxWidth: options.maxWidth,
@@ -119,7 +103,7 @@ let S3Provider = class S3Provider {
119
103
  contentType = result.format;
120
104
  }
121
105
  const key = options.folderPath ? `${options.folderPath}/${fileName}` : fileName;
122
- const upload = new Upload({
106
+ await new Upload({
123
107
  client: this.s3,
124
108
  params: {
125
109
  Bucket: this.bucketName,
@@ -127,8 +111,7 @@ let S3Provider = class S3Provider {
127
111
  Body: processedBuffer,
128
112
  ContentType: contentType
129
113
  }
130
- });
131
- await upload.done();
114
+ }).done();
132
115
  return {
133
116
  name: fileName,
134
117
  key,
@@ -141,44 +124,40 @@ let S3Provider = class S3Provider {
141
124
  }
142
125
  async deleteFile(key) {
143
126
  const { DeleteObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
144
- const command = new DeleteObjectCommand({
127
+ await this.s3.send(new DeleteObjectCommand({
145
128
  Bucket: this.bucketName,
146
129
  Key: key
147
- });
148
- await this.s3.send(command);
130
+ }));
149
131
  this.logger.log(`Deleted file from S3: ${key}`);
150
132
  }
151
133
  async deleteMultipleFiles(keys) {
152
134
  const { DeleteObjectsCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
153
- const command = new DeleteObjectsCommand({
135
+ await this.s3.send(new DeleteObjectsCommand({
154
136
  Bucket: this.bucketName,
155
137
  Delete: {
156
138
  Objects: keys.map((key)=>({
157
139
  Key: key
158
140
  }))
159
141
  }
160
- });
161
- await this.s3.send(command);
142
+ }));
162
143
  this.logger.log(`Deleted ${keys.length} files from S3`);
163
144
  }
164
145
  async generatePresignedUrl(key, expiresInSeconds = 3600) {
165
146
  const { GetObjectCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
166
147
  const { getSignedUrl } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/s3-request-presigner")));
167
- const command = new GetObjectCommand({
148
+ return getSignedUrl(this.s3, new GetObjectCommand({
168
149
  Bucket: this.bucketName,
169
150
  Key: key
170
- });
171
- return getSignedUrl(this.s3, command, {
151
+ }), {
172
152
  expiresIn: expiresInSeconds
173
153
  });
174
154
  }
175
155
  async healthCheck() {
176
156
  try {
177
157
  const { HeadBucketCommand } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3")));
178
- const command = new HeadBucketCommand({
158
+ await this.s3.send(new HeadBucketCommand({
179
159
  Bucket: this.bucketName
180
- });
181
- await this.s3.send(command);
160
+ }));
182
161
  return true;
183
162
  } catch {
184
163
  return false;
@@ -186,7 +165,7 @@ let S3Provider = class S3Provider {
186
165
  }
187
166
  constructor(){
188
167
  _define_property(this, "logger", new _common.Logger(S3Provider.name));
189
- _define_property(this, "s3", void 0); // S3Client - typed as any to avoid import
168
+ _define_property(this, "s3", void 0);
190
169
  _define_property(this, "bucketName", '');
191
170
  _define_property(this, "region", '');
192
171
  }
@@ -8,10 +8,10 @@ Object.defineProperty(exports, "StorageFactoryService", {
8
8
  return StorageFactoryService;
9
9
  }
10
10
  });
11
- const _storageproviderregistry = require("./storage-provider.registry");
12
11
  const _common = require("@nestjs/common");
13
12
  const _crypto = /*#__PURE__*/ _interop_require_wildcard(require("crypto"));
14
- const _config = require("../config");
13
+ const _services = require("../services");
14
+ const _storageproviderregistry = require("./storage-provider.registry");
15
15
  function _define_property(obj, key, value) {
16
16
  if (key in obj) {
17
17
  Object.defineProperty(obj, key, {
@@ -81,131 +81,86 @@ function _ts_param(paramIndex, decorator) {
81
81
  };
82
82
  }
83
83
  let StorageFactoryService = class StorageFactoryService {
84
- /**
85
- * Generate a stable cache key for provider configuration
86
- * Uses SHA256 hash to handle complex config objects consistently
87
- */ generateCacheKey(config) {
88
- // Sort keys for consistent ordering, then hash
89
- const configString = JSON.stringify(config.config, Object.keys(config.config || {}).sort());
90
- const configHash = _crypto.createHash('sha256').update(configString).digest('hex').substring(0, 16);
91
- return `${config.provider}-${configHash}`;
92
- }
93
- /**
94
- * Create a storage provider instance based on configuration
95
- * Uses lazy loading - provider is only instantiated when requested
96
- */ async createProvider(config) {
97
- const providerKey = this.generateCacheKey(config);
98
- // Check cache first
99
- if (this.providerCache.has(providerKey)) {
100
- return this.providerCache.get(providerKey);
101
- }
102
- // Get provider class from registry
84
+ async createProvider(config) {
85
+ const cacheKey = this.generateCacheKey(config);
86
+ const cached = this.cache.get(cacheKey);
87
+ if (cached) return cached;
103
88
  const ProviderClass = _storageproviderregistry.StorageProviderRegistry.get(config.provider);
104
89
  if (!ProviderClass) {
105
- throw new _common.NotFoundException(`Storage provider '${config.provider}' is not registered. ` + `Available providers: ${_storageproviderregistry.StorageProviderRegistry.getAll().join(', ')}`);
90
+ throw new _common.NotFoundException(`Storage provider '${config.provider}' not registered. Available: ${_storageproviderregistry.StorageProviderRegistry.getAll().join(', ')}`);
106
91
  }
107
- // Instantiate provider
108
92
  try {
109
93
  const instance = new ProviderClass();
110
- // Initialize if method exists
111
- if ('initialize' in instance && typeof instance.initialize === 'function') {
112
- // For local provider, inject appUrl as fallback for baseUrl
113
- let initConfig = config.config;
114
- if (config.provider === 'local' && !config.config?.baseUrl) {
115
- const appUrl = this.storageConfigService.getAppUrl();
116
- if (appUrl) {
117
- initConfig = {
118
- ...config.config,
119
- baseUrl: appUrl
120
- };
121
- this.logger.debug(`Using appUrl from config as baseUrl: ${appUrl}`);
122
- }
123
- }
124
- await instance.initialize(initConfig);
125
- }
126
- // Cache the instance
127
- this.providerCache.set(providerKey, instance);
128
- this.logger.log(`Created storage provider: ${config.provider} (key: ${providerKey})`);
94
+ await this.initializeProvider(instance, config);
95
+ this.cache.set(cacheKey, instance);
96
+ this.logger.log(`Created provider: ${config.provider} (${cacheKey})`);
129
97
  return instance;
130
98
  } catch (error) {
131
- this.logger.error(`Failed to create provider ${config.provider}:`, error);
132
- // Preserve original error message for better debugging
133
- const originalMessage = error?.message || 'Unknown error';
134
- throw new Error(`Failed to initialize storage provider '${config.provider}': ${originalMessage}`);
99
+ const message = error instanceof Error ? error.message : 'Unknown error';
100
+ throw new Error(`Failed to initialize '${config.provider}': ${message}`);
135
101
  }
136
102
  }
137
- /**
138
- * Get provider by name (creates with default config if not cached)
139
- */ async getProvider(providerName) {
140
- const cachedKey = Array.from(this.providerCache.keys()).find((key)=>key.startsWith(providerName));
141
- if (cachedKey) {
142
- return this.providerCache.get(cachedKey);
143
- }
144
- // Create with empty config
103
+ async getProvider(providerName) {
104
+ const cachedKey = Array.from(this.cache.keys()).find((k)=>k.startsWith(providerName));
105
+ if (cachedKey) return this.cache.get(cachedKey);
145
106
  return this.createProvider({
146
107
  provider: providerName,
147
108
  config: {}
148
109
  });
149
110
  }
150
- /**
151
- * Clear cached providers
152
- */ clearCache() {
153
- this.providerCache.clear();
111
+ getLocalProviderBasePath() {
112
+ const localKey = Array.from(this.cache.keys()).find((k)=>k.startsWith('local'));
113
+ const provider = localKey ? this.cache.get(localKey) : null;
114
+ return provider && 'basePath' in provider ? provider.basePath : null;
154
115
  }
155
- /**
156
- * Check if a provider is available
157
- */ isProviderAvailable(providerName) {
158
- return _storageproviderregistry.StorageProviderRegistry.has(providerName);
116
+ clearCache() {
117
+ this.cache.clear();
159
118
  }
160
- /**
161
- * Get list of available providers
162
- */ getAvailableProviders() {
163
- return _storageproviderregistry.StorageProviderRegistry.getAll();
119
+ async onModuleDestroy() {
120
+ this.logger.log('Cleaning up storage providers...');
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}`)));
122
+ await Promise.allSettled(closePromises);
123
+ this.cache.clear();
124
+ this.logger.log('Storage provider cleanup complete');
164
125
  }
165
- /**
166
- * Get the basePath from cached local provider
167
- * Returns null if no local provider is cached
168
- */ getLocalProviderBasePath() {
169
- // Find cached local provider
170
- const localKey = Array.from(this.providerCache.keys()).find((key)=>key.startsWith('local'));
171
- if (localKey) {
172
- const provider = this.providerCache.get(localKey);
173
- if (provider && 'basePath' in provider) {
174
- return provider.basePath;
175
- }
176
- }
177
- return null;
126
+ // ─── Private Helpers ──────────────────────────────────────────────────────────
127
+ generateCacheKey(config) {
128
+ const sortedKeys = Object.keys(config.config || {}).sort();
129
+ const hash = _crypto.createHash('sha256').update(JSON.stringify(config.config, sortedKeys)).digest('hex').substring(0, 16);
130
+ return `${config.provider}-${hash}`;
178
131
  }
179
- /**
180
- * Cleanup all cached provider connections on module destroy
181
- * Ensures SFTP connections and other resources are properly released
182
- */ async onModuleDestroy() {
183
- this.logger.log('Cleaning up storage provider connections...');
184
- const closePromises = [];
185
- for (const [key, provider] of this.providerCache.entries()){
186
- // Check if provider has a close method (e.g., SFTP)
187
- if ('close' in provider && typeof provider.close === 'function') {
188
- closePromises.push(provider.close().then(()=>this.logger.debug(`Closed provider: ${key}`)).catch((err)=>this.logger.warn(`Failed to close provider ${key}: ${err.message}`)));
132
+ async initializeProvider(instance, config) {
133
+ if (!('initialize' in instance) || typeof instance.initialize !== 'function') {
134
+ return;
135
+ }
136
+ let initConfig = config.config;
137
+ // Inject appUrl as baseUrl fallback for local provider
138
+ if (config.provider === 'local' && !config.config?.baseUrl) {
139
+ const appUrl = this.configService.getAppUrl();
140
+ if (appUrl) {
141
+ initConfig = {
142
+ ...config.config,
143
+ baseUrl: appUrl
144
+ };
145
+ this.logger.debug(`Using appUrl as baseUrl: ${appUrl}`);
189
146
  }
190
147
  }
191
- await Promise.allSettled(closePromises);
192
- this.providerCache.clear();
193
- this.logger.log('Storage provider cleanup complete');
148
+ await instance.initialize(initConfig);
194
149
  }
195
- constructor(storageConfigService){
196
- _define_property(this, "storageConfigService", void 0);
150
+ constructor(configService){
151
+ _define_property(this, "configService", void 0);
197
152
  _define_property(this, "logger", void 0);
198
- _define_property(this, "providerCache", void 0);
199
- this.storageConfigService = storageConfigService;
153
+ _define_property(this, "cache", void 0);
154
+ this.configService = configService;
200
155
  this.logger = new _common.Logger(StorageFactoryService.name);
201
- this.providerCache = new Map();
156
+ this.cache = new Map();
202
157
  }
203
158
  };
204
159
  StorageFactoryService = _ts_decorate([
205
160
  (0, _common.Injectable)(),
206
- _ts_param(0, (0, _common.Inject)(_config.StorageConfigService)),
161
+ _ts_param(0, (0, _common.Inject)(_services.StorageConfigService)),
207
162
  _ts_metadata("design:type", Function),
208
163
  _ts_metadata("design:paramtypes", [
209
- typeof _config.StorageConfigService === "undefined" ? Object : _config.StorageConfigService
164
+ typeof _services.StorageConfigService === "undefined" ? Object : _services.StorageConfigService
210
165
  ])
211
166
  ], StorageFactoryService);
@@ -22,29 +22,19 @@ function _define_property(obj, key, value) {
22
22
  return obj;
23
23
  }
24
24
  let StorageProviderRegistry = class StorageProviderRegistry {
25
- /**
26
- * Register a storage provider
27
- */ static register(providerName, providerClass) {
28
- this.providers.set(providerName.toLowerCase(), providerClass);
25
+ static register(name, providerClass) {
26
+ this.providers.set(name.toLowerCase(), providerClass);
29
27
  }
30
- /**
31
- * Get a registered provider class
32
- */ static get(providerName) {
33
- return this.providers.get(providerName.toLowerCase());
28
+ static get(name) {
29
+ return this.providers.get(name.toLowerCase());
34
30
  }
35
- /**
36
- * Check if a provider is registered
37
- */ static has(providerName) {
38
- return this.providers.has(providerName.toLowerCase());
31
+ static has(name) {
32
+ return this.providers.has(name.toLowerCase());
39
33
  }
40
- /**
41
- * Get all registered provider names
42
- */ static getAll() {
34
+ static getAll() {
43
35
  return Array.from(this.providers.keys());
44
36
  }
45
- /**
46
- * Clear all providers (useful for testing)
47
- */ static clear() {
37
+ static clear() {
48
38
  this.providers.clear();
49
39
  }
50
40
  };