retold-facto 0.0.4
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/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- package/test/model/fable-configuration.json +14 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retold Facto - Store Connection Manager Service
|
|
3
|
+
*
|
|
4
|
+
* Manages external database connections for projection deployment.
|
|
5
|
+
* Provides CRUD endpoints for store connections, connection testing,
|
|
6
|
+
* and available connector type discovery.
|
|
7
|
+
*
|
|
8
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
9
|
+
*/
|
|
10
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
11
|
+
|
|
12
|
+
const defaultStoreConnectionManagerOptions = (
|
|
13
|
+
{
|
|
14
|
+
RoutePrefix: '/facto'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
class RetoldFactoStoreConnectionManager extends libFableServiceProviderBase
|
|
18
|
+
{
|
|
19
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
20
|
+
{
|
|
21
|
+
let tmpOptions = Object.assign({}, defaultStoreConnectionManagerOptions, pOptions);
|
|
22
|
+
super(pFable, tmpOptions, pServiceHash);
|
|
23
|
+
|
|
24
|
+
this.serviceType = 'RetoldFactoStoreConnectionManager';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Mask sensitive fields (password) in a Config JSON string before sending to client.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} pConfigJSON - Raw Config JSON string
|
|
31
|
+
* @returns {string} Masked Config JSON string
|
|
32
|
+
*/
|
|
33
|
+
_maskConfig(pConfigJSON)
|
|
34
|
+
{
|
|
35
|
+
if (!pConfigJSON) return '{}';
|
|
36
|
+
try
|
|
37
|
+
{
|
|
38
|
+
let tmpConfig = JSON.parse(pConfigJSON);
|
|
39
|
+
if (tmpConfig.password)
|
|
40
|
+
{
|
|
41
|
+
tmpConfig.password = '***';
|
|
42
|
+
}
|
|
43
|
+
if (tmpConfig.Password)
|
|
44
|
+
{
|
|
45
|
+
tmpConfig.Password = '***';
|
|
46
|
+
}
|
|
47
|
+
return JSON.stringify(tmpConfig);
|
|
48
|
+
}
|
|
49
|
+
catch (pError)
|
|
50
|
+
{
|
|
51
|
+
return '{}';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge an incoming Config object with the stored one,
|
|
57
|
+
* preserving the actual password if the client sent '***'.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} pNewConfig - The incoming config from the client
|
|
60
|
+
* @param {string} pStoredConfigJSON - The existing stored config JSON
|
|
61
|
+
* @returns {string} Merged config JSON
|
|
62
|
+
*/
|
|
63
|
+
_mergeConfig(pNewConfig, pStoredConfigJSON)
|
|
64
|
+
{
|
|
65
|
+
let tmpStored = {};
|
|
66
|
+
try
|
|
67
|
+
{
|
|
68
|
+
tmpStored = JSON.parse(pStoredConfigJSON || '{}');
|
|
69
|
+
}
|
|
70
|
+
catch (pError) { /* ignore parse errors */ }
|
|
71
|
+
|
|
72
|
+
if (pNewConfig.password === '***' && tmpStored.password)
|
|
73
|
+
{
|
|
74
|
+
pNewConfig.password = tmpStored.password;
|
|
75
|
+
}
|
|
76
|
+
if (pNewConfig.Password === '***' && tmpStored.Password)
|
|
77
|
+
{
|
|
78
|
+
pNewConfig.Password = tmpStored.Password;
|
|
79
|
+
}
|
|
80
|
+
return JSON.stringify(pNewConfig);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Connect REST API routes for store connection management.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} pOratorServiceServer - The Orator service server instance
|
|
87
|
+
*/
|
|
88
|
+
connectRoutes(pOratorServiceServer)
|
|
89
|
+
{
|
|
90
|
+
let tmpRoutePrefix = this.options.RoutePrefix;
|
|
91
|
+
|
|
92
|
+
// GET /facto/connections -- list all non-deleted connections
|
|
93
|
+
pOratorServiceServer.doGet(`${tmpRoutePrefix}/connections`,
|
|
94
|
+
(pRequest, pResponse, fNext) =>
|
|
95
|
+
{
|
|
96
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
97
|
+
{
|
|
98
|
+
pResponse.send({ Connections: [] });
|
|
99
|
+
return fNext();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let tmpQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
103
|
+
.addFilter('Deleted', 0);
|
|
104
|
+
|
|
105
|
+
this.fable.DAL.StoreConnection.doReads(tmpQuery,
|
|
106
|
+
(pError, pQuery, pRecords) =>
|
|
107
|
+
{
|
|
108
|
+
if (pError)
|
|
109
|
+
{
|
|
110
|
+
this.fable.log.error(`StoreConnectionManager error listing connections: ${pError}`);
|
|
111
|
+
pResponse.send({ Error: pError.message || pError, Connections: [] });
|
|
112
|
+
return fNext();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Mask passwords before sending
|
|
116
|
+
for (let i = 0; i < pRecords.length; i++)
|
|
117
|
+
{
|
|
118
|
+
pRecords[i].Config = this._maskConfig(pRecords[i].Config);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pResponse.send({ Count: pRecords.length, Connections: pRecords });
|
|
122
|
+
return fNext();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// POST /facto/connection -- create a new connection
|
|
127
|
+
pOratorServiceServer.doPost(`${tmpRoutePrefix}/connection`,
|
|
128
|
+
(pRequest, pResponse, fNext) =>
|
|
129
|
+
{
|
|
130
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
131
|
+
{
|
|
132
|
+
pResponse.send({ Error: 'StoreConnection DAL not initialized' });
|
|
133
|
+
return fNext();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let tmpBody = pRequest.body || {};
|
|
137
|
+
let tmpName = tmpBody.Name || 'Untitled Connection';
|
|
138
|
+
let tmpType = tmpBody.Type || 'MySQL';
|
|
139
|
+
let tmpConfig = tmpBody.Config || {};
|
|
140
|
+
|
|
141
|
+
let tmpRecord =
|
|
142
|
+
{
|
|
143
|
+
Name: tmpName,
|
|
144
|
+
Type: tmpType,
|
|
145
|
+
Config: (typeof tmpConfig === 'string') ? tmpConfig : JSON.stringify(tmpConfig),
|
|
146
|
+
Status: 'Untested'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let tmpQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
150
|
+
.setIDUser(0)
|
|
151
|
+
.addRecord(tmpRecord);
|
|
152
|
+
|
|
153
|
+
this.fable.DAL.StoreConnection.doCreate(tmpQuery,
|
|
154
|
+
(pError, pQuery, pRecord) =>
|
|
155
|
+
{
|
|
156
|
+
if (pError)
|
|
157
|
+
{
|
|
158
|
+
pResponse.send({ Error: pError.message || pError });
|
|
159
|
+
return fNext();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
pRecord.Config = this._maskConfig(pRecord.Config);
|
|
163
|
+
pResponse.send({ Success: true, Connection: pRecord });
|
|
164
|
+
return fNext();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// GET /facto/connection/:IDStoreConnection -- read a single connection
|
|
169
|
+
pOratorServiceServer.doGet(`${tmpRoutePrefix}/connection/:IDStoreConnection`,
|
|
170
|
+
(pRequest, pResponse, fNext) =>
|
|
171
|
+
{
|
|
172
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
173
|
+
{
|
|
174
|
+
pResponse.send({ Error: 'StoreConnection DAL not initialized' });
|
|
175
|
+
return fNext();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let tmpID = parseInt(pRequest.params.IDStoreConnection, 10);
|
|
179
|
+
|
|
180
|
+
let tmpQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
181
|
+
.addFilter('IDStoreConnection', tmpID);
|
|
182
|
+
|
|
183
|
+
this.fable.DAL.StoreConnection.doRead(tmpQuery,
|
|
184
|
+
(pError, pQuery, pRecord) =>
|
|
185
|
+
{
|
|
186
|
+
if (pError)
|
|
187
|
+
{
|
|
188
|
+
pResponse.send({ Error: pError.message || pError });
|
|
189
|
+
return fNext();
|
|
190
|
+
}
|
|
191
|
+
if (!pRecord || !pRecord.IDStoreConnection)
|
|
192
|
+
{
|
|
193
|
+
pResponse.send({ Error: 'Connection not found' });
|
|
194
|
+
return fNext();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pRecord.Config = this._maskConfig(pRecord.Config);
|
|
198
|
+
pResponse.send({ Connection: pRecord });
|
|
199
|
+
return fNext();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// PUT /facto/connection/:IDStoreConnection -- update a connection
|
|
204
|
+
pOratorServiceServer.doPut(`${tmpRoutePrefix}/connection/:IDStoreConnection`,
|
|
205
|
+
(pRequest, pResponse, fNext) =>
|
|
206
|
+
{
|
|
207
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
208
|
+
{
|
|
209
|
+
pResponse.send({ Error: 'StoreConnection DAL not initialized' });
|
|
210
|
+
return fNext();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let tmpID = parseInt(pRequest.params.IDStoreConnection, 10);
|
|
214
|
+
let tmpBody = pRequest.body || {};
|
|
215
|
+
|
|
216
|
+
// First read the existing record to preserve password if masked
|
|
217
|
+
let tmpReadQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
218
|
+
.addFilter('IDStoreConnection', tmpID);
|
|
219
|
+
|
|
220
|
+
this.fable.DAL.StoreConnection.doRead(tmpReadQuery,
|
|
221
|
+
(pReadError, pReadQuery, pExisting) =>
|
|
222
|
+
{
|
|
223
|
+
if (pReadError || !pExisting || !pExisting.IDStoreConnection)
|
|
224
|
+
{
|
|
225
|
+
pResponse.send({ Error: 'Connection not found' });
|
|
226
|
+
return fNext();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (tmpBody.Name !== undefined) pExisting.Name = tmpBody.Name;
|
|
230
|
+
if (tmpBody.Type !== undefined) pExisting.Type = tmpBody.Type;
|
|
231
|
+
if (tmpBody.Config !== undefined)
|
|
232
|
+
{
|
|
233
|
+
let tmpNewConfig = (typeof tmpBody.Config === 'string')
|
|
234
|
+
? JSON.parse(tmpBody.Config)
|
|
235
|
+
: tmpBody.Config;
|
|
236
|
+
pExisting.Config = this._mergeConfig(tmpNewConfig, pExisting.Config);
|
|
237
|
+
}
|
|
238
|
+
if (tmpBody.Status !== undefined) pExisting.Status = tmpBody.Status;
|
|
239
|
+
|
|
240
|
+
let tmpUpdateQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
241
|
+
.addRecord(pExisting);
|
|
242
|
+
|
|
243
|
+
this.fable.DAL.StoreConnection.doUpdate(tmpUpdateQuery,
|
|
244
|
+
(pError, pQuery, pRecord) =>
|
|
245
|
+
{
|
|
246
|
+
if (pError)
|
|
247
|
+
{
|
|
248
|
+
pResponse.send({ Error: pError.message || pError });
|
|
249
|
+
return fNext();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
pRecord.Config = this._maskConfig(pRecord.Config);
|
|
253
|
+
pResponse.send({ Success: true, Connection: pRecord });
|
|
254
|
+
return fNext();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// DELETE /facto/connection/:IDStoreConnection -- soft-delete
|
|
260
|
+
pOratorServiceServer.doDel(`${tmpRoutePrefix}/connection/:IDStoreConnection`,
|
|
261
|
+
(pRequest, pResponse, fNext) =>
|
|
262
|
+
{
|
|
263
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
264
|
+
{
|
|
265
|
+
pResponse.send({ Error: 'StoreConnection DAL not initialized' });
|
|
266
|
+
return fNext();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let tmpID = parseInt(pRequest.params.IDStoreConnection, 10);
|
|
270
|
+
|
|
271
|
+
let tmpReadQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
272
|
+
.addFilter('IDStoreConnection', tmpID);
|
|
273
|
+
|
|
274
|
+
this.fable.DAL.StoreConnection.doRead(tmpReadQuery,
|
|
275
|
+
(pReadError, pReadQuery, pExisting) =>
|
|
276
|
+
{
|
|
277
|
+
if (pReadError || !pExisting || !pExisting.IDStoreConnection)
|
|
278
|
+
{
|
|
279
|
+
pResponse.send({ Error: 'Connection not found' });
|
|
280
|
+
return fNext();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pExisting.Deleted = 1;
|
|
284
|
+
pExisting.DeleteDate = new Date().toISOString();
|
|
285
|
+
|
|
286
|
+
let tmpUpdateQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
287
|
+
.addRecord(pExisting);
|
|
288
|
+
|
|
289
|
+
this.fable.DAL.StoreConnection.doUpdate(tmpUpdateQuery,
|
|
290
|
+
(pError) =>
|
|
291
|
+
{
|
|
292
|
+
if (pError)
|
|
293
|
+
{
|
|
294
|
+
pResponse.send({ Error: pError.message || pError });
|
|
295
|
+
return fNext();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
pResponse.send({ Success: true });
|
|
299
|
+
return fNext();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// POST /facto/connection/:IDStoreConnection/test -- test a saved connection
|
|
305
|
+
pOratorServiceServer.doPost(`${tmpRoutePrefix}/connection/:IDStoreConnection/test`,
|
|
306
|
+
(pRequest, pResponse, fNext) =>
|
|
307
|
+
{
|
|
308
|
+
if (!this.fable.DAL || !this.fable.DAL.StoreConnection)
|
|
309
|
+
{
|
|
310
|
+
pResponse.send({ Success: false, Error: 'StoreConnection DAL not initialized' });
|
|
311
|
+
return fNext();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let tmpID = parseInt(pRequest.params.IDStoreConnection, 10);
|
|
315
|
+
|
|
316
|
+
let tmpReadQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
317
|
+
.addFilter('IDStoreConnection', tmpID);
|
|
318
|
+
|
|
319
|
+
this.fable.DAL.StoreConnection.doRead(tmpReadQuery,
|
|
320
|
+
(pReadError, pReadQuery, pExisting) =>
|
|
321
|
+
{
|
|
322
|
+
if (pReadError || !pExisting || !pExisting.IDStoreConnection)
|
|
323
|
+
{
|
|
324
|
+
pResponse.send({ Success: false, Error: 'Connection not found' });
|
|
325
|
+
return fNext();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let tmpConfig = {};
|
|
329
|
+
try { tmpConfig = JSON.parse(pExisting.Config || '{}'); }
|
|
330
|
+
catch (e) { /* ignore */ }
|
|
331
|
+
|
|
332
|
+
this._testConnection(pExisting.Type, tmpConfig,
|
|
333
|
+
(pTestError, pResult) =>
|
|
334
|
+
{
|
|
335
|
+
// Update the connection record with test results
|
|
336
|
+
pExisting.LastTestedDate = new Date().toISOString();
|
|
337
|
+
pExisting.Status = pTestError ? 'Failed' : 'OK';
|
|
338
|
+
|
|
339
|
+
let tmpUpdateQuery = this.fable.DAL.StoreConnection.query.clone()
|
|
340
|
+
.addRecord(pExisting);
|
|
341
|
+
|
|
342
|
+
this.fable.DAL.StoreConnection.doUpdate(tmpUpdateQuery,
|
|
343
|
+
() =>
|
|
344
|
+
{
|
|
345
|
+
if (pTestError)
|
|
346
|
+
{
|
|
347
|
+
pResponse.send({ Success: false, Error: pTestError.message || pTestError, Status: 'Failed' });
|
|
348
|
+
}
|
|
349
|
+
else
|
|
350
|
+
{
|
|
351
|
+
pResponse.send({ Success: true, Status: 'OK', Result: pResult || {} });
|
|
352
|
+
}
|
|
353
|
+
return fNext();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// POST /facto/connection/test -- test an ad-hoc connection config (no save)
|
|
360
|
+
pOratorServiceServer.doPost(`${tmpRoutePrefix}/connection/test`,
|
|
361
|
+
(pRequest, pResponse, fNext) =>
|
|
362
|
+
{
|
|
363
|
+
let tmpBody = pRequest.body || {};
|
|
364
|
+
let tmpType = tmpBody.Type || '';
|
|
365
|
+
let tmpConfig = tmpBody.Config || {};
|
|
366
|
+
|
|
367
|
+
if (!tmpType)
|
|
368
|
+
{
|
|
369
|
+
pResponse.send({ Success: false, Error: 'Type is required' });
|
|
370
|
+
return fNext();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this._testConnection(tmpType, tmpConfig,
|
|
374
|
+
(pTestError, pResult) =>
|
|
375
|
+
{
|
|
376
|
+
if (pTestError)
|
|
377
|
+
{
|
|
378
|
+
pResponse.send({ Success: false, Error: pTestError.message || pTestError });
|
|
379
|
+
}
|
|
380
|
+
else
|
|
381
|
+
{
|
|
382
|
+
pResponse.send({ Success: true, Result: pResult || {} });
|
|
383
|
+
}
|
|
384
|
+
return fNext();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// GET /facto/connection/available-types -- list installed connector types
|
|
389
|
+
pOratorServiceServer.doGet(`${tmpRoutePrefix}/connection/available-types`,
|
|
390
|
+
(pRequest, pResponse, fNext) =>
|
|
391
|
+
{
|
|
392
|
+
let tmpAvailable = [];
|
|
393
|
+
let tmpProviders = this.fable.MeadowConnectionManager.getAvailableProviders();
|
|
394
|
+
let tmpTypes = Object.keys(tmpProviders);
|
|
395
|
+
|
|
396
|
+
for (let i = 0; i < tmpTypes.length; i++)
|
|
397
|
+
{
|
|
398
|
+
tmpAvailable.push(
|
|
399
|
+
{
|
|
400
|
+
Type: tmpTypes[i],
|
|
401
|
+
Installed: tmpProviders[tmpTypes[i]]
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
pResponse.send({ Types: tmpAvailable });
|
|
406
|
+
return fNext();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
this.fable.log.info(`StoreConnectionManager routes connected at ${tmpRoutePrefix}/connection*`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Test a database connection by attempting to connect with the given config.
|
|
414
|
+
*
|
|
415
|
+
* @param {string} pType - Connection type (MySQL, PostgreSQL, etc.)
|
|
416
|
+
* @param {object} pConfig - Connection configuration object
|
|
417
|
+
* @param {function} fCallback - Callback(pError, pResult)
|
|
418
|
+
*/
|
|
419
|
+
_testConnection(pType, pConfig, fCallback)
|
|
420
|
+
{
|
|
421
|
+
let tmpTestConfig = Object.assign({}, pConfig, { Type: pType });
|
|
422
|
+
|
|
423
|
+
this.fable.MeadowConnectionManager.testConnection(tmpTestConfig,
|
|
424
|
+
(pError, pResult) =>
|
|
425
|
+
{
|
|
426
|
+
if (pError)
|
|
427
|
+
{
|
|
428
|
+
return fCallback(pError);
|
|
429
|
+
}
|
|
430
|
+
if (!pResult.Success)
|
|
431
|
+
{
|
|
432
|
+
return fCallback(new Error(pResult.Error || 'Connection test failed'));
|
|
433
|
+
}
|
|
434
|
+
return fCallback(null, { Connected: true, Type: pType });
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = RetoldFactoStoreConnectionManager;
|
|
440
|
+
module.exports.serviceType = 'RetoldFactoStoreConnectionManager';
|
|
441
|
+
module.exports.default_configuration = defaultStoreConnectionManagerOptions;
|