Skip to content

Commit 2e684b5

Browse files
ptitzlerglynnbird
authored andcommitted
Provide new initialization API endpoint (I63) (#64)
* i63:init index * validate schema spec * schema validation: bug fixes
1 parent 92c12d2 commit 2e684b5

File tree

5 files changed

+238
-6
lines changed

5 files changed

+238
-6
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,50 @@ The response is similar to that of editing a row, although again note that the r
252252
}
253253
```
254254

255+
### Initializing the index
256+
257+
To programatically delete all data and initialize the index
258+
259+
```
260+
POST /initialize
261+
```
262+
263+
including the `schema` property in the payload defining the following structure
264+
265+
```
266+
{ "fields": [
267+
{
268+
"name": "id",
269+
"type": "string",
270+
"example": "example_id",
271+
"facet": true
272+
},
273+
{
274+
"name": "score",
275+
"type": "number",
276+
"example": 8,
277+
"facet": false
278+
},
279+
{
280+
"name": "tags",
281+
"type": "arrayofstrings",
282+
"example": "example_tag_1,example_tag_2",
283+
"facet": true
284+
}
285+
]
286+
}
287+
288+
> This example defines a schema containing three fields of which two will be enabled for faceted search.
289+
290+
```
291+
Valid values:
292+
293+
* Property `name`: any string
294+
* Property `type`: `number`, `boolean`, `string`, `arrayofstrings` (e.g. `val1,val2,val3`)
295+
* Property `example`: any valid value for this `type`
296+
* Property `facet`: `true` or `false`
297+
298+
255299
## Privacy Notice
256300

257301
The Simple Search Service web application includes code to track deployments to Bluemix and other Cloud Foundry platforms. The following information is sent to a [Deployment Tracker](https://github.com/IBM-Bluemix/cf-deployment-tracker-service) service on each deployment:

app.js

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,6 @@ app.post('/fetch', bodyParser, isloggedin.auth, function(req, res){
124124

125125
// import previously uploaded CSV
126126
app.post('/import', bodyParser, isloggedin.auth, function(req, res){
127-
console.log("****",req.body.schema);
128-
console.log("****");
129127

130128
var currentUpload = app.locals.import[req.body.upload_id];
131129

@@ -147,6 +145,59 @@ app.post('/import', bodyParser, isloggedin.auth, function(req, res){
147145
res.status(204).end();
148146
});
149147

148+
// Initialize the SSS index by deleting all data and saving the new schema
149+
// Request parameters:
150+
// - schema: JSON defining a valid schema (required)
151+
//
152+
app.post('/initialize', bodyParser, isloggedin.auth, function(req, res){
153+
154+
if((!req.body) || (! req.body.schema)) {
155+
return res.status(400).json({error: "Request rejected", reason: "Schema definition is missing."});
156+
}
157+
158+
var theschema = null;
159+
160+
try {
161+
// parse schema
162+
theschema = JSON.parse(req.body.schema);
163+
}
164+
catch(err) {
165+
// payload is not valid JSON; return client error
166+
console.error("/initialize: payload " + req.body.schema + " caused JSON parsing error: " + JSON.stringify(err));
167+
return res.status(400).json({error: "Request rejected", reason: "Schema definition is not valid JSON."});
168+
}
169+
170+
// validate schema definition
171+
var validationErrors = schema.validateSchemaDef(theschema);
172+
if(validationErrors) {
173+
// payload is not valid schema definition; return client error
174+
return res.status(400).json({error: "Request rejected", reason: "Schema validation failed. Errors: " + validationErrors.join("; ")});
175+
}
176+
177+
var cache = require("./lib/cache.js")(app.locals.cache);
178+
if (cache) {
179+
cache.clearAll();
180+
}
181+
182+
// re-create index database
183+
db.deleteAndCreate(function(err) {
184+
if(err) {
185+
// index database could not be dropped/created; return server error
186+
return res.status(500).json({error: "Request failed", reason: "Index database could not be re-initialized: " + JSON.stringify(err)});
187+
}
188+
// save schema definition
189+
schema.save(theschema, function(err) {
190+
if(err) {
191+
// schema could not be saved; return server error
192+
return res.status(500).json({error: "Request failed", reason: "Schema could not be saved: " + JSON.stringify(err)});
193+
}
194+
// initialization complete; return OK
195+
return res.status(200).end();
196+
});
197+
});
198+
});
199+
200+
150201
app.get('/import/status', isloggedin.auth, function(req, res) {
151202
var status = dbimport.status();
152203
res.send(status);

internal-api-reference.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,48 @@ where `"complete":true` indicates the completion of the import process.
178178

179179
## POST /deleteeverything
180180

181-
Delete the database and start again.
181+
Delete the database and start again.
182+
183+
184+
## POST /initialize
185+
186+
Delete the database and define schema.
187+
188+
A form-encoded HTTP POST is expected to include a valid JSON payload describing the schema
189+
190+
```
191+
"fields": [
192+
{
193+
"name": "id",
194+
"type": "string",
195+
"facet": true,
196+
"example": "4a9f23"
197+
},
198+
{
199+
"name": "tags",
200+
"type": "arrayofstrings",
201+
"facet": true,
202+
"example": "eins,zwei,drei"
203+
},
204+
...
205+
]
206+
```
207+
208+
Each field specification must define the [field] `name`, [field] `type` and `facet` properties.
209+
The `example` property is optional. If set it should contain a valid value.
210+
211+
> All property names are case sensitive.
212+
213+
Valid values:
214+
215+
* `name`: any unique string
216+
* `type`: "`string`" || "`number`" || "`boolean`" || "`arrayofstrings`" (case sensitive)
217+
* `facet`: `true` or `false`
218+
* `example`: any string representing a valid value for the field
219+
220+
Return codes and responses:
221+
222+
* `200` Request was successfully processed.
223+
* `400` The schema definition is invalid. JSON response includes properties `error` and `reason`.
224+
* `500` Request processing failed. JSON response includes properties `error` and `reason`.
225+

lib/schema.js

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,103 @@ var validate = function(schema, row, editMode) {
188188
}
189189

190190
};
191-
191+
192+
/**
193+
* Determines whether a schema definition adheres to the specification. Data structure:
194+
* "fields" : [
195+
* {
196+
* "name": <unique_string>,
197+
* "type": <type_value>,
198+
* "facet": <facet_value>,
199+
* "example": <string>
200+
* },
201+
* ...
202+
* ]
203+
* where <type_value> one of ["string","number","boolean","arrayofstrings"] and <facet_value>
204+
* one of [true, false]. All properties except example are required.
205+
* @param {Object} schema - the schema definition to be validated
206+
* @return {Array} null if the schema definition is valid or an array of Strings listing issues
207+
*/
208+
var validateSchemaDef = function(schema) {
209+
210+
var errors = [];
211+
212+
if((! schema) || ((! schema.hasOwnProperty("fields")))) {
213+
// fatal schema definition error
214+
errors.push("The schema must contain at least one field definition.");
215+
return errors;
216+
}
217+
218+
if(! Array.isArray(schema.fields)) {
219+
// fatal schema definition error
220+
errors.push("The property named `fields` must define a non-empty array of field definitions.");
221+
return errors;
222+
}
223+
224+
if(schema.fields.length < 1) {
225+
// fatal schema definition error
226+
errors.push("The property named `fields` must define a non-empty array of field definitions.");
227+
return errors;
228+
}
229+
230+
// schema specification (property name, data type, required, values)
231+
const fieldSpecs = [
232+
{name: "name", type: "string", required: true},
233+
{name: "type", type: "string", required: true, values: ["string","number","boolean","arrayofstrings"]},
234+
{name: "facet", type: "boolean", required: true, values: [true, false]},
235+
{name: "example", type: "string", required: false}
236+
];
237+
238+
// field counter
239+
var count = 1;
240+
// iterate through all field definitions and verify that they adhere to the specification;
241+
// identify all issues before returning an error
242+
_.each(schema.fields,
243+
function(field) {
244+
_.each(fieldSpecs,
245+
function(fieldSpec) {
246+
if(! field.hasOwnProperty(fieldSpec.name)) {
247+
if(fieldSpec.required) {
248+
// a required property is missing
249+
errors.push("Field " + count + " - property `" + fieldSpec.name + "` is missing");
250+
}
251+
}
252+
else {
253+
// validate data type of property value
254+
if(typeof field[fieldSpec.name] !== fieldSpec.type) {
255+
// the data type of the field's property value is invalid
256+
errors.push("Field " + count + " - data type of property `" + fieldSpec.name + "` must be `" + fieldSpec.type + "`");
257+
}
258+
else {
259+
// validate property value
260+
if((fieldSpec.hasOwnProperty('values')) && (! _.contains(fieldSpec.values, field[fieldSpec.name]))) {
261+
// the property value is invalid
262+
errors.push("Field " + count + " - value of property `" + fieldSpec.name + "` must be one of " + _.map(fieldSpec.values, function(element) { return ("`" + element + "`");}).join(","));
263+
}
264+
// enforce constraint that no facet can be defined for fields with data type "number"
265+
if((field.type === "number") && (field.facet)) {
266+
errors.push("Field " + count + " - cannot facet field with data type `number`. Use data type `string` instead.");
267+
}
268+
}
269+
}
270+
});
271+
count++;
272+
});
273+
274+
if(errors.length) {
275+
// schema definition appears to be invalid; return error list
276+
return errors;
277+
}
278+
else {
279+
// schema definition appears to be valid; return null
280+
return null;
281+
}
282+
};
283+
192284
module.exports = {
193285
load: load,
194286
save: save,
195287
generateSearchIndex: generateSearchIndex,
196-
validate: validate
288+
validate: validate,
289+
validateSchemaDef: validateSchemaDef
197290
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "simple-search-service",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "A Node app that creates a faceted search engine, powered by IBM Cloudant",
55
"scripts": {
66
"start": "node app.js",

0 commit comments

Comments
 (0)