@things-factory/integration-sftp 4.3.660 → 4.3.668
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/CHANGELOG.md +369 -0
- package/dist-server/controllers/herbalife/herbalife.js +9 -5
- package/dist-server/controllers/herbalife/herbalife.js.map +1 -1
- package/dist-server/controllers/index.js +1 -0
- package/dist-server/controllers/index.js.map +1 -1
- package/dist-server/controllers/sftp-api/index.js +7 -0
- package/dist-server/controllers/sftp-api/index.js.map +1 -1
- package/dist-server/controllers/yltc/apis/create-yltc-inventory-report.js +40 -0
- package/dist-server/controllers/yltc/apis/create-yltc-inventory-report.js.map +1 -0
- package/dist-server/controllers/yltc/apis/echo.js +19 -0
- package/dist-server/controllers/yltc/apis/echo.js.map +1 -0
- package/dist-server/controllers/yltc/apis/index.js +19 -0
- package/dist-server/controllers/yltc/apis/index.js.map +1 -0
- package/dist-server/controllers/yltc/index.js +34 -0
- package/dist-server/controllers/yltc/index.js.map +1 -0
- package/dist-server/controllers/yltc/platform-action.js +30 -0
- package/dist-server/controllers/yltc/platform-action.js.map +1 -0
- package/dist-server/controllers/yltc/yltc.js +71 -0
- package/dist-server/controllers/yltc/yltc.js.map +1 -0
- package/dist-server/service/sftp/sftp-mutation.js +15 -8
- package/dist-server/service/sftp/sftp-mutation.js.map +1 -1
- package/dist-server/service/sftp/sftp.js +41 -0
- package/dist-server/service/sftp/sftp.js.map +1 -1
- package/dist-server/sftp-const.js +36 -2
- package/dist-server/sftp-const.js.map +1 -1
- package/dist-server/storage/providers/ftp-storage.provider.js +178 -0
- package/dist-server/storage/providers/ftp-storage.provider.js.map +1 -0
- package/dist-server/storage/providers/local-storage.provider.js +153 -0
- package/dist-server/storage/providers/local-storage.provider.js.map +1 -0
- package/dist-server/storage/providers/s3-storage.provider.js +181 -0
- package/dist-server/storage/providers/s3-storage.provider.js.map +1 -0
- package/dist-server/storage/providers/sftp-storage.provider.js +133 -0
- package/dist-server/storage/providers/sftp-storage.provider.js.map +1 -0
- package/dist-server/storage/storage-factory.js +55 -0
- package/dist-server/storage/storage-factory.js.map +1 -0
- package/dist-server/storage/storage-manager.js +86 -0
- package/dist-server/storage/storage-manager.js.map +1 -0
- package/dist-server/storage/storage-provider.interface.js +3 -0
- package/dist-server/storage/storage-provider.interface.js.map +1 -0
- package/dist-server/util/file-formatters.js +100 -0
- package/dist-server/util/file-formatters.js.map +1 -0
- package/dist-server/util/generate-files.js +77 -17
- package/dist-server/util/generate-files.js.map +1 -1
- package/dist-server/util/get-permitted-directories.js +9 -5
- package/dist-server/util/get-permitted-directories.js.map +1 -1
- package/package.json +6 -3
- package/server/controllers/herbalife/herbalife.ts +11 -6
- package/server/controllers/index.ts +1 -0
- package/server/controllers/sftp-api/index.ts +3 -0
- package/server/controllers/yltc/apis/create-yltc-inventory-report.ts +37 -0
- package/server/controllers/yltc/apis/echo.ts +14 -0
- package/server/controllers/yltc/apis/index.ts +2 -0
- package/server/controllers/yltc/index.ts +7 -0
- package/server/controllers/yltc/platform-action.ts +34 -0
- package/server/controllers/yltc/yltc.ts +93 -0
- package/server/service/sftp/sftp-mutation.ts +16 -10
- package/server/service/sftp/sftp.ts +34 -0
- package/server/sftp-const.ts +41 -1
- package/server/storage/providers/ftp-storage.provider.ts +177 -0
- package/server/storage/providers/local-storage.provider.ts +159 -0
- package/server/storage/providers/s3-storage.provider.ts +214 -0
- package/server/storage/providers/sftp-storage.provider.ts +157 -0
- package/server/storage/storage-factory.ts +62 -0
- package/server/storage/storage-manager.ts +103 -0
- package/server/storage/storage-provider.interface.ts +42 -0
- package/server/util/file-formatters.ts +97 -0
- package/server/util/generate-files.ts +79 -17
- package/server/util/get-permitted-directories.ts +11 -7
package/CHANGELOG.md
CHANGED
|
@@ -5,4 +5,373 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This guide explains how to migrate from the current S3-only SFTP integration to support multiple storage providers including external SFTP servers.
|
|
11
|
+
|
|
12
|
+
## New Architecture
|
|
13
|
+
|
|
14
|
+
The new system uses a provider-based architecture with the following components:
|
|
15
|
+
|
|
16
|
+
1. **StorageProvider Interface** - Common contract for all storage providers
|
|
17
|
+
2. **StorageFactory** - Creates appropriate provider instances
|
|
18
|
+
3. **StorageManager** - Singleton manager for provider lifecycle
|
|
19
|
+
4. **Provider Implementations** - S3, SFTP, FTP, Local storage providers
|
|
20
|
+
|
|
21
|
+
## Migration Steps
|
|
22
|
+
|
|
23
|
+
### 1. Update Configuration
|
|
24
|
+
|
|
25
|
+
**Old Configuration (S3 only):**
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// config/sftp.js
|
|
29
|
+
module.exports = {
|
|
30
|
+
sftpFileStorage: {
|
|
31
|
+
type: 's3',
|
|
32
|
+
accessKeyId: 'your-access-key',
|
|
33
|
+
secretAccessKey: 'your-secret-key',
|
|
34
|
+
bucketName: 'your-bucket'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**New Configuration (Multi-provider):**
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// config/sftp.js
|
|
43
|
+
module.exports = {
|
|
44
|
+
sftpFileStorage: {
|
|
45
|
+
// For S3 (existing)
|
|
46
|
+
type: 's3',
|
|
47
|
+
accessKeyId: 'your-access-key',
|
|
48
|
+
secretAccessKey: 'your-secret-key',
|
|
49
|
+
bucketName: 'your-bucket',
|
|
50
|
+
region: 'us-east-1'
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// For External SFTP Server
|
|
54
|
+
sftpExternal: {
|
|
55
|
+
type: 'sftp',
|
|
56
|
+
host: 'sftp.example.com',
|
|
57
|
+
port: 22,
|
|
58
|
+
username: 'sftp-user',
|
|
59
|
+
password: 'sftp-password', // or use privateKey
|
|
60
|
+
privateKey: '/path/to/private/key',
|
|
61
|
+
basePath: '/remote/path',
|
|
62
|
+
timeout: 30000,
|
|
63
|
+
retries: 3
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// For FTP Server
|
|
67
|
+
ftpServer: {
|
|
68
|
+
type: 'ftp',
|
|
69
|
+
host: 'ftp.example.com',
|
|
70
|
+
port: 21,
|
|
71
|
+
username: 'ftp-user',
|
|
72
|
+
password: 'ftp-password',
|
|
73
|
+
basePath: '/remote/path',
|
|
74
|
+
secure: false // true for FTPS
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// For Local Storage
|
|
78
|
+
localStorage: {
|
|
79
|
+
type: 'local',
|
|
80
|
+
basePath: './storage'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Update Code Usage
|
|
86
|
+
|
|
87
|
+
**Old Usage:**
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { SFTPFILESTORAGE } from './sftp-const'
|
|
91
|
+
|
|
92
|
+
// Read file
|
|
93
|
+
const content = await SFTPFILESTORAGE.readFile(path, 'utf-8')
|
|
94
|
+
|
|
95
|
+
// Upload file
|
|
96
|
+
await SFTPFILESTORAGE.uploadFile({ stream, filename, uploadPath })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**New Usage:**
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { StorageManager } from './storage/storage-manager'
|
|
103
|
+
import { config } from '@things-factory/env'
|
|
104
|
+
|
|
105
|
+
const storageManager = StorageManager.getInstance()
|
|
106
|
+
|
|
107
|
+
// Get configuration
|
|
108
|
+
const sftpConfig = config.get('sftpExternal')
|
|
109
|
+
|
|
110
|
+
// Read file
|
|
111
|
+
const content = await storageManager.readFile(sftpConfig, path, 'utf-8')
|
|
112
|
+
|
|
113
|
+
// Upload file
|
|
114
|
+
await storageManager.uploadFile(sftpConfig, { stream, filename, uploadPath })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Update Existing Code
|
|
118
|
+
|
|
119
|
+
Replace direct `SFTPFILESTORAGE` usage with `StorageManager`:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Old code in sftp-mutation.ts
|
|
123
|
+
const results: any[] = await getPermittedDirectories({ path: initialDataPath }, context)
|
|
124
|
+
|
|
125
|
+
// New code
|
|
126
|
+
import { StorageManager } from '../storage/storage-manager'
|
|
127
|
+
const storageManager = StorageManager.getInstance()
|
|
128
|
+
const sftpConfig = config.get('sftpFileStorage') // or your specific config
|
|
129
|
+
const results: any[] = await storageManager.readFolders(sftpConfig, { path: initialDataPath })
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Update Platform Implementations
|
|
133
|
+
|
|
134
|
+
**Old Herbalife Platform:**
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// server/controllers/herbalife/herbalife.ts
|
|
138
|
+
async get(path: string, data: any = {}) {
|
|
139
|
+
const fileResult: any = await SFTPFILESTORAGE.readFile(path, 'utf-8')
|
|
140
|
+
// ...
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**New Herbalife Platform:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// server/controllers/herbalife/herbalife.ts
|
|
148
|
+
import { StorageManager } from '../../storage/storage-manager'
|
|
149
|
+
import { config } from '@things-factory/env'
|
|
150
|
+
|
|
151
|
+
export class Herbalife {
|
|
152
|
+
private storageManager = StorageManager.getInstance()
|
|
153
|
+
private sftpConfig = config.get('sftpFileStorage') // or specific config
|
|
154
|
+
|
|
155
|
+
async get(path: string, data: any = {}) {
|
|
156
|
+
const fileResult: any = await this.storageManager.readFile(this.sftpConfig, path, 'utf-8')
|
|
157
|
+
// ...
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Configuration Examples
|
|
163
|
+
|
|
164
|
+
### External SFTP Server
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
{
|
|
168
|
+
type: 'sftp',
|
|
169
|
+
host: 'sftp.company.com',
|
|
170
|
+
port: 22,
|
|
171
|
+
username: 'integration-user',
|
|
172
|
+
password: 'secure-password',
|
|
173
|
+
basePath: '/incoming/orders',
|
|
174
|
+
timeout: 30000,
|
|
175
|
+
retries: 3
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### SFTP with Private Key
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
{
|
|
183
|
+
type: 'sftp',
|
|
184
|
+
host: 'sftp.company.com',
|
|
185
|
+
port: 22,
|
|
186
|
+
username: 'integration-user',
|
|
187
|
+
privateKey: fs.readFileSync('/path/to/private/key'),
|
|
188
|
+
basePath: '/incoming/orders',
|
|
189
|
+
timeout: 30000
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### FTP Server
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
{
|
|
197
|
+
type: 'ftp',
|
|
198
|
+
host: 'ftp.company.com',
|
|
199
|
+
port: 21,
|
|
200
|
+
username: 'ftp-user',
|
|
201
|
+
password: 'ftp-password',
|
|
202
|
+
basePath: '/orders',
|
|
203
|
+
secure: false // true for FTPS
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Error Handling
|
|
208
|
+
|
|
209
|
+
The new system provides better error handling:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
try {
|
|
213
|
+
const content = await storageManager.readFile(config, path, 'utf-8')
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error.message.includes('Failed to connect')) {
|
|
216
|
+
// Connection error
|
|
217
|
+
logger.error('Storage connection failed:', error)
|
|
218
|
+
} else if (error.message.includes('File not found')) {
|
|
219
|
+
// File not found
|
|
220
|
+
logger.warn('File not found:', path)
|
|
221
|
+
} else {
|
|
222
|
+
// Other errors
|
|
223
|
+
logger.error('Storage operation failed:', error)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Testing
|
|
229
|
+
|
|
230
|
+
Test different providers:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// Test S3
|
|
234
|
+
const s3Config = { type: 's3', bucketName: 'test-bucket', ... }
|
|
235
|
+
await storageManager.writeFile(s3Config, '/test.txt', 'Hello S3')
|
|
236
|
+
|
|
237
|
+
// Test SFTP
|
|
238
|
+
const sftpConfig = { type: 'sftp', host: 'test-sftp.com', ... }
|
|
239
|
+
await storageManager.writeFile(sftpConfig, '/test.txt', 'Hello SFTP')
|
|
240
|
+
|
|
241
|
+
// Test Local
|
|
242
|
+
const localConfig = { type: 'local', basePath: './test-storage' }
|
|
243
|
+
await storageManager.writeFile(localConfig, '/test.txt', 'Hello Local')
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Performance Considerations
|
|
247
|
+
|
|
248
|
+
1. **Connection Pooling**: The StorageManager maintains provider connections
|
|
249
|
+
2. **Lazy Loading**: Providers are created only when needed
|
|
250
|
+
3. **Resource Cleanup**: Call `disconnectAll()` on application shutdown
|
|
251
|
+
|
|
252
|
+
## Security
|
|
253
|
+
|
|
254
|
+
1. **Credentials**: Store sensitive credentials in environment variables
|
|
255
|
+
2. **Private Keys**: Use file paths or environment variables for private keys
|
|
256
|
+
3. **Network Security**: Use FTPS for FTP connections when possible
|
|
257
|
+
4. **Access Control**: Implement proper file permissions on SFTP servers
|
|
258
|
+
|
|
259
|
+
## Troubleshooting
|
|
260
|
+
|
|
261
|
+
### Common Issues
|
|
262
|
+
|
|
263
|
+
1. **Connection Timeout**: Increase `timeout` in configuration
|
|
264
|
+
2. **Authentication Failed**: Check credentials and key permissions
|
|
265
|
+
3. **Path Issues**: Ensure `basePath` is correctly configured
|
|
266
|
+
4. **Permission Denied**: Check file/directory permissions on remote servers
|
|
267
|
+
|
|
268
|
+
### Debug Mode
|
|
269
|
+
|
|
270
|
+
Enable debug logging:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { logger } from '@things-factory/env'
|
|
274
|
+
logger.level = 'debug'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Backward Compatibility
|
|
278
|
+
|
|
279
|
+
The old `SFTPFILESTORAGE` object is still available for backward compatibility, but it's recommended to migrate to the new system for better maintainability and extensibility.
|
|
280
|
+
|
|
281
|
+
## Migration Addendum (SFTP DB config, formatters, and direct remote writes)
|
|
282
|
+
|
|
283
|
+
### 5. Store SFTP connection in DB (Sftp entity)
|
|
284
|
+
|
|
285
|
+
New columns were added to the `sftps` table so credentials and connection parameters can be managed per record instead of via config files.
|
|
286
|
+
|
|
287
|
+
- Columns: `password text`, `port integer`, `base_path text`, `timeout integer`, `retries integer`
|
|
288
|
+
- One-time SQL migration (PostgreSQL):
|
|
289
|
+
|
|
290
|
+
```sql
|
|
291
|
+
BEGIN;
|
|
292
|
+
ALTER TABLE public.sftps
|
|
293
|
+
ADD COLUMN IF NOT EXISTS password text,
|
|
294
|
+
ADD COLUMN IF NOT EXISTS port integer,
|
|
295
|
+
ADD COLUMN IF NOT EXISTS base_path text,
|
|
296
|
+
ADD COLUMN IF NOT EXISTS timeout integer,
|
|
297
|
+
ADD COLUMN IF NOT EXISTS retries integer;
|
|
298
|
+
|
|
299
|
+
-- Optional backfill from folder_path
|
|
300
|
+
UPDATE public.sftps
|
|
301
|
+
SET base_path = folder_path
|
|
302
|
+
WHERE base_path IS NULL
|
|
303
|
+
AND folder_path IS NOT NULL;
|
|
304
|
+
COMMIT;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
At runtime, the uploader now builds a `StorageConfig` from the `Sftp` entity when possible (fallback to `sftpExternal` if the record has no credentials). Populate `host`, `port`, `username`, `password`, `basePath`, `timeout`, `retries` in the `sftps` row(s).
|
|
308
|
+
|
|
309
|
+
### 6. Standardized file formatting (CSV/XML/XLSX)
|
|
310
|
+
|
|
311
|
+
Formatting is centralized in `server/util/file-formatters.ts`:
|
|
312
|
+
|
|
313
|
+
- `formatData(data, contentType)` → returns a CSV/XML string (XLSX currently falls back to CSV)
|
|
314
|
+
- `formatAsCSV`, `formatAsXML`, `parseCSV` helpers
|
|
315
|
+
- Dates are normalized to `YYYY-MM-DD` for CSV/XML when values are `Date` or date-like strings
|
|
316
|
+
|
|
317
|
+
Platform uploaders (e.g., `Yltc.post`) now accept either raw data arrays or preformatted strings and choose the correct extension based on `contentType`.
|
|
318
|
+
|
|
319
|
+
### 7. API payload adjustments
|
|
320
|
+
|
|
321
|
+
When calling `SftpAPI.createYLTCInventoryReport`, send:
|
|
322
|
+
|
|
323
|
+
- Required: `title` (without extension), `content` (raw array or string), `contentType` (e.g., `'csv'`)
|
|
324
|
+
- Optional: `backupTitle`, `backupPath` (only include if you actually want a backup file)
|
|
325
|
+
|
|
326
|
+
The denormalizer omits undefined optionals so they don’t appear in logs.
|
|
327
|
+
|
|
328
|
+
### 8. Direct remote writes (no local temp files)
|
|
329
|
+
|
|
330
|
+
The upload utility no longer writes to a local `uploaded-files` directory. Files are written directly to the remote storage:
|
|
331
|
+
|
|
332
|
+
- Ensures remote directory (when `uploadPath` is provided)
|
|
333
|
+
- Builds remote path as `<uploadPath>/<title>` and calls `StorageManager.writeFile(...)`
|
|
334
|
+
- If a provider `basePath` is configured, an empty `uploadPath` writes to the base path root (e.g., `/Staging`)
|
|
335
|
+
|
|
336
|
+
### 9. Path and environment notes
|
|
337
|
+
|
|
338
|
+
- With `basePath` set (e.g., `/Staging`), pass an empty `uploadPath` to write directly under that folder
|
|
339
|
+
- If you need subfolders, pass `uploadPath: 'subdir'` and the uploader will create it if missing
|
|
340
|
+
|
|
341
|
+
### 10. Troubleshooting
|
|
342
|
+
|
|
343
|
+
- Enable debug: `DEBUG=things-factory:integration-sftp:*` to see connect and upload traces
|
|
344
|
+
- Look for lines like: `upload content prepared: type=string, contentType=csv, length=..., preview=report_date,...`
|
|
345
|
+
- Connection diagnostics log whether a password/privateKey is present without exposing secrets
|
|
346
|
+
|
|
347
|
+
### 11. S3 archival of generated files
|
|
348
|
+
|
|
349
|
+
When uploads run via a platform uploader (e.g., YLTC), a best-effort archive copy of the generated file can be written to S3 if `sftpFileStorage` is configured with `type: 's3'`.
|
|
350
|
+
|
|
351
|
+
- Archive key format: `<platform>/<filename>`
|
|
352
|
+
- The `<platform>` prefix is taken from the `platform` column of the `sftps` table row used during the upload (e.g., `yltc`).
|
|
353
|
+
- If `platform` is empty or not provided, a fallback of `yltc` is used.
|
|
354
|
+
- This archive write is independent of the primary upload (SFTP or otherwise). Any S3 archive failure is logged but will not block the main upload.
|
|
355
|
+
- Ensure `sftpFileStorage` includes the correct bucket and region, and that IAM has `s3:PutObject` permission on `arn:aws:s3:::<bucket>/*`. If your bucket enforces server-side encryption, set the following optional fields:
|
|
356
|
+
- `serverSideEncryption: 'AES256'` or `serverSideEncryption: 'aws:kms'`
|
|
357
|
+
- `sseKmsKeyId: '<your-kms-key-arn>'` (when using KMS)
|
|
358
|
+
|
|
359
|
+
Example `sftpFileStorage` for S3 archival:
|
|
360
|
+
|
|
361
|
+
```js
|
|
362
|
+
// config.development.js
|
|
363
|
+
module.exports = {
|
|
364
|
+
sftpFileStorage: {
|
|
365
|
+
type: 's3',
|
|
366
|
+
accessKeyId: '...',
|
|
367
|
+
secretAccessKey: '...',
|
|
368
|
+
bucketName: 'operato-sftp',
|
|
369
|
+
region: 'ap-southeast-1'
|
|
370
|
+
// optional if bucket enforces SSE
|
|
371
|
+
// serverSideEncryption: 'AES256',
|
|
372
|
+
// sseKmsKeyId: '<kms-key-arn>'
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
8
377
|
<!-- ## [Unreleased] -->
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Herbalife = void 0;
|
|
4
|
-
require("../../sftp-s3");
|
|
5
4
|
const xml_js_1 = require("xml-js");
|
|
6
|
-
const
|
|
5
|
+
const env_1 = require("@things-factory/env");
|
|
6
|
+
const storage_manager_1 = require("../../storage/storage-manager");
|
|
7
7
|
const generate_files_1 = require("../../util/generate-files");
|
|
8
8
|
const debug = require('debug')('things-factory:integration-sftp:herbalife');
|
|
9
9
|
class Herbalife {
|
|
10
|
-
constructor() {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.storageManager = storage_manager_1.StorageManager.getInstance();
|
|
12
|
+
// Get the SFTP configuration from config
|
|
13
|
+
this.sftpConfig = env_1.config.get('sftpFileStorage');
|
|
14
|
+
}
|
|
11
15
|
async get(path, data = {}) {
|
|
12
|
-
const fileResult = await
|
|
16
|
+
const fileResult = await this.storageManager.readFile(this.sftpConfig, path, 'utf-8');
|
|
13
17
|
const item = (0, xml_js_1.xml2js)(fileResult, {
|
|
14
18
|
compact: true
|
|
15
19
|
});
|
|
@@ -32,7 +36,7 @@ class Herbalife {
|
|
|
32
36
|
content
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
|
-
await (0, generate_files_1.generateFiles)(params);
|
|
39
|
+
await (0, generate_files_1.generateFiles)(params, this.sftpConfig);
|
|
36
40
|
return sftp;
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"herbalife.js","sourceRoot":"","sources":["../../../server/controllers/herbalife/herbalife.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"herbalife.js","sourceRoot":"","sources":["../../../server/controllers/herbalife/herbalife.ts"],"names":[],"mappings":";;;AAAA,mCAA+B;AAC/B,6CAA4C;AAG5C,mEAA8D;AAC9D,8DAAyD;AAEzD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,2CAA2C,CAAC,CAAA;AAI3E,MAAa,SAAS;IAIpB;QAHQ,mBAAc,GAAG,gCAAc,CAAC,WAAW,EAAE,CAAA;QAInD,yCAAyC;QACzC,IAAI,CAAC,UAAU,GAAG,YAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACjD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAY,EAAE,OAAY,EAAE;QACpC,MAAM,UAAU,GAAQ,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1F,MAAM,IAAI,GAAQ,IAAA,eAAM,EAAC,UAAU,EAAE;YACnC,OAAO,EAAE,IAAI;SACd,CAAC,CAAA;QACF,MAAM,MAAM,qBAAa,IAAI,CAAE,CAAA;QAC/B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,OAAY,EAAE;QACrC,MAAM,EACJ,KAAK,EACL,WAAW,EACX,OAAO,EACP,IAAI,EACJ,UAAU,EACX,GAA4F,IAAI,CAAA;QAEjG,IAAI,MAAM,GAAU;YAClB;gBACE,KAAK;gBACL,UAAU,EAAE,IAAI;gBAChB,OAAO;aACR;SACF,CAAA;QAED,IAAI,UAAU,EAAE;YACd,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,WAAW;gBAClB,UAAU,EAAE,UAAU;gBACtB,OAAO;aACR,CAAC,CAAA;SACH;QACD,MAAM,IAAA,8BAAa,EAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QAE5C,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AA9CD,8BA8CC"}
|
|
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
require("./herbalife");
|
|
18
|
+
require("./yltc");
|
|
18
19
|
__exportStar(require("./sftp-api"), exports);
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/controllers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uBAAoB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/controllers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uBAAoB;AACpB,kBAAe;AAEf,6CAA0B"}
|
|
@@ -34,6 +34,7 @@ class SftpAPI {
|
|
|
34
34
|
static getOutboundOrder(sftp, req) { }
|
|
35
35
|
static createShipment(sftp, req) { }
|
|
36
36
|
static createSerialNumber(sftp, req) { }
|
|
37
|
+
static createYLTCInventoryReport(sftp, req) { }
|
|
37
38
|
}
|
|
38
39
|
SftpAPI.platforms = {};
|
|
39
40
|
__decorate([
|
|
@@ -60,5 +61,11 @@ __decorate([
|
|
|
60
61
|
__metadata("design:paramtypes", [Object, Object]),
|
|
61
62
|
__metadata("design:returntype", Object)
|
|
62
63
|
], SftpAPI, "createSerialNumber", null);
|
|
64
|
+
__decorate([
|
|
65
|
+
decorators_1.api,
|
|
66
|
+
__metadata("design:type", Function),
|
|
67
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
68
|
+
__metadata("design:returntype", Object)
|
|
69
|
+
], SftpAPI, "createYLTCInventoryReport", null);
|
|
63
70
|
exports.SftpAPI = SftpAPI;
|
|
64
71
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/controllers/sftp-api/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAuC;AAEvC,2CAAoC;AACpC,6CAAkC;AAElC,MAAa,OAAO;IAGlB,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI;QACxC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG;YACxB,MAAM;YACN,IAAI;SACL,CAAA;IACH,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,IAAI;QACrB,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,cAAI,CAAC,CAAA;QACtC,OAAO,MAAM,UAAU,CAAC,OAAO,CAAC;YAC9B,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,QAAQ,CAAC;SACtB,CAAC,CAAA;IACJ,CAAC;IAGM,AAAP,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGvB,AAAP,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGnC,AAAP,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGjC,AAAP,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/controllers/sftp-api/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAAuC;AAEvC,2CAAoC;AACpC,6CAAkC;AAElC,MAAa,OAAO;IAGlB,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI;QACxC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG;YACxB,MAAM;YACN,IAAI;SACL,CAAA;IACH,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,IAAI;QACrB,OAAO,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,cAAI,CAAC,CAAA;QACtC,OAAO,MAAM,UAAU,CAAC,OAAO,CAAC;YAC9B,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,QAAQ,CAAC;SACtB,CAAC,CAAA;IACJ,CAAC;IAGM,AAAP,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGvB,AAAP,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGnC,AAAP,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGjC,AAAP,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;IAGrC,AAAP,MAAM,CAAC,yBAAyB,CAAC,IAAI,EAAE,GAAG,IAAQ,CAAC;;AAlC5C,iBAAS,GAAG,EAAE,CAAA;AAsBd;IADN,gBAAG;;;;yBAC0B;AAGvB;IADN,gBAAG;;;;qCACsC;AAGnC;IADN,gBAAG;;;;mCACoC;AAGjC;IADN,gBAAG;;;;uCACwC;AAGrC;IADN,gBAAG;;;;8CAC+C;AAnCrD,0BAoCC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createYLTCInventoryReport = void 0;
|
|
4
|
+
function createYLTCInventoryReport() {
|
|
5
|
+
return {
|
|
6
|
+
method: 'post',
|
|
7
|
+
path: '{folderPath}',
|
|
8
|
+
denormalize(req) {
|
|
9
|
+
const { sftp, title, backupTitle, content, backupPath, contentType } = req;
|
|
10
|
+
const { folderPath, platform } = sftp;
|
|
11
|
+
// Values used by platform-action substitute() for path placeholders
|
|
12
|
+
const resource = {
|
|
13
|
+
folderPath: folderPath || ''
|
|
14
|
+
};
|
|
15
|
+
// Build payload with required fields only
|
|
16
|
+
const payload = {
|
|
17
|
+
sftp,
|
|
18
|
+
title,
|
|
19
|
+
content,
|
|
20
|
+
contentType,
|
|
21
|
+
folderPath,
|
|
22
|
+
platform
|
|
23
|
+
};
|
|
24
|
+
// Include optionals only when present
|
|
25
|
+
if (backupTitle)
|
|
26
|
+
payload.backupTitle = backupTitle;
|
|
27
|
+
if (backupPath)
|
|
28
|
+
payload.backupPath = backupPath;
|
|
29
|
+
return {
|
|
30
|
+
resource,
|
|
31
|
+
payload
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
normalize(res) {
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
exports.createYLTCInventoryReport = createYLTCInventoryReport;
|
|
40
|
+
//# sourceMappingURL=create-yltc-inventory-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-yltc-inventory-report.js","sourceRoot":"","sources":["../../../../server/controllers/yltc/apis/create-yltc-inventory-report.ts"],"names":[],"mappings":";;;AAAA,SAAgB,yBAAyB;IACvC,OAAO;QACL,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,cAAc;QACpB,WAAW,CAAC,GAAG;YACb,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,GAAG,CAAA;YAC1E,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;YAErC,oEAAoE;YACpE,MAAM,QAAQ,GAAG;gBACf,UAAU,EAAE,UAAU,IAAI,EAAE;aAC7B,CAAA;YAED,0CAA0C;YAC1C,MAAM,OAAO,GAAQ;gBACnB,IAAI;gBACJ,KAAK;gBACL,OAAO;gBACP,WAAW;gBACX,UAAU;gBACV,QAAQ;aACT,CAAA;YAED,sCAAsC;YACtC,IAAI,WAAW;gBAAE,OAAO,CAAC,WAAW,GAAG,WAAW,CAAA;YAClD,IAAI,UAAU;gBAAE,OAAO,CAAC,UAAU,GAAG,UAAU,CAAA;YAE/C,OAAO;gBACL,QAAQ;gBACR,OAAO;aACR,CAAA;QACH,CAAC;QACD,SAAS,CAAC,GAAG;YACX,OAAO,GAAG,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AApCD,8DAoCC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.echo = void 0;
|
|
4
|
+
function echo() {
|
|
5
|
+
return {
|
|
6
|
+
path: '/echo',
|
|
7
|
+
denormalize(req) {
|
|
8
|
+
return Object.assign({}, req);
|
|
9
|
+
},
|
|
10
|
+
normalize(res) {
|
|
11
|
+
return Object.assign({}, res);
|
|
12
|
+
},
|
|
13
|
+
action({ store, method, path, request, platformAction }) {
|
|
14
|
+
return Object.assign({}, request);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
exports.echo = echo;
|
|
19
|
+
//# sourceMappingURL=echo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echo.js","sourceRoot":"","sources":["../../../../server/controllers/yltc/apis/echo.ts"],"names":[],"mappings":";;;AAAA,SAAgB,IAAI;IAClB,OAAO;QACL,IAAI,EAAE,OAAO;QACb,WAAW,CAAC,GAAG;YACb,yBAAY,GAAG,EAAE;QACnB,CAAC;QACD,SAAS,CAAC,GAAG;YACX,yBAAY,GAAG,EAAE;QACnB,CAAC;QACD,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE;YACrD,yBAAY,OAAO,EAAE;QACvB,CAAC;KACF,CAAA;AACH,CAAC;AAbD,oBAaC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./echo"), exports);
|
|
18
|
+
__exportStar(require("./create-yltc-inventory-report"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../server/controllers/yltc/apis/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAsB;AACtB,iEAA8C"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
26
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const sftp_api_1 = require("../sftp-api");
|
|
30
|
+
const APIS = __importStar(require("./apis"));
|
|
31
|
+
const platform_action_1 = require("./platform-action");
|
|
32
|
+
__exportStar(require("./yltc"), exports);
|
|
33
|
+
sftp_api_1.SftpAPI.registerPlatform('yltc', platform_action_1.action, APIS);
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/controllers/yltc/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,0CAAqC;AACrC,6CAA8B;AAC9B,uDAA0C;AAE1C,yCAAsB;AAEtB,kBAAO,CAAC,gBAAgB,CAAC,MAAM,EAAE,wBAAM,EAAE,IAAI,CAAC,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.action = void 0;
|
|
4
|
+
const yltc_1 = require("./yltc");
|
|
5
|
+
function substitute(path, obj) {
|
|
6
|
+
var props = [];
|
|
7
|
+
var re = /{([^}]+)}/g;
|
|
8
|
+
var text;
|
|
9
|
+
while ((text = re.exec(path))) {
|
|
10
|
+
props.push(text[1]);
|
|
11
|
+
}
|
|
12
|
+
var result = path;
|
|
13
|
+
props.forEach(prop => {
|
|
14
|
+
let value = obj[prop.trim()];
|
|
15
|
+
result = result.replace(`{${prop}}`, value === undefined ? '' : value);
|
|
16
|
+
});
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
const action = async ({ method = 'get', path, request }) => {
|
|
20
|
+
const client = new yltc_1.Yltc();
|
|
21
|
+
const { resource = {}, payload = {} } = request;
|
|
22
|
+
path = substitute(path, resource);
|
|
23
|
+
var response = await client[method](path, payload);
|
|
24
|
+
if (response.errors) {
|
|
25
|
+
throw response;
|
|
26
|
+
}
|
|
27
|
+
return response;
|
|
28
|
+
};
|
|
29
|
+
exports.action = action;
|
|
30
|
+
//# sourceMappingURL=platform-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-action.js","sourceRoot":"","sources":["../../../server/controllers/yltc/platform-action.ts"],"names":[],"mappings":";;;AAAA,iCAA6B;AAE7B,SAAS,UAAU,CAAC,IAAI,EAAE,GAAG;IAC3B,IAAI,KAAK,GAAG,EAAE,CAAA;IACd,IAAI,EAAE,GAAG,YAAY,CAAA;IACrB,IAAI,IAAI,CAAA;IAER,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE;QAC7B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;KACpB;IAED,IAAI,MAAM,GAAG,IAAI,CAAA;IACjB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACnB,IAAI,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QAC5B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC;AAEM,MAAM,MAAM,GAAG,KAAK,EAAE,EAAE,MAAM,GAAG,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;IAChE,MAAM,MAAM,GAAG,IAAI,WAAI,EAAE,CAAA;IAEzB,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;IAE/C,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IAEjC,IAAI,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAClD,IAAI,QAAQ,CAAC,MAAM,EAAE;QACnB,MAAM,QAAQ,CAAA;KACf;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AAbY,QAAA,MAAM,UAalB"}
|