retold-harness 1.1.3 → 1.1.5
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/package.json +2 -1
- package/source/providers/Retold-Harness-Service-MeadowProviderConfigurator.js +49 -1
- package/source/schemas/Retold-Harness-Service-Schema-Bookstore.js +90 -0
- package/source/web/harness_app.js +4602 -0
- package/source/web/index.html +79 -265
- package/source/web/pict.js +8072 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-harness",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Restful API harness. Serves on 8086.",
|
|
5
5
|
"main": "source/Retold-Harness.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"pict-application": "^1.0.33",
|
|
49
49
|
"pict-terminalui": "^0.0.3",
|
|
50
50
|
"pict-view": "^1.0.67",
|
|
51
|
+
"orator-authentication": "file:../../orator/orator-authentication",
|
|
51
52
|
"retold-data-service": "^2.0.19"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
@@ -146,7 +146,8 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
|
|
|
146
146
|
*/
|
|
147
147
|
serveWebUI(fCallback)
|
|
148
148
|
{
|
|
149
|
-
let
|
|
149
|
+
let tmpWebFolder = libPath.join(__dirname, '..', 'web');
|
|
150
|
+
let tmpWebUIPath = libPath.join(tmpWebFolder, 'index.html');
|
|
150
151
|
|
|
151
152
|
if (!libFS.existsSync(tmpWebUIPath))
|
|
152
153
|
{
|
|
@@ -156,6 +157,7 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
|
|
|
156
157
|
|
|
157
158
|
let tmpWebUIHTML = libFS.readFileSync(tmpWebUIPath, 'utf8');
|
|
158
159
|
|
|
160
|
+
// Serve index.html at the root URL
|
|
159
161
|
this.fable.OratorServiceServer.server.get('/',
|
|
160
162
|
(pRequest, pResponse, fNext) =>
|
|
161
163
|
{
|
|
@@ -164,6 +166,51 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
|
|
|
164
166
|
return fNext();
|
|
165
167
|
});
|
|
166
168
|
|
|
169
|
+
// Serve static files (JS, CSS, etc.) from the web folder.
|
|
170
|
+
// Files are served at their plain filename, e.g. /pict.js → source/web/pict.js.
|
|
171
|
+
// This route is registered after all API routes, so API endpoints take priority.
|
|
172
|
+
let tmpMimeTypes =
|
|
173
|
+
{
|
|
174
|
+
'.js': 'application/javascript',
|
|
175
|
+
'.css': 'text/css',
|
|
176
|
+
'.html': 'text/html',
|
|
177
|
+
'.json': 'application/json',
|
|
178
|
+
'.map': 'application/json',
|
|
179
|
+
'.png': 'image/png',
|
|
180
|
+
'.jpg': 'image/jpeg',
|
|
181
|
+
'.svg': 'image/svg+xml',
|
|
182
|
+
'.ico': 'image/x-icon'
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.fable.OratorServiceServer.server.get('/:filename',
|
|
186
|
+
(pRequest, pResponse, fNext) =>
|
|
187
|
+
{
|
|
188
|
+
let tmpRequestedFile = pRequest.params.filename;
|
|
189
|
+
|
|
190
|
+
// Only serve files with recognized extensions
|
|
191
|
+
let tmpExt = libPath.extname(tmpRequestedFile).toLowerCase();
|
|
192
|
+
if (!tmpMimeTypes[tmpExt])
|
|
193
|
+
{
|
|
194
|
+
return fNext();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Prevent directory traversal
|
|
198
|
+
let tmpSafeName = libPath.basename(tmpRequestedFile);
|
|
199
|
+
let tmpFilePath = libPath.join(tmpWebFolder, tmpSafeName);
|
|
200
|
+
|
|
201
|
+
if (!libFS.existsSync(tmpFilePath) || !libFS.statSync(tmpFilePath).isFile())
|
|
202
|
+
{
|
|
203
|
+
return fNext();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let tmpContentType = tmpMimeTypes[tmpExt];
|
|
207
|
+
let tmpFileContent = libFS.readFileSync(tmpFilePath);
|
|
208
|
+
|
|
209
|
+
pResponse.writeHead(200, { 'Content-Type': tmpContentType });
|
|
210
|
+
pResponse.end(tmpFileContent);
|
|
211
|
+
return fNext();
|
|
212
|
+
});
|
|
213
|
+
|
|
167
214
|
return fCallback();
|
|
168
215
|
}
|
|
169
216
|
|
|
@@ -189,6 +236,7 @@ class RetoldHarnessMeadowProviderConfigurator extends libFableServiceProviderBas
|
|
|
189
236
|
let tmpPort = this.fable.settings.APIServerPort || 8086;
|
|
190
237
|
this.log.info(`Retold Harness running on port ${tmpPort}`);
|
|
191
238
|
this.log.info(`Web UI available at http://localhost:${tmpPort}/`);
|
|
239
|
+
this.log.info(`Authentication: http://localhost:${tmpPort}/1.0/Authenticate/{user}/{pass}`);
|
|
192
240
|
return fStepComplete();
|
|
193
241
|
});
|
|
194
242
|
|
|
@@ -98,6 +98,96 @@ class RetoldHarnessSchemaBookstore extends libRetoldHarnessSchemaProvider
|
|
|
98
98
|
{
|
|
99
99
|
let tmpFable = this.fable;
|
|
100
100
|
|
|
101
|
+
// --- Enable body parsing for POST routes ---
|
|
102
|
+
if (tmpFable.OratorServiceServer && typeof tmpFable.OratorServiceServer.bodyParser === 'function')
|
|
103
|
+
{
|
|
104
|
+
tmpFable.OratorServiceServer.server.use(tmpFable.OratorServiceServer.bodyParser());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Authentication Setup ---
|
|
108
|
+
let libOratorAuthentication = require('orator-authentication');
|
|
109
|
+
|
|
110
|
+
tmpFable.serviceManager.addServiceType('OratorAuthentication', libOratorAuthentication);
|
|
111
|
+
tmpFable.serviceManager.instantiateServiceProvider('OratorAuthentication',
|
|
112
|
+
{
|
|
113
|
+
DeniedPasswords: ['abc', 'badpassword', '111']
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Plug in a fake authenticator that accepts any existing User by LoginID.
|
|
117
|
+
// Password is ignored — the only failures come from the denied list or
|
|
118
|
+
// a LoginID that doesn't exist in the database.
|
|
119
|
+
tmpFable.OratorAuthentication.setAuthenticator(
|
|
120
|
+
(pUsername, pPassword, fAuthCallback) =>
|
|
121
|
+
{
|
|
122
|
+
if (!tmpFable.DAL || !tmpFable.DAL.User)
|
|
123
|
+
{
|
|
124
|
+
tmpFable.log.warn('BookStore authenticator: DAL.User not available, allowing login with stub record.');
|
|
125
|
+
return fAuthCallback(null, { LoginID: pUsername, IDUser: 0 });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let tmpQuery = tmpFable.DAL.User.query.addFilter('LoginID', pUsername);
|
|
129
|
+
tmpFable.DAL.User.doRead(tmpQuery,
|
|
130
|
+
(pReadError, pReadQuery, pUserRecord) =>
|
|
131
|
+
{
|
|
132
|
+
if (pReadError)
|
|
133
|
+
{
|
|
134
|
+
tmpFable.log.error(`BookStore authenticator error: ${pReadError}`);
|
|
135
|
+
return fAuthCallback(pReadError, null);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!pUserRecord || !pUserRecord.IDUser || pUserRecord.IDUser < 1)
|
|
139
|
+
{
|
|
140
|
+
tmpFable.log.info(`BookStore authenticator: User [${pUsername}] not found.`);
|
|
141
|
+
return fAuthCallback(null, null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
tmpFable.log.info(`BookStore authenticator: User [${pUsername}] found (IDUser ${pUserRecord.IDUser}).`);
|
|
145
|
+
return fAuthCallback(null, pUserRecord);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
tmpFable.OratorAuthentication.connectRoutes();
|
|
150
|
+
this.log.info('BookStore authentication routes registered.');
|
|
151
|
+
|
|
152
|
+
// --- Demo-Only Endpoint: list valid LoginIDs without requiring auth ---
|
|
153
|
+
// This bypasses Meadow's authorization so the web UI can show a
|
|
154
|
+
// convenient user picker. Not something you would do in production!
|
|
155
|
+
tmpFable.Orator.serviceServer.get('/1.0/Demo/Users',
|
|
156
|
+
(pRequest, pResponse, fNext) =>
|
|
157
|
+
{
|
|
158
|
+
if (!tmpFable.DAL || !tmpFable.DAL.User)
|
|
159
|
+
{
|
|
160
|
+
pResponse.send({ Users: [] });
|
|
161
|
+
return fNext();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let tmpQuery = tmpFable.DAL.User.query;
|
|
165
|
+
tmpFable.DAL.User.doReads(tmpQuery,
|
|
166
|
+
(pReadError, pReadQuery, pUserRecords) =>
|
|
167
|
+
{
|
|
168
|
+
if (pReadError || !pUserRecords)
|
|
169
|
+
{
|
|
170
|
+
pResponse.send({ Users: [] });
|
|
171
|
+
return fNext();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let tmpUsers = [];
|
|
175
|
+
for (let i = 0; i < pUserRecords.length; i++)
|
|
176
|
+
{
|
|
177
|
+
tmpUsers.push(
|
|
178
|
+
{
|
|
179
|
+
IDUser: pUserRecords[i].IDUser,
|
|
180
|
+
LoginID: pUserRecords[i].LoginID,
|
|
181
|
+
FullName: pUserRecords[i].FullName || ''
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
pResponse.send({ Users: tmpUsers });
|
|
186
|
+
return fNext();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
this.log.info('BookStore demo user list endpoint registered at /1.0/Demo/Users');
|
|
190
|
+
|
|
101
191
|
if (!tmpFable.MeadowEndpoints || !tmpFable.MeadowEndpoints.Book)
|
|
102
192
|
{
|
|
103
193
|
this.log.warn('BookStore applyBehaviors: MeadowEndpoints.Book not available, skipping author enrichment behavior.');
|