@flusys/nestjs-storage 1.1.0-beta → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -6
- package/cjs/config/index.js +0 -1
- package/cjs/config/storage.constants.js +0 -17
- package/cjs/controllers/file-manager.controller.js +44 -1
- package/cjs/controllers/folder.controller.js +44 -1
- package/cjs/controllers/storage-config.controller.js +44 -1
- package/cjs/controllers/upload.controller.js +18 -29
- package/cjs/docs/storage-swagger.config.js +24 -136
- package/cjs/dtos/file-manager.dto.js +70 -34
- package/cjs/dtos/folder.dto.js +15 -9
- package/cjs/dtos/storage-config.dto.js +4 -85
- package/cjs/dtos/upload.dto.js +24 -17
- package/cjs/entities/file-manager-with-company.entity.js +3 -4
- package/cjs/entities/file-manager.entity.js +71 -3
- package/cjs/entities/folder-with-company.entity.js +3 -4
- package/cjs/entities/folder.entity.js +19 -3
- package/cjs/entities/index.js +9 -10
- package/cjs/entities/storage-config-with-company.entity.js +3 -4
- package/cjs/entities/storage-config.entity.js +74 -3
- package/cjs/interfaces/index.js +0 -1
- package/cjs/middlewares/file-serve.middleware.js +113 -100
- package/cjs/modules/storage.module.js +82 -136
- package/cjs/providers/azure-provider.optional.js +10 -38
- package/cjs/providers/local-provider.js +38 -31
- package/cjs/providers/s3-provider.optional.js +19 -40
- package/cjs/providers/storage-factory.service.js +54 -99
- package/cjs/providers/storage-provider.registry.js +8 -18
- package/cjs/services/file-manager.service.js +238 -323
- package/cjs/services/folder.service.js +8 -11
- package/cjs/services/index.js +1 -0
- package/cjs/{config → services}/storage-config.service.js +32 -76
- package/cjs/services/storage-datasource.provider.js +16 -26
- package/cjs/services/storage-provider-config.service.js +15 -37
- package/cjs/services/upload.service.js +72 -88
- package/cjs/utils/file-validator.util.js +458 -0
- package/cjs/utils/image-compressor.util.js +3 -8
- package/config/index.d.ts +0 -1
- package/config/storage.constants.d.ts +0 -8
- package/controllers/upload.controller.d.ts +3 -6
- package/dtos/file-manager.dto.d.ts +12 -5
- package/dtos/folder.dto.d.ts +5 -5
- package/dtos/storage-config.dto.d.ts +7 -13
- package/entities/file-manager-with-company.entity.d.ts +2 -2
- package/entities/file-manager.entity.d.ts +12 -2
- package/entities/folder-with-company.entity.d.ts +2 -2
- package/entities/folder.entity.d.ts +4 -2
- package/entities/index.d.ts +3 -4
- package/entities/storage-config-with-company.entity.d.ts +2 -2
- package/entities/storage-config.entity.d.ts +8 -2
- package/fesm/config/index.js +0 -1
- package/fesm/config/storage.constants.js +0 -8
- package/fesm/controllers/file-manager.controller.js +45 -2
- package/fesm/controllers/folder.controller.js +45 -2
- package/fesm/controllers/storage-config.controller.js +45 -2
- package/fesm/controllers/upload.controller.js +19 -30
- package/fesm/docs/storage-swagger.config.js +27 -142
- package/fesm/dtos/file-manager.dto.js +71 -35
- package/fesm/dtos/folder.dto.js +16 -10
- package/fesm/dtos/storage-config.dto.js +8 -95
- package/fesm/dtos/upload.dto.js +25 -19
- package/fesm/entities/file-manager-with-company.entity.js +3 -4
- package/fesm/entities/file-manager.entity.js +72 -4
- package/fesm/entities/folder-with-company.entity.js +3 -4
- package/fesm/entities/folder.entity.js +20 -4
- package/fesm/entities/index.js +5 -13
- package/fesm/entities/storage-config-with-company.entity.js +3 -4
- package/fesm/entities/storage-config.entity.js +75 -4
- package/fesm/interfaces/index.js +0 -1
- package/fesm/interfaces/storage-config.interface.js +1 -3
- package/fesm/middlewares/file-serve.middleware.js +114 -101
- package/fesm/modules/storage.module.js +83 -136
- package/fesm/providers/azure-provider.optional.js +11 -42
- package/fesm/providers/local-provider.js +38 -31
- package/fesm/providers/s3-provider.optional.js +20 -44
- package/fesm/providers/storage-factory.service.js +52 -97
- package/fesm/providers/storage-provider.registry.js +10 -20
- package/fesm/services/file-manager.service.js +237 -322
- package/fesm/services/folder.service.js +6 -9
- package/fesm/services/index.js +1 -0
- package/fesm/{config → services}/storage-config.service.js +32 -76
- package/fesm/services/storage-datasource.provider.js +16 -26
- package/fesm/services/storage-provider-config.service.js +13 -35
- package/fesm/services/upload.service.js +71 -87
- package/fesm/utils/file-validator.util.js +451 -0
- package/fesm/utils/image-compressor.util.js +3 -8
- package/interfaces/file-manager.interface.d.ts +7 -4
- package/interfaces/index.d.ts +0 -1
- package/interfaces/storage-config.interface.d.ts +0 -20
- package/interfaces/storage-module-options.interface.d.ts +0 -5
- package/middlewares/file-serve.middleware.d.ts +9 -1
- package/modules/storage.module.d.ts +1 -2
- package/package.json +6 -6
- package/providers/azure-provider.optional.d.ts +8 -6
- package/providers/local-provider.d.ts +2 -7
- package/providers/s3-provider.optional.d.ts +9 -7
- package/providers/storage-factory.service.d.ts +8 -9
- package/providers/storage-provider.registry.d.ts +4 -4
- package/services/file-manager.service.d.ts +23 -16
- package/services/folder.service.d.ts +4 -4
- package/services/index.d.ts +1 -0
- package/services/storage-config.service.d.ts +24 -0
- package/services/storage-datasource.provider.d.ts +3 -4
- package/services/storage-provider-config.service.d.ts +4 -4
- package/services/upload.service.d.ts +3 -2
- package/utils/file-validator.util.d.ts +19 -0
- package/cjs/entities/file-manager-base.entity.js +0 -115
- package/cjs/entities/folder-base.entity.js +0 -55
- package/cjs/entities/storage-config-base.entity.js +0 -93
- package/cjs/interfaces/file-upload-response.interface.js +0 -4
- package/config/storage-config.service.d.ts +0 -22
- package/entities/file-manager-base.entity.d.ts +0 -13
- package/entities/folder-base.entity.d.ts +0 -5
- package/entities/storage-config-base.entity.d.ts +0 -9
- package/fesm/entities/file-manager-base.entity.js +0 -108
- package/fesm/entities/folder-base.entity.js +0 -48
- package/fesm/entities/storage-config-base.entity.js +0 -83
- package/fesm/interfaces/file-upload-response.interface.js +0 -1
- package/interfaces/file-upload-response.interface.d.ts +0 -6
|
@@ -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 (
|
|
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
|
-
*
|
|
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
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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}'
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
*/ isProviderAvailable(providerName) {
|
|
158
|
-
return _storageproviderregistry.StorageProviderRegistry.has(providerName);
|
|
116
|
+
clearCache() {
|
|
117
|
+
this.cache.clear();
|
|
159
118
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
|
|
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
|
|
192
|
-
this.providerCache.clear();
|
|
193
|
-
this.logger.log('Storage provider cleanup complete');
|
|
148
|
+
await instance.initialize(initConfig);
|
|
194
149
|
}
|
|
195
|
-
constructor(
|
|
196
|
-
_define_property(this, "
|
|
150
|
+
constructor(configService){
|
|
151
|
+
_define_property(this, "configService", void 0);
|
|
197
152
|
_define_property(this, "logger", void 0);
|
|
198
|
-
_define_property(this, "
|
|
199
|
-
this.
|
|
153
|
+
_define_property(this, "cache", void 0);
|
|
154
|
+
this.configService = configService;
|
|
200
155
|
this.logger = new _common.Logger(StorageFactoryService.name);
|
|
201
|
-
this.
|
|
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)(
|
|
161
|
+
_ts_param(0, (0, _common.Inject)(_services.StorageConfigService)),
|
|
207
162
|
_ts_metadata("design:type", Function),
|
|
208
163
|
_ts_metadata("design:paramtypes", [
|
|
209
|
-
typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|