@onlineapps/conn-base-storage 1.0.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/API.md +618 -0
- package/README.md +341 -0
- package/SHARED_URL_ADDRESSING.md +258 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +213 -0
- package/coverage/coverage-final.json +3 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/index.js.html +1579 -0
- package/coverage/internal-url-adapter.js.html +604 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +131 -0
- package/coverage/lcov-report/index.js.html +1579 -0
- package/coverage/lcov-report/internal-url-adapter.js.html +604 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +434 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/jest.config.js +13 -0
- package/jest.integration.config.js +9 -0
- package/package.json +33 -0
- package/src/index.js +853 -0
- package/src/internal-url-adapter.js +174 -0
- package/src/sharedUrlAdapter.js +258 -0
- package/test/component/storage.component.test.js +363 -0
- package/test/integration/setup.js +3 -0
- package/test/integration/storage.integration.test.js +224 -0
- package/test/unit/internal-url-adapter.test.js +211 -0
- package/test/unit/legacy.storage.test.js.bak +614 -0
- package/test/unit/storage.extended.unit.test.js +435 -0
- package/test/unit/storage.unit.test.js +373 -0
package/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Connector Storage - Sdílený přístup k MinIO
|
|
2
|
+
|
|
3
|
+
Jednotná knihovna pro všechny služby pro práci s api_services_storage (MinIO).
|
|
4
|
+
|
|
5
|
+
## Proč přímý přístup (ne MQ wrapper)?
|
|
6
|
+
|
|
7
|
+
1. **Performance** - MinIO je optimalizované pro přímý přístup
|
|
8
|
+
2. **Standard** - S3 API je průmyslový standard
|
|
9
|
+
3. **Efektivita** - Streaming velkých souborů
|
|
10
|
+
4. **Best Practice** - Object storage se vždy používá přímo
|
|
11
|
+
|
|
12
|
+
## Instalace
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @shared/connector-storage
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Použití
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
const StorageConnector = require('@shared/connector-storage');
|
|
22
|
+
|
|
23
|
+
const storage = new StorageConnector({
|
|
24
|
+
endpoint: process.env.MINIO_HOST || 'api_services_storage',
|
|
25
|
+
port: 9000,
|
|
26
|
+
accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
|
|
27
|
+
secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Upload s fingerprintem
|
|
31
|
+
const result = await storage.uploadWithFingerprint(
|
|
32
|
+
'registry',
|
|
33
|
+
content,
|
|
34
|
+
'specs/invoicing'
|
|
35
|
+
);
|
|
36
|
+
// Returns: { path: 'specs/invoicing/abc123.json', fingerprint: 'abc123', size: 1234 }
|
|
37
|
+
|
|
38
|
+
// Download s ověřením
|
|
39
|
+
const content = await storage.downloadWithVerification(
|
|
40
|
+
'registry',
|
|
41
|
+
'specs/invoicing/abc123.json',
|
|
42
|
+
'abc123' // expected fingerprint
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Get pre-signed URL pro externí přístup
|
|
46
|
+
const url = await storage.getPresignedUrl(
|
|
47
|
+
'registry',
|
|
48
|
+
'specs/invoicing/abc123.json',
|
|
49
|
+
3600 // expiration in seconds
|
|
50
|
+
);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### Core Methods
|
|
56
|
+
|
|
57
|
+
#### `uploadWithFingerprint(bucket, content, basePath)`
|
|
58
|
+
Nahraje obsah a automaticky přidá fingerprint do názvu souboru.
|
|
59
|
+
|
|
60
|
+
#### `downloadWithVerification(bucket, path, expectedFingerprint)`
|
|
61
|
+
Stáhne soubor a ověří jeho fingerprint.
|
|
62
|
+
|
|
63
|
+
#### `exists(bucket, path)`
|
|
64
|
+
Zkontroluje existenci souboru.
|
|
65
|
+
|
|
66
|
+
#### `getPresignedUrl(bucket, path, expiry)`
|
|
67
|
+
Vytvoří dočasnou URL pro externí přístup.
|
|
68
|
+
|
|
69
|
+
### Helper Methods
|
|
70
|
+
|
|
71
|
+
#### `calculateFingerprint(content)`
|
|
72
|
+
Spočítá SHA256 fingerprint obsahu.
|
|
73
|
+
|
|
74
|
+
#### `listByPrefix(bucket, prefix)`
|
|
75
|
+
Vypíše všechny objekty s daným prefixem.
|
|
76
|
+
|
|
77
|
+
## Implementace
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// connector-storage/index.js
|
|
81
|
+
const Minio = require('minio');
|
|
82
|
+
const crypto = require('crypto');
|
|
83
|
+
|
|
84
|
+
class StorageConnector {
|
|
85
|
+
constructor(config = {}) {
|
|
86
|
+
this.client = new Minio.Client({
|
|
87
|
+
endPoint: config.endpoint || 'api_services_storage',
|
|
88
|
+
port: config.port || 9000,
|
|
89
|
+
useSSL: config.useSSL || false,
|
|
90
|
+
accessKey: config.accessKey || 'minioadmin',
|
|
91
|
+
secretKey: config.secretKey || 'minioadmin'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Ensure buckets exist
|
|
95
|
+
this.initBuckets();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async initBuckets() {
|
|
99
|
+
const buckets = ['registry', 'workflow', 'services'];
|
|
100
|
+
for (const bucket of buckets) {
|
|
101
|
+
const exists = await this.client.bucketExists(bucket);
|
|
102
|
+
if (!exists) {
|
|
103
|
+
await this.client.makeBucket(bucket);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
calculateFingerprint(content) {
|
|
109
|
+
return crypto
|
|
110
|
+
.createHash('sha256')
|
|
111
|
+
.update(content)
|
|
112
|
+
.digest('hex');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async uploadWithFingerprint(bucket, content, basePath) {
|
|
116
|
+
const fingerprint = this.calculateFingerprint(content);
|
|
117
|
+
const path = `${basePath}/${fingerprint}.json`;
|
|
118
|
+
|
|
119
|
+
await this.client.putObject(
|
|
120
|
+
bucket,
|
|
121
|
+
path,
|
|
122
|
+
content,
|
|
123
|
+
content.length,
|
|
124
|
+
{
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
'X-Fingerprint': fingerprint
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
path,
|
|
132
|
+
fingerprint,
|
|
133
|
+
size: content.length,
|
|
134
|
+
bucket
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async downloadWithVerification(bucket, path, expectedFingerprint) {
|
|
139
|
+
const stream = await this.client.getObject(bucket, path);
|
|
140
|
+
const chunks = [];
|
|
141
|
+
|
|
142
|
+
for await (const chunk of stream) {
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const content = Buffer.concat(chunks).toString();
|
|
147
|
+
const actualFingerprint = this.calculateFingerprint(content);
|
|
148
|
+
|
|
149
|
+
if (expectedFingerprint && actualFingerprint !== expectedFingerprint) {
|
|
150
|
+
throw new Error(`Fingerprint mismatch! Expected: ${expectedFingerprint}, Got: ${actualFingerprint}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return content;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async exists(bucket, path) {
|
|
157
|
+
try {
|
|
158
|
+
await this.client.statObject(bucket, path);
|
|
159
|
+
return true;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
if (err.code === 'NotFound') {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async getPresignedUrl(bucket, path, expiry = 3600) {
|
|
169
|
+
return await this.client.presignedGetObject(bucket, path, expiry);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async listByPrefix(bucket, prefix) {
|
|
173
|
+
const objects = [];
|
|
174
|
+
const stream = this.client.listObjectsV2(bucket, prefix);
|
|
175
|
+
|
|
176
|
+
for await (const obj of stream) {
|
|
177
|
+
objects.push(obj);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return objects;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = StorageConnector;
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Použití v Registry
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// api_services_registry/src/services/specPublisher.js
|
|
191
|
+
const StorageConnector = require('@shared/connector-storage');
|
|
192
|
+
|
|
193
|
+
class SpecPublisher {
|
|
194
|
+
constructor() {
|
|
195
|
+
this.storage = new StorageConnector();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async publishSpec(serviceName, spec) {
|
|
199
|
+
// Upload spec to MinIO
|
|
200
|
+
const result = await this.storage.uploadWithFingerprint(
|
|
201
|
+
'registry',
|
|
202
|
+
JSON.stringify(spec),
|
|
203
|
+
`specs/${serviceName}`
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Publish event with reference
|
|
207
|
+
await this.mq.publish('registry.changes', {
|
|
208
|
+
type: 'SPEC_PUBLISHED',
|
|
209
|
+
service: serviceName,
|
|
210
|
+
fingerprint: result.fingerprint,
|
|
211
|
+
path: result.path,
|
|
212
|
+
bucket: result.bucket
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Použití ve službě
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// services/invoicing/src/index.js
|
|
224
|
+
const StorageConnector = require('@shared/connector-storage');
|
|
225
|
+
|
|
226
|
+
class InvoicingService {
|
|
227
|
+
constructor() {
|
|
228
|
+
this.storage = new StorageConnector();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async saveInvoice(invoice) {
|
|
232
|
+
// Save invoice PDF to storage
|
|
233
|
+
const result = await this.storage.uploadWithFingerprint(
|
|
234
|
+
'services',
|
|
235
|
+
invoice.pdfBuffer,
|
|
236
|
+
'invoicing/invoices'
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Return reference, not content
|
|
240
|
+
return {
|
|
241
|
+
invoiceId: invoice.id,
|
|
242
|
+
pdfUrl: result.path,
|
|
243
|
+
fingerprint: result.fingerprint,
|
|
244
|
+
size: result.size
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async getInvoice(path, fingerprint) {
|
|
249
|
+
// Download and verify
|
|
250
|
+
return await this.storage.downloadWithVerification(
|
|
251
|
+
'services',
|
|
252
|
+
path,
|
|
253
|
+
fingerprint
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Bezpečnost
|
|
260
|
+
|
|
261
|
+
### Přístupová práva (buckets)
|
|
262
|
+
- `registry/` - Read-only pro služby, write pro Registry
|
|
263
|
+
- `workflow/` - Read/write pro workflow komponenty
|
|
264
|
+
- `services/` - Read/write pro služby (každá má svůj prefix)
|
|
265
|
+
|
|
266
|
+
### Network isolation
|
|
267
|
+
- MinIO běží pouze v internal Docker network
|
|
268
|
+
- Externí přístup pouze přes pre-signed URLs
|
|
269
|
+
|
|
270
|
+
## Testing
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// test/connector-storage.test.js
|
|
274
|
+
const StorageConnector = require('../index');
|
|
275
|
+
|
|
276
|
+
describe('StorageConnector', () => {
|
|
277
|
+
let storage;
|
|
278
|
+
|
|
279
|
+
beforeAll(() => {
|
|
280
|
+
storage = new StorageConnector({
|
|
281
|
+
endpoint: 'localhost',
|
|
282
|
+
port: 33025 // Host port
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('upload and download with fingerprint', async () => {
|
|
287
|
+
const content = JSON.stringify({ test: 'data' });
|
|
288
|
+
|
|
289
|
+
// Upload
|
|
290
|
+
const upload = await storage.uploadWithFingerprint(
|
|
291
|
+
'test',
|
|
292
|
+
content,
|
|
293
|
+
'specs/test'
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
expect(upload.fingerprint).toBeDefined();
|
|
297
|
+
|
|
298
|
+
// Download and verify
|
|
299
|
+
const downloaded = await storage.downloadWithVerification(
|
|
300
|
+
'test',
|
|
301
|
+
upload.path,
|
|
302
|
+
upload.fingerprint
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
expect(downloaded).toBe(content);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Environment Variables
|
|
311
|
+
|
|
312
|
+
```env
|
|
313
|
+
# MinIO Configuration
|
|
314
|
+
MINIO_HOST=api_services_storage
|
|
315
|
+
MINIO_PORT=9000
|
|
316
|
+
MINIO_ACCESS_KEY=minioadmin
|
|
317
|
+
MINIO_SECRET_KEY=minioadmin
|
|
318
|
+
MINIO_USE_SSL=false
|
|
319
|
+
|
|
320
|
+
# From host (for testing)
|
|
321
|
+
MINIO_HOST=localhost
|
|
322
|
+
MINIO_PORT=33025
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Výhody tohoto přístupu
|
|
326
|
+
|
|
327
|
+
1. **Jednotné API** - Všechny služby používají stejnou knihovnu
|
|
328
|
+
2. **Fingerprint built-in** - Automatická správa immutable obsahu
|
|
329
|
+
3. **Verification** - Ověření integrity při stahování
|
|
330
|
+
4. **Pre-signed URLs** - Bezpečný externí přístup
|
|
331
|
+
5. **Efektivní** - Přímý streaming, žádné MQ overhead
|
|
332
|
+
6. **Standard** - S3 API je průmyslový standard
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
Toto je správný způsob práce s object storage v microservices architektuře.
|
|
337
|
+
## 📚 Documentation
|
|
338
|
+
|
|
339
|
+
- [Complete Connectors Documentation](../../../docs/modules/connector.md)
|
|
340
|
+
- [Testing Standards](../../../docs/modules/connector.md#testing-standards)
|
|
341
|
+
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Shared URL Addressing System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The shared URL addressing system provides a unified way to reference resources across the OA Drive platform using the `shared://` protocol. This abstraction layer allows both backend services and frontend applications to use the same addressing scheme for accessing resources.
|
|
6
|
+
|
|
7
|
+
## URL Format
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
shared://<namespace>/<path>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Available Namespaces
|
|
14
|
+
|
|
15
|
+
- **registry** - Service specifications and metadata
|
|
16
|
+
- **workflow** - Workflow cookbooks and definitions
|
|
17
|
+
- **storage** - General file storage
|
|
18
|
+
- **cache** - Temporary cache storage
|
|
19
|
+
- **logs** - Centralized logging storage
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
### Registry Specifications
|
|
24
|
+
```javascript
|
|
25
|
+
// Service specification
|
|
26
|
+
shared://registry/hello-service/v1.0.0/spec.json
|
|
27
|
+
|
|
28
|
+
// Service metadata
|
|
29
|
+
shared://registry/hello-service/v1.0.0/metadata.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Workflow Cookbooks
|
|
33
|
+
```javascript
|
|
34
|
+
// Workflow definition
|
|
35
|
+
shared://workflow/cookbook-123/definition.json
|
|
36
|
+
|
|
37
|
+
// Workflow step
|
|
38
|
+
shared://workflow/cookbook-123/steps/step-1.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### General Storage
|
|
42
|
+
```javascript
|
|
43
|
+
// User upload
|
|
44
|
+
shared://storage/users/user-123/profile-photo.jpg
|
|
45
|
+
|
|
46
|
+
// Document storage
|
|
47
|
+
shared://storage/documents/report-2025.pdf
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Usage
|
|
51
|
+
|
|
52
|
+
### Upload Content
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const storage = new StorageConnector(config);
|
|
56
|
+
await storage.initialize();
|
|
57
|
+
|
|
58
|
+
// Upload to shared URL
|
|
59
|
+
const result = await storage.sharedUrl.upload(
|
|
60
|
+
'shared://registry/my-service/v1.0.0/spec.json',
|
|
61
|
+
JSON.stringify(serviceSpec)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
console.log(`Uploaded with fingerprint: ${result.fingerprint}`);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Download Content
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// Download from shared URL
|
|
71
|
+
const content = await storage.sharedUrl.download(
|
|
72
|
+
'shared://registry/my-service/v1.0.0/spec.json'
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Download with fingerprint verification
|
|
76
|
+
const verified = await storage.sharedUrl.download(
|
|
77
|
+
'shared://registry/my-service/v1.0.0/spec.json',
|
|
78
|
+
expectedFingerprint
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Generate HTTP URL
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Get presigned HTTP URL for external access
|
|
86
|
+
const httpUrl = await storage.sharedUrl.toHttpUrl(
|
|
87
|
+
'shared://registry/my-service/v1.0.0/spec.json',
|
|
88
|
+
3600 // expiry in seconds
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### List Resources
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// List all services in registry
|
|
96
|
+
const services = await storage.sharedUrl.list('shared://registry/');
|
|
97
|
+
|
|
98
|
+
// List workflow cookbooks
|
|
99
|
+
const cookbooks = await storage.sharedUrl.list('shared://workflow/');
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Check Existence
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const exists = await storage.sharedUrl.exists(
|
|
106
|
+
'shared://registry/my-service/v1.0.0/spec.json'
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Get Metadata
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const metadata = await storage.sharedUrl.getMetadata(
|
|
114
|
+
'shared://registry/my-service/v1.0.0/spec.json'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
console.log(`Size: ${metadata.size} bytes`);
|
|
118
|
+
console.log(`Last Modified: ${metadata.lastModified}`);
|
|
119
|
+
console.log(`ETag: ${metadata.etag}`);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Frontend Usage
|
|
123
|
+
|
|
124
|
+
Frontend applications can use the same shared URLs and convert them to HTTP URLs for browser access:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// In React/Vue/Angular component
|
|
128
|
+
const sharedUrl = 'shared://registry/my-service/v1.0.0/spec.json';
|
|
129
|
+
|
|
130
|
+
// Get HTTP URL for browser
|
|
131
|
+
const httpUrl = await api.getPresignedUrl(sharedUrl);
|
|
132
|
+
|
|
133
|
+
// Use in component
|
|
134
|
+
<img src={httpUrl} />
|
|
135
|
+
<a href={httpUrl}>Download Spec</a>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Benefits
|
|
139
|
+
|
|
140
|
+
1. **Unified Addressing** - Same URLs work for backend and frontend
|
|
141
|
+
2. **Location Independence** - Resources can be moved without changing code
|
|
142
|
+
3. **Access Control** - Centralized permission management
|
|
143
|
+
4. **Caching** - Built-in caching support
|
|
144
|
+
5. **Fingerprinting** - Automatic content verification
|
|
145
|
+
6. **Presigned URLs** - Secure temporary access for external clients
|
|
146
|
+
|
|
147
|
+
## Implementation Details
|
|
148
|
+
|
|
149
|
+
### Backend Mapping
|
|
150
|
+
|
|
151
|
+
The shared URL adapter maps namespaces to MinIO buckets:
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
{
|
|
155
|
+
'registry': 'registry', // Service specifications
|
|
156
|
+
'workflow': 'workflow', // Workflow definitions
|
|
157
|
+
'storage': 'api-storage', // General storage
|
|
158
|
+
'cache': 'cache', // Temporary cache
|
|
159
|
+
'logs': 'logs' // Centralized logs
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Security
|
|
164
|
+
|
|
165
|
+
- All uploads include automatic fingerprinting
|
|
166
|
+
- Downloads can verify fingerprints
|
|
167
|
+
- Presigned URLs expire after specified time
|
|
168
|
+
- Access control at bucket level
|
|
169
|
+
|
|
170
|
+
### Performance
|
|
171
|
+
|
|
172
|
+
- Built-in caching for frequently accessed resources
|
|
173
|
+
- Direct MinIO access for optimal performance
|
|
174
|
+
- Lazy loading support for large resources
|
|
175
|
+
|
|
176
|
+
## Migration Guide
|
|
177
|
+
|
|
178
|
+
### From Direct MinIO Access
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
// Before
|
|
182
|
+
const url = await minioClient.presignedUrl(
|
|
183
|
+
'GET',
|
|
184
|
+
'registry',
|
|
185
|
+
'service/spec.json',
|
|
186
|
+
86400
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// After
|
|
190
|
+
const url = await storage.sharedUrl.toHttpUrl(
|
|
191
|
+
'shared://registry/service/spec.json',
|
|
192
|
+
86400
|
|
193
|
+
);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### From File Paths
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Before
|
|
200
|
+
const spec = fs.readFileSync('./specs/service.json');
|
|
201
|
+
|
|
202
|
+
// After
|
|
203
|
+
const spec = await storage.sharedUrl.download(
|
|
204
|
+
'shared://registry/service/spec.json'
|
|
205
|
+
);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Best Practices
|
|
209
|
+
|
|
210
|
+
1. **Use Namespaces Appropriately**
|
|
211
|
+
- registry: Only for service specifications
|
|
212
|
+
- workflow: Only for workflow definitions
|
|
213
|
+
- storage: General purpose files
|
|
214
|
+
|
|
215
|
+
2. **Include Versions in Paths**
|
|
216
|
+
```
|
|
217
|
+
shared://registry/service/v1.0.0/spec.json ✓
|
|
218
|
+
shared://registry/service/spec.json ✗
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
3. **Verify Fingerprints for Critical Data**
|
|
222
|
+
```javascript
|
|
223
|
+
await storage.sharedUrl.download(url, expectedFingerprint);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
4. **Set Appropriate Expiry Times**
|
|
227
|
+
- Public resources: 86400 (24 hours)
|
|
228
|
+
- Private resources: 3600 (1 hour)
|
|
229
|
+
- Sensitive data: 300 (5 minutes)
|
|
230
|
+
|
|
231
|
+
## Troubleshooting
|
|
232
|
+
|
|
233
|
+
### Invalid URL Format
|
|
234
|
+
```
|
|
235
|
+
Error: Invalid shared URL format. Must start with 'shared://'
|
|
236
|
+
```
|
|
237
|
+
Solution: Ensure URL starts with `shared://`
|
|
238
|
+
|
|
239
|
+
### Unknown Namespace
|
|
240
|
+
```
|
|
241
|
+
Error: Unknown namespace 'custom'. Available: registry, workflow, storage, cache, logs
|
|
242
|
+
```
|
|
243
|
+
Solution: Use one of the predefined namespaces
|
|
244
|
+
|
|
245
|
+
### Object Not Found
|
|
246
|
+
```
|
|
247
|
+
Error: The specified key does not exist
|
|
248
|
+
```
|
|
249
|
+
Solution: Check if the resource exists using `exists()` method
|
|
250
|
+
|
|
251
|
+
## Future Enhancements
|
|
252
|
+
|
|
253
|
+
- [ ] Custom namespace registration
|
|
254
|
+
- [ ] URL aliases and shortcuts
|
|
255
|
+
- [ ] Batch operations support
|
|
256
|
+
- [ ] Real-time change notifications
|
|
257
|
+
- [ ] Cross-region replication
|
|
258
|
+
- [ ] CDN integration for global access
|