@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.
Files changed (39) hide show
  1. package/API.md +618 -0
  2. package/README.md +341 -0
  3. package/SHARED_URL_ADDRESSING.md +258 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +213 -0
  7. package/coverage/coverage-final.json +3 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +131 -0
  10. package/coverage/index.js.html +1579 -0
  11. package/coverage/internal-url-adapter.js.html +604 -0
  12. package/coverage/lcov-report/base.css +224 -0
  13. package/coverage/lcov-report/block-navigation.js +87 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/index.html +131 -0
  16. package/coverage/lcov-report/index.js.html +1579 -0
  17. package/coverage/lcov-report/internal-url-adapter.js.html +604 -0
  18. package/coverage/lcov-report/prettify.css +1 -0
  19. package/coverage/lcov-report/prettify.js +2 -0
  20. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  21. package/coverage/lcov-report/sorter.js +210 -0
  22. package/coverage/lcov.info +434 -0
  23. package/coverage/prettify.css +1 -0
  24. package/coverage/prettify.js +2 -0
  25. package/coverage/sort-arrow-sprite.png +0 -0
  26. package/coverage/sorter.js +210 -0
  27. package/jest.config.js +13 -0
  28. package/jest.integration.config.js +9 -0
  29. package/package.json +33 -0
  30. package/src/index.js +853 -0
  31. package/src/internal-url-adapter.js +174 -0
  32. package/src/sharedUrlAdapter.js +258 -0
  33. package/test/component/storage.component.test.js +363 -0
  34. package/test/integration/setup.js +3 -0
  35. package/test/integration/storage.integration.test.js +224 -0
  36. package/test/unit/internal-url-adapter.test.js +211 -0
  37. package/test/unit/legacy.storage.test.js.bak +614 -0
  38. package/test/unit/storage.extended.unit.test.js +435 -0
  39. 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