@onlineapps/conn-base-storage 1.0.2 → 1.0.3
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/coverage/clover.xml +288 -204
- package/coverage/coverage-final.json +5 -2
- package/coverage/lcov-report/config.js.html +244 -0
- package/coverage/lcov-report/defaults.js.html +214 -0
- package/coverage/lcov-report/index.html +72 -27
- package/coverage/lcov-report/index.js.html +1228 -199
- package/coverage/lcov-report/internal-url-adapter.js.html +28 -73
- package/coverage/lcov-report/sharedUrlAdapter.js.html +856 -0
- package/coverage/lcov.info +522 -393
- package/package.json +3 -1
- package/src/config.js +53 -0
- package/src/defaults.js +43 -0
- package/src/index.js +72 -220
- package/src/internal-url-adapter.js +13 -28
- package/tests/unit/internal-url-adapter.test.js +4 -0
- package/tests/unit/storage.extended.unit.test.js +14 -6
- package/tests/unit/storage.unit.test.js +16 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-base-storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "MinIO storage connector with fingerprinting for immutable content storage",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"connector"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@onlineapps/runtime-config": "1.0.2",
|
|
24
|
+
"@onlineapps/storage-core": "1.0.8",
|
|
23
25
|
"minio": "^7.1.3",
|
|
24
26
|
"crypto": "^1.0.1",
|
|
25
27
|
"winston": "^3.8.2"
|
package/src/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runtime configuration schema for @onlineapps/conn-base-storage.
|
|
5
|
+
*
|
|
6
|
+
* Uses @onlineapps/runtime-config for unified priority:
|
|
7
|
+
* 1. Explicit config (passed to constructor)
|
|
8
|
+
* 2. Environment variable
|
|
9
|
+
* 3. Owner defaults (from ./defaults.js)
|
|
10
|
+
*
|
|
11
|
+
* NOTE:
|
|
12
|
+
* This module resolves MinIO runtime configuration via @onlineapps/runtime-config
|
|
13
|
+
* to avoid inline process.env access in implementation code.
|
|
14
|
+
*
|
|
15
|
+
* @module @onlineapps/conn-base-storage/config
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { createRuntimeConfig } = require('@onlineapps/runtime-config');
|
|
19
|
+
const DEFAULTS = require('./defaults');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runtime config schema for StorageConnector
|
|
23
|
+
*/
|
|
24
|
+
const runtimeCfg = createRuntimeConfig({
|
|
25
|
+
defaults: DEFAULTS,
|
|
26
|
+
schema: {
|
|
27
|
+
// MinIO topology (FAIL-FAST)
|
|
28
|
+
// Keep key names aligned with constructor config for predictability.
|
|
29
|
+
endPoint: { env: 'MINIO_ENDPOINT', required: true },
|
|
30
|
+
port: { env: 'MINIO_PORT', required: true, type: 'number' },
|
|
31
|
+
useSSL: { env: 'MINIO_USE_SSL', default: false, type: 'boolean' },
|
|
32
|
+
accessKey: { env: 'MINIO_ACCESS_KEY', required: true },
|
|
33
|
+
secretKey: { env: 'MINIO_SECRET_KEY', required: true },
|
|
34
|
+
|
|
35
|
+
// Wrapper behavior (module-owned defaults)
|
|
36
|
+
logLevel: { env: 'STORAGE_LOG_LEVEL', defaultKey: 'logLevel' },
|
|
37
|
+
defaultBucket: { env: 'MINIO_DEFAULT_BUCKET', defaultKey: 'defaultBucket' },
|
|
38
|
+
bucketName: { defaultKey: 'bucketName' },
|
|
39
|
+
cacheMaxSize: { defaultKey: 'cacheMaxSize', type: 'number' },
|
|
40
|
+
maxCacheSize: { defaultKey: 'maxCacheSize', type: 'number' },
|
|
41
|
+
|
|
42
|
+
// InternalUrlAdapter behavior (module-owned defaults + env hints)
|
|
43
|
+
nodeEnv: { env: 'NODE_ENV', default: 'development' },
|
|
44
|
+
dockerNetwork: { env: 'DOCKER_NETWORK', default: false, type: 'boolean' },
|
|
45
|
+
dockerEnv: { env: 'DOCKER_ENV' },
|
|
46
|
+
dockerContainer: { env: 'DOCKER_CONTAINER' },
|
|
47
|
+
hostname: { env: 'HOSTNAME' },
|
|
48
|
+
internalUrlServiceMap: { defaultKey: 'internalUrlServiceMap' },
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
module.exports = runtimeCfg;
|
|
53
|
+
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Module-owned defaults for @onlineapps/conn-base-storage.
|
|
5
|
+
*
|
|
6
|
+
* Ownership rule:
|
|
7
|
+
* - Defaults for the StorageConnector wrapper live here (bucket naming, cache sizing, log level).
|
|
8
|
+
* - MinIO connection defaults belong to @onlineapps/storage-core.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
logLevel: 'info',
|
|
13
|
+
defaultBucket: 'api-storage',
|
|
14
|
+
bucketName: 'oa-drive-storage',
|
|
15
|
+
cacheMaxSize: 100,
|
|
16
|
+
maxCacheSize: 100,
|
|
17
|
+
|
|
18
|
+
// InternalUrlAdapter defaults (module-owned)
|
|
19
|
+
internalUrlServiceMap: {
|
|
20
|
+
storage: {
|
|
21
|
+
docker: 'api_services_storage:9000',
|
|
22
|
+
local: 'localhost:9000'
|
|
23
|
+
},
|
|
24
|
+
registry: {
|
|
25
|
+
docker: 'api_registry:8080',
|
|
26
|
+
local: 'localhost:8080'
|
|
27
|
+
},
|
|
28
|
+
monitoring: {
|
|
29
|
+
docker: 'api_monitoring:3000',
|
|
30
|
+
local: 'localhost:3000'
|
|
31
|
+
},
|
|
32
|
+
mq: {
|
|
33
|
+
docker: 'api_services_rabbit:5672',
|
|
34
|
+
local: 'localhost:5672'
|
|
35
|
+
},
|
|
36
|
+
cache: {
|
|
37
|
+
docker: 'api_node_cache:6379',
|
|
38
|
+
local: 'localhost:6379'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
|
package/src/index.js
CHANGED
|
@@ -9,80 +9,10 @@
|
|
|
9
9
|
* @since 1.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const crypto = require('crypto');
|
|
12
|
+
const { StorageCore } = require('@onlineapps/storage-core');
|
|
14
13
|
const winston = require('winston');
|
|
15
|
-
const dns = require('dns').promises;
|
|
16
14
|
const SharedUrlAdapter = require('./sharedUrlAdapter');
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check if string is already an IP address
|
|
20
|
-
* @param {string} str - String to check
|
|
21
|
-
* @returns {boolean} True if string is an IP address
|
|
22
|
-
*/
|
|
23
|
-
function isIP(str) {
|
|
24
|
-
// IPv4 pattern
|
|
25
|
-
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
26
|
-
// IPv6 pattern (simplified)
|
|
27
|
-
const ipv6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
|
28
|
-
return ipv4.test(str) || ipv6.test(str);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve hostname to IP address
|
|
33
|
-
* MinIO client sometimes has issues with hostnames, this ensures we use IP
|
|
34
|
-
* @param {string} host - Hostname or IP
|
|
35
|
-
* @returns {Promise<string>} Resolved IP address
|
|
36
|
-
*/
|
|
37
|
-
async function resolveHostToIP(host) {
|
|
38
|
-
// If already IP, return as-is
|
|
39
|
-
if (isIP(host)) {
|
|
40
|
-
return host;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// If localhost, return as-is
|
|
44
|
-
if (host === 'localhost' || host === '127.0.0.1') {
|
|
45
|
-
return host;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const { address } = await dns.lookup(host);
|
|
50
|
-
return address;
|
|
51
|
-
} catch (err) {
|
|
52
|
-
// If DNS lookup fails, return original hostname
|
|
53
|
-
console.warn(`[StorageConnector] DNS lookup failed for ${host}, using original: ${err.message}`);
|
|
54
|
-
return host;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Parse storage reference to bucket and path
|
|
60
|
-
* Supports formats:
|
|
61
|
-
* - minio://bucket/path
|
|
62
|
-
* - internal://storage/bucket/path
|
|
63
|
-
*
|
|
64
|
-
* @param {string} ref - Storage reference
|
|
65
|
-
* @returns {{ bucket: string, path: string, fingerprint?: string }}
|
|
66
|
-
*/
|
|
67
|
-
function parseStorageRef(ref) {
|
|
68
|
-
if (!ref || typeof ref !== 'string') {
|
|
69
|
-
throw new Error('Invalid storage reference: must be a string');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// minio://bucket/path
|
|
73
|
-
let match = ref.match(/^minio:\/\/([^/]+)\/(.+)$/);
|
|
74
|
-
if (match) {
|
|
75
|
-
return { bucket: match[1], path: match[2] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// internal://storage/bucket/path
|
|
79
|
-
match = ref.match(/^internal:\/\/storage\/([^/]+)\/(.+)$/);
|
|
80
|
-
if (match) {
|
|
81
|
-
return { bucket: match[1], path: match[2] };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
throw new Error(`Invalid storage reference format: ${ref}`);
|
|
85
|
-
}
|
|
15
|
+
const runtimeCfg = require('./config');
|
|
86
16
|
|
|
87
17
|
/**
|
|
88
18
|
* Storage connector for MinIO with automatic fingerprinting
|
|
@@ -139,9 +69,12 @@ class StorageConnector {
|
|
|
139
69
|
* });
|
|
140
70
|
*/
|
|
141
71
|
constructor(config = {}) {
|
|
72
|
+
// Resolve runtime configuration for wrapper behavior
|
|
73
|
+
const resolved = runtimeCfg.resolve(config);
|
|
74
|
+
|
|
142
75
|
// Logger setup
|
|
143
76
|
this.logger = winston.createLogger({
|
|
144
|
-
level:
|
|
77
|
+
level: resolved.logLevel,
|
|
145
78
|
format: winston.format.combine(
|
|
146
79
|
winston.format.timestamp(),
|
|
147
80
|
winston.format.json()
|
|
@@ -151,33 +84,37 @@ class StorageConnector {
|
|
|
151
84
|
]
|
|
152
85
|
});
|
|
153
86
|
|
|
154
|
-
// MinIO client
|
|
155
|
-
|
|
156
|
-
endPoint:
|
|
157
|
-
port:
|
|
158
|
-
useSSL:
|
|
159
|
-
accessKey:
|
|
160
|
-
secretKey:
|
|
161
|
-
};
|
|
87
|
+
// Create StorageCore instance (handles MinIO client internally)
|
|
88
|
+
this.core = new StorageCore({
|
|
89
|
+
endPoint: resolved.endPoint,
|
|
90
|
+
port: resolved.port,
|
|
91
|
+
useSSL: resolved.useSSL,
|
|
92
|
+
accessKey: resolved.accessKey,
|
|
93
|
+
secretKey: resolved.secretKey
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Expose MinIO client for backward compatibility
|
|
97
|
+
this.client = this.core.client;
|
|
98
|
+
this.minioClient = this.core.client;
|
|
162
99
|
|
|
163
|
-
this.
|
|
164
|
-
this.defaultBucket = config.defaultBucket || process.env.MINIO_DEFAULT_BUCKET || 'api-storage';
|
|
100
|
+
this.defaultBucket = resolved.defaultBucket;
|
|
165
101
|
|
|
166
102
|
// Store config for access in tests
|
|
167
103
|
this.config = {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
104
|
+
endPoint: this.core.config.endPoint,
|
|
105
|
+
port: this.core.config.port,
|
|
106
|
+
useSSL: this.core.config.useSSL,
|
|
107
|
+
accessKey: this.core.config.accessKey,
|
|
108
|
+
secretKey: this.core.config.secretKey,
|
|
109
|
+
bucketName: this.validateBucketName(config.bucketName || resolved.bucketName),
|
|
110
|
+
cacheMaxSize: resolved.cacheMaxSize
|
|
171
111
|
};
|
|
172
112
|
|
|
173
|
-
// MinIO client
|
|
174
|
-
this.minioClient = this.client;
|
|
175
|
-
|
|
176
113
|
// Cache configuration
|
|
177
114
|
this.cacheEnabled = config.cacheEnabled !== false;
|
|
178
115
|
this.cache = new Map();
|
|
179
116
|
this.contentCache = new Map();
|
|
180
|
-
this.maxCacheSize =
|
|
117
|
+
this.maxCacheSize = resolved.maxCacheSize;
|
|
181
118
|
|
|
182
119
|
// Track initialization
|
|
183
120
|
this.initialized = false;
|
|
@@ -186,8 +123,8 @@ class StorageConnector {
|
|
|
186
123
|
this.sharedUrl = new SharedUrlAdapter(this);
|
|
187
124
|
|
|
188
125
|
this.logger.info('StorageConnector initialized', {
|
|
189
|
-
endpoint:
|
|
190
|
-
port:
|
|
126
|
+
endpoint: this.core.config.endPoint,
|
|
127
|
+
port: this.core.config.port,
|
|
191
128
|
defaultBucket: this.defaultBucket
|
|
192
129
|
});
|
|
193
130
|
}
|
|
@@ -211,39 +148,16 @@ class StorageConnector {
|
|
|
211
148
|
*/
|
|
212
149
|
async initialize() {
|
|
213
150
|
try {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
this.logger.info('StorageConnector: Resolved hostname to IP', {
|
|
220
|
-
original: originalEndpoint,
|
|
221
|
-
resolved: resolvedEndpoint
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Recreate MinIO client with resolved IP
|
|
225
|
-
const minioConfig = {
|
|
226
|
-
endPoint: resolvedEndpoint,
|
|
227
|
-
port: this.config.port,
|
|
228
|
-
useSSL: this.config.useSSL || false,
|
|
229
|
-
accessKey: this.config.accessKey,
|
|
230
|
-
secretKey: this.config.secretKey
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
this.client = new Minio.Client(minioConfig);
|
|
234
|
-
this.minioClient = this.client;
|
|
235
|
-
this.config.endPoint = resolvedEndpoint;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Check if default bucket exists
|
|
239
|
-
const bucketExists = await this.client.bucketExists(this.defaultBucket);
|
|
151
|
+
// Initialize StorageCore
|
|
152
|
+
await this.core.initialize();
|
|
153
|
+
|
|
154
|
+
// Ensure default bucket exists (using StorageCore)
|
|
155
|
+
const created = await this.core.ensureBucket(this.defaultBucket);
|
|
240
156
|
|
|
241
|
-
if (
|
|
242
|
-
// Create bucket with versioning
|
|
243
|
-
await this.client.makeBucket(this.defaultBucket, 'us-east-1');
|
|
157
|
+
if (created) {
|
|
244
158
|
this.logger.info(`Bucket ${this.defaultBucket} created`);
|
|
245
159
|
|
|
246
|
-
// Set bucket policy for read access
|
|
160
|
+
// Set bucket policy for read access (business-specific feature)
|
|
247
161
|
const policy = {
|
|
248
162
|
Version: '2012-10-17',
|
|
249
163
|
Statement: [
|
|
@@ -287,25 +201,8 @@ class StorageConnector {
|
|
|
287
201
|
* // Returns consistent hash for the object
|
|
288
202
|
*/
|
|
289
203
|
generateFingerprint(content) {
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
content = String(content);
|
|
293
|
-
} else if (typeof content === 'symbol') {
|
|
294
|
-
content = content.toString();
|
|
295
|
-
} else if (typeof content !== 'string' && !Buffer.isBuffer(content)) {
|
|
296
|
-
// Handle circular references
|
|
297
|
-
try {
|
|
298
|
-
content = JSON.stringify(content);
|
|
299
|
-
} catch (err) {
|
|
300
|
-
// If circular reference, use a deterministic string representation
|
|
301
|
-
content = '[Circular Reference]';
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return crypto
|
|
306
|
-
.createHash('sha256')
|
|
307
|
-
.update(content)
|
|
308
|
-
.digest('hex');
|
|
204
|
+
// Delegate to StorageCore
|
|
205
|
+
return this.core.calculateFingerprint(content);
|
|
309
206
|
}
|
|
310
207
|
|
|
311
208
|
/**
|
|
@@ -371,9 +268,9 @@ class StorageConnector {
|
|
|
371
268
|
? `${pathPrefix}/${fingerprint}.json`
|
|
372
269
|
: `${fingerprint}.json`;
|
|
373
270
|
|
|
374
|
-
// Check if object already exists (immutable)
|
|
375
|
-
|
|
376
|
-
|
|
271
|
+
// Check if object already exists (immutable) - using StorageCore
|
|
272
|
+
const exists = await this.core.objectExists(bucket, path);
|
|
273
|
+
if (exists) {
|
|
377
274
|
this.logger.debug(`Object already exists: ${bucket}/${path}`);
|
|
378
275
|
|
|
379
276
|
return {
|
|
@@ -383,16 +280,13 @@ class StorageConnector {
|
|
|
383
280
|
url: this.getPublicUrl(bucket, path),
|
|
384
281
|
existed: true
|
|
385
282
|
};
|
|
386
|
-
} catch (err) {
|
|
387
|
-
// Object doesn't exist, proceed with upload
|
|
388
283
|
}
|
|
389
284
|
|
|
390
|
-
// Upload to MinIO
|
|
391
|
-
await this.
|
|
285
|
+
// Upload to MinIO - using StorageCore
|
|
286
|
+
await this.core.putObject(
|
|
392
287
|
bucket,
|
|
393
288
|
path,
|
|
394
289
|
buffer,
|
|
395
|
-
buffer.length,
|
|
396
290
|
{
|
|
397
291
|
'Content-Type': 'application/json',
|
|
398
292
|
'x-amz-meta-fingerprint': fingerprint,
|
|
@@ -460,8 +354,8 @@ class StorageConnector {
|
|
|
460
354
|
return this.contentCache.get(cacheKey);
|
|
461
355
|
}
|
|
462
356
|
|
|
463
|
-
// Download from MinIO
|
|
464
|
-
const stream = await this.
|
|
357
|
+
// Download from MinIO - using StorageCore
|
|
358
|
+
const stream = await this.core.getObject(bucket, path);
|
|
465
359
|
|
|
466
360
|
// Collect stream data
|
|
467
361
|
const chunks = [];
|
|
@@ -470,16 +364,14 @@ class StorageConnector {
|
|
|
470
364
|
}
|
|
471
365
|
const buffer = Buffer.concat(chunks);
|
|
472
366
|
|
|
473
|
-
// Verify fingerprint if provided
|
|
367
|
+
// Verify fingerprint if provided - using StorageCore
|
|
474
368
|
if (expectedFingerprint) {
|
|
475
|
-
const actualFingerprint = this.
|
|
476
|
-
|
|
369
|
+
const actualFingerprint = this.core.calculateFingerprint(buffer);
|
|
477
370
|
if (actualFingerprint !== expectedFingerprint) {
|
|
478
371
|
throw new Error(
|
|
479
372
|
`Fingerprint mismatch! Expected: ${expectedFingerprint}, Got: ${actualFingerprint}`
|
|
480
373
|
);
|
|
481
374
|
}
|
|
482
|
-
|
|
483
375
|
this.logger.debug(`Fingerprint verified for ${bucket}/${path}`);
|
|
484
376
|
}
|
|
485
377
|
|
|
@@ -530,11 +422,8 @@ class StorageConnector {
|
|
|
530
422
|
try {
|
|
531
423
|
bucket = bucket || this.defaultBucket;
|
|
532
424
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
path,
|
|
536
|
-
expiry
|
|
537
|
-
);
|
|
425
|
+
// Use StorageCore
|
|
426
|
+
const url = await this.core.getPresignedUrl(bucket, path, expiry);
|
|
538
427
|
|
|
539
428
|
this.logger.debug(`Generated presigned URL for ${bucket}/${path}`);
|
|
540
429
|
return url;
|
|
@@ -570,20 +459,25 @@ class StorageConnector {
|
|
|
570
459
|
try {
|
|
571
460
|
bucket = bucket || this.defaultBucket;
|
|
572
461
|
|
|
462
|
+
// Use StorageCore to list objects
|
|
463
|
+
const objectNames = await this.core.listByPrefix(bucket, prefix, true);
|
|
464
|
+
|
|
465
|
+
// Get metadata for each object
|
|
573
466
|
const objects = [];
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
467
|
+
for (const name of objectNames) {
|
|
468
|
+
try {
|
|
469
|
+
const stat = await this.core.statObject(bucket, name);
|
|
470
|
+
objects.push({
|
|
471
|
+
name,
|
|
472
|
+
size: stat.size,
|
|
473
|
+
lastModified: stat.lastModified,
|
|
474
|
+
fingerprint: stat.metaData?.['x-amz-meta-fingerprint'],
|
|
475
|
+
uploadedAt: stat.metaData?.['x-amz-meta-uploaded-at']
|
|
476
|
+
});
|
|
477
|
+
} catch (err) {
|
|
478
|
+
// Skip objects that can't be stat'd
|
|
479
|
+
this.logger.warn(`Failed to stat object ${name}`, err);
|
|
480
|
+
}
|
|
587
481
|
}
|
|
588
482
|
|
|
589
483
|
return objects;
|
|
@@ -614,7 +508,8 @@ class StorageConnector {
|
|
|
614
508
|
try {
|
|
615
509
|
bucket = bucket || this.defaultBucket;
|
|
616
510
|
|
|
617
|
-
|
|
511
|
+
// Use StorageCore
|
|
512
|
+
await this.core.deleteObject(bucket, path);
|
|
618
513
|
this.logger.warn(`Deleted object: ${bucket}/${path}`);
|
|
619
514
|
|
|
620
515
|
// Clear from cache
|
|
@@ -688,10 +583,10 @@ class StorageConnector {
|
|
|
688
583
|
*/
|
|
689
584
|
async ensureBucket(bucketName) {
|
|
690
585
|
try {
|
|
691
|
-
|
|
586
|
+
// Use StorageCore
|
|
587
|
+
const created = await this.core.ensureBucket(bucketName);
|
|
692
588
|
|
|
693
|
-
if (
|
|
694
|
-
await this.client.makeBucket(bucketName, 'us-east-1');
|
|
589
|
+
if (created) {
|
|
695
590
|
this.logger.info(`Bucket ${bucketName} created`);
|
|
696
591
|
}
|
|
697
592
|
|
|
@@ -921,49 +816,6 @@ class StorageConnector {
|
|
|
921
816
|
* @property {string} [uploadedAt] - Upload timestamp
|
|
922
817
|
*/
|
|
923
818
|
|
|
924
|
-
/**
|
|
925
|
-
* Download file from storage reference
|
|
926
|
-
*
|
|
927
|
-
* @param {string} ref - Storage reference (minio://bucket/path)
|
|
928
|
-
* @returns {Promise<Buffer>} File content
|
|
929
|
-
*
|
|
930
|
-
* @example
|
|
931
|
-
* const buffer = await storage.downloadFromRef('minio://workflow/inputs/wf-123/doc.pdf');
|
|
932
|
-
*/
|
|
933
|
-
StorageConnector.prototype.downloadFromRef = async function(ref) {
|
|
934
|
-
const { bucket, path } = parseStorageRef(ref);
|
|
935
|
-
|
|
936
|
-
// Download from MinIO
|
|
937
|
-
const stream = await this.client.getObject(bucket, path);
|
|
938
|
-
|
|
939
|
-
// Collect stream data
|
|
940
|
-
const chunks = [];
|
|
941
|
-
for await (const chunk of stream) {
|
|
942
|
-
chunks.push(chunk);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
return Buffer.concat(chunks);
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Check if storage reference is valid format
|
|
950
|
-
* @param {string} ref - Reference to check
|
|
951
|
-
* @returns {boolean}
|
|
952
|
-
*/
|
|
953
|
-
StorageConnector.isValidRef = function(ref) {
|
|
954
|
-
try {
|
|
955
|
-
parseStorageRef(ref);
|
|
956
|
-
return true;
|
|
957
|
-
} catch {
|
|
958
|
-
return false;
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
* Parse storage reference (static helper)
|
|
964
|
-
*/
|
|
965
|
-
StorageConnector.parseRef = parseStorageRef;
|
|
966
|
-
|
|
967
819
|
// Export main class
|
|
968
820
|
module.exports = StorageConnector;
|
|
969
821
|
|
|
@@ -4,34 +4,16 @@
|
|
|
4
4
|
* Skrývá implementační detaily před službami
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const runtimeCfg = require('./config');
|
|
9
|
+
|
|
7
10
|
class InternalUrlAdapter {
|
|
8
11
|
constructor(config = {}) {
|
|
9
|
-
this.environment = config.environment
|
|
10
|
-
this.dockerNetwork =
|
|
12
|
+
this.environment = runtimeCfg.get('nodeEnv', config.environment);
|
|
13
|
+
this.dockerNetwork = runtimeCfg.get('dockerNetwork', config.dockerNetwork);
|
|
11
14
|
|
|
12
15
|
// Mapování služeb na jejich interní adresy
|
|
13
|
-
this.serviceMap = {
|
|
14
|
-
storage: {
|
|
15
|
-
docker: 'api_services_storage:9000',
|
|
16
|
-
local: 'localhost:9000'
|
|
17
|
-
},
|
|
18
|
-
registry: {
|
|
19
|
-
docker: 'api_registry:8080',
|
|
20
|
-
local: 'localhost:8080'
|
|
21
|
-
},
|
|
22
|
-
monitoring: {
|
|
23
|
-
docker: 'api_monitoring:3000',
|
|
24
|
-
local: 'localhost:3000'
|
|
25
|
-
},
|
|
26
|
-
mq: {
|
|
27
|
-
docker: 'api_services_rabbit:5672',
|
|
28
|
-
local: 'localhost:5672'
|
|
29
|
-
},
|
|
30
|
-
cache: {
|
|
31
|
-
docker: 'api_node_cache:6379',
|
|
32
|
-
local: 'localhost:6379'
|
|
33
|
-
}
|
|
34
|
-
};
|
|
16
|
+
this.serviceMap = { ...runtimeCfg.get('internalUrlServiceMap') };
|
|
35
17
|
|
|
36
18
|
// Rozšiřitelné přes config
|
|
37
19
|
if (config.services) {
|
|
@@ -91,11 +73,14 @@ class InternalUrlAdapter {
|
|
|
91
73
|
*/
|
|
92
74
|
isRunningInDocker() {
|
|
93
75
|
// Několik způsobů detekce Docker kontejneru
|
|
76
|
+
const dockerEnv = runtimeCfg.get('dockerEnv');
|
|
77
|
+
const dockerContainer = runtimeCfg.get('dockerContainer');
|
|
78
|
+
const hostname = runtimeCfg.get('hostname');
|
|
94
79
|
return !!(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
(
|
|
80
|
+
dockerEnv ||
|
|
81
|
+
dockerContainer ||
|
|
82
|
+
(fs.existsSync('/.dockerenv')) ||
|
|
83
|
+
(hostname && hostname.length === 12) // Docker container ID
|
|
99
84
|
);
|
|
100
85
|
}
|
|
101
86
|
|
|
@@ -9,6 +9,8 @@ describe('InternalUrlAdapter @unit', () => {
|
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
adapter = new InternalUrlAdapter();
|
|
12
|
+
// Tests run inside a Docker container; force local-mode behavior unless explicitly enabled.
|
|
13
|
+
adapter.isRunningInDocker = jest.fn(() => false);
|
|
12
14
|
});
|
|
13
15
|
|
|
14
16
|
describe('URL Resolution', () => {
|
|
@@ -202,6 +204,8 @@ describe('InternalUrlAdapter @unit', () => {
|
|
|
202
204
|
}
|
|
203
205
|
});
|
|
204
206
|
|
|
207
|
+
// Tests run inside Docker; force local-mode behavior for this adapter instance.
|
|
208
|
+
customAdapter.isRunningInDocker = jest.fn(() => false);
|
|
205
209
|
customAdapter.dockerNetwork = false;
|
|
206
210
|
const resolved = customAdapter.resolve('internal://my-service/api');
|
|
207
211
|
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
const StorageConnector = require('../../src/index');
|
|
7
7
|
const Minio = require('minio');
|
|
8
8
|
|
|
9
|
+
// Minimal required MinIO config for tests (all external I/O is mocked)
|
|
10
|
+
const MINIO_CONFIG = {
|
|
11
|
+
endPoint: 'localhost',
|
|
12
|
+
port: 9000,
|
|
13
|
+
accessKey: 'test',
|
|
14
|
+
secretKey: 'test',
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
// Mock MinIO client
|
|
10
18
|
jest.mock('minio');
|
|
11
19
|
|
|
@@ -321,15 +329,15 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
321
329
|
|
|
322
330
|
describe('Internal URL Generation', () => {
|
|
323
331
|
test('should generate abstract internal URL', () => {
|
|
324
|
-
storage = new StorageConnector();
|
|
332
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
325
333
|
|
|
326
334
|
const url = storage.getInternalUrl('bucket', 'path/to/object');
|
|
327
335
|
expect(url).toBe('internal://storage/bucket/path/to/object');
|
|
328
336
|
});
|
|
329
337
|
|
|
330
338
|
test('should generate consistent internal URLs', () => {
|
|
331
|
-
const storage1 = new StorageConnector();
|
|
332
|
-
const storage2 = new StorageConnector();
|
|
339
|
+
const storage1 = new StorageConnector(MINIO_CONFIG);
|
|
340
|
+
const storage2 = new StorageConnector(MINIO_CONFIG);
|
|
333
341
|
|
|
334
342
|
const url1 = storage1.getInternalUrl('bucket', 'object');
|
|
335
343
|
const url2 = storage2.getInternalUrl('bucket', 'object');
|
|
@@ -341,7 +349,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
341
349
|
|
|
342
350
|
describe('Bucket Operations', () => {
|
|
343
351
|
beforeEach(() => {
|
|
344
|
-
storage = new StorageConnector();
|
|
352
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
345
353
|
});
|
|
346
354
|
|
|
347
355
|
test('should ensure bucket exists', async () => {
|
|
@@ -373,7 +381,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
373
381
|
|
|
374
382
|
describe('Cache Operations', () => {
|
|
375
383
|
test('should clear all caches', () => {
|
|
376
|
-
storage = new StorageConnector();
|
|
384
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
377
385
|
|
|
378
386
|
// Add items to caches
|
|
379
387
|
storage.cache.set('key1', 'value1');
|
|
@@ -389,7 +397,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
389
397
|
});
|
|
390
398
|
|
|
391
399
|
test('should handle cache size limits', () => {
|
|
392
|
-
storage = new StorageConnector({ cacheMaxSize: 2 });
|
|
400
|
+
storage = new StorageConnector({ ...MINIO_CONFIG, cacheMaxSize: 2 });
|
|
393
401
|
|
|
394
402
|
storage.addToCache('key1', 'value1');
|
|
395
403
|
storage.addToCache('key2', 'value2');
|