Browse Source

progress commit

pull/2/head
Peter Johnson 10 years ago
parent
commit
4323b0030b
  1. 5
      controller/index.js
  2. 37
      controller/suggest.js
  3. 14
      express.js
  4. 50
      index.js
  5. 8
      middleware/404.js
  6. 9
      middleware/500.js
  7. 3
      package.json
  8. 14
      sanitiser/suggest.js
  9. 33
      src/responder.js
  10. 17
      test/ciao/404.coffee
  11. 9
      test/ciao/cors.coffee
  12. 6
      test/ciao/index.coffee
  13. 10
      test/ciao/jsonp.coffee
  14. 15
      test/ciao/suggest/success.coffee

5
controller/index.js

@ -1,13 +1,16 @@
var pkg = require('../package');
function controller( req, res ){
function controller( req, res, next ){
// stats
res.json({
name: pkg.name,
version: {
number: pkg.version
}
});
}
module.exports = controller;

37
controller/suggest.js

@ -1,32 +1,35 @@
var logger = require('../src/logger'),
responder = require('../src/responder'),
query = require('../query/suggest'),
var query = require('../query/suggest'),
backend = require('../src/backend');
module.exports = function( req, res, next ){
var reply = {
date: new Date().getTime(),
body: []
};
function controller( req, res, next ){
// backend command
var cmd = {
index: 'pelias',
body: query( req.clean ) // generate query from clean params
body: query( req.clean )
};
// Proxy request to ES backend & map response to a valid FeatureCollection
// query backend
backend().client.suggest( cmd, function( err, data ){
if( err ){ return responder.error( req, res, next, err ); }
if( data && data.pelias && data.pelias.length ){
var docs = [];
// map options to reply body
reply.body = data['pelias'][0].options;
// handle backend errors
if( err ){ return next( err ); }
// map response to a valid FeatureCollection
if( data && Array.isArray( data.pelias ) && data.pelias.length ){
docs = data['pelias'][0].options || [];
}
return responder.cors( req, res, reply );
// respond
return res.status(200).json({
date: new Date().getTime(),
body: docs
});
});
};
}
module.exports = controller;

14
express.js

@ -1,14 +0,0 @@
var express = require('express'),
app = express();
// middleware modules
// app.use( require('cookie-parser')() );
// enable client-side caching of 60s by default
app.use(function(req, res, next){
res.header('Cache-Control','public,max-age=60');
next();
});
module.exports = app;

50
index.js

@ -1,5 +1,47 @@
var app = require('./express');
var app = require('express')();
/** ----------------------- middleware ----------------------- **/
// generic headers
app.use(function(req, res, next){
res.header('Charset','utf8');
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET');
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.header('Access-Control-Allow-Credentials', true);
res.header('X-Powered-By', 'pelias');
next();
});
// jsonp middleware
// override json() to handle jsonp
app.use(function(req, res, next){
res._json = res.json;
res.json = function( data ){
// jsonp
if( req.query && req.query.callback ){
res.header('Content-type','application/javascript');
return res.send( req.query.callback + '('+ JSON.stringify( data ) + ');' );
}
// regular json
res.header('Content-type','application/json');
return res._json( data );
};
next();
});
// enable client-side caching of 60s by default
app.use(function(req, res, next){
res.header('Cache-Control','public,max-age=60');
next();
});
/** ----------------------- Routes ----------------------- **/
// api root
app.get( '/', require('./controller/index') );
@ -7,4 +49,10 @@ app.get( '/', require('./controller/index') );
// suggest API
app.get( '/suggest', require('./sanitiser/suggest'), require('./controller/suggest') );
/** ----------------------- error middleware ----------------------- **/
// handle application errors
app.use( require('./middleware/404') );
app.use( require('./middleware/500') );
app.listen( process.env.PORT || 3100 );

8
middleware/404.js

@ -0,0 +1,8 @@
// handle not found errors
function middleware(req, res) {
res.header('Cache-Control','public,max-age=300'); // 5 minute cache
res.status(404).json({ error: 'not found: invalid path' });
}
module.exports = middleware;

9
middleware/500.js

@ -0,0 +1,9 @@
// handle application errors
function middleware(err, req, res, next) {
res.header('Cache-Control','no-cache');
if( res.statusCode < 400 ){ res.status(500); }
res.json({ error: err });
}
module.exports = middleware;

3
package.json

@ -10,7 +10,8 @@
"start": "node index.js",
"test": "npm run unit && npm run ciao",
"unit": "node test/unit/run.js | tap-spec",
"ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao"
"ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao",
"docs": "cd test/ciao; node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs"
},
"repository": {
"type": "git",

14
sanitiser/suggest.js

@ -14,7 +14,7 @@ function sanitize( params, cb ){
// input text
if('string' !== typeof params.input || !params.input.length){
return cb( 'invalid input text length, must be >0' );
return cb( 'invalid param \'input\': text length, must be >0' );
}
clean.input = params.input;
@ -33,7 +33,7 @@ function sanitize( params, cb ){
});
for( var x=0; x<layers.length; x++ ){
if( -1 === indeces.indexOf( layers[x] ) ){
return cb( 'invalid layer, must be one or more of ' + layers.join(',') );
return cb( 'invalid param \'layer\': must be one or more of ' + layers.join(',') );
}
}
clean.layers = layers;
@ -45,14 +45,14 @@ function sanitize( params, cb ){
// lat
var lat = parseFloat( params.lat, 10 );
if( isNaN( lat ) || lat < 0 || lat > 90 ){
return cb( 'invalid lat, must be >0 and <90' );
return cb( 'invalid param \'lat\': must be >0 and <90' );
}
clean.lat = lat;
// lon
var lon = parseFloat( params.lon, 10 );
if( isNaN( lon ) || lon < -180 || lon > 180 ){
return cb( 'invalid lon, must be >-180 and <180' );
return cb( 'invalid param \'lon\': must be >-180 and <180' );
}
clean.lon = lon;
@ -68,9 +68,13 @@ function sanitize( params, cb ){
}
// middleware
module.exports = function( req, res, next ){
sanitize( req.query, function( err, clean ){
if( err ){ next( err ); }
if( err ){
res.status(400); // 400 Bad Request
return next(err);
}
req.clean = clean;
next();
});

33
src/responder.js

@ -1,33 +0,0 @@
// send a reply that is capable of JSON, CORS and JSONP
function cors( req, res, obj ){
res.header('Charset','utf8');
res.header('Cache-Control','public,max-age=60');
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET');
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.header('Access-Control-Allow-Credentials', true);
res.header('X-Powered-By', 'pelias');
// jsonp
if( req.query && req.query.callback ){
res.header('Content-type','application/javascript');
return res.send( req.query.callback + '('+ JSON.stringify( obj ) + ');' );
}
// regular json
res.header('Content-type','application/json');
return res.json( obj );
}
// send an error
function error( req, res, next, err ){
console.error( 'application error:', err );
// mask error from user (contains paths)
return cors( req, res, { error: 'application error' } );
}
module.exports = {
cors: cors,
error: error
};

17
test/ciao/404.coffee

@ -0,0 +1,17 @@
#> invalid path
path: '/notexist'
#? not found
response.statusCode.should.equal 404
#? content-type header correctly set
response.should.have.header 'Content-Type','application/json; charset=utf-8'
#? cache-control header correctly set
response.should.have.header 'Cache-Control','public,max-age=300'
#? should respond in json with server info
should.exist json
should.exist json.error
json.error.should.equal 'not found: invalid path'

9
test/ciao/cors.coffee

@ -0,0 +1,9 @@
#> cross-origin resource sharing
path: '/'
#? access control headers correctly set
response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

6
test/ciao/index.coffee

@ -8,9 +8,15 @@ response.statusCode.should.equal 200
#? content-type header correctly set
response.should.have.header 'Content-Type','application/json; charset=utf-8'
#? charset header correctly set
response.should.have.header 'Charset','utf8'
#? cache-control header correctly set
response.should.have.header 'Cache-Control','public,max-age=60'
#? vanity header correctly set
response.should.have.header 'X-Powered-By','pelias'
#? should respond in json with server info
should.exist json
should.exist json.name

10
test/ciao/jsonp.coffee

@ -0,0 +1,10 @@
#> jsonp
path: '/?callback=test'
#? content-type header correctly set
response.should.have.header 'Content-Type','application/javascript; charset=utf-8'
#? should respond with jsonp
should.exist response.body
response.body.substr(0,5).should.equal 'test(';

15
test/ciao/suggest/success.coffee

@ -0,0 +1,15 @@
#> valid suggest query
path: '/suggest?input=a&lat=0&lon=0'
#? 200 ok
response.statusCode.should.equal 200
#? valid response
now = new Date().getTime()
should.exist json
should.not.exist json.error
should.exist json.date
json.date.should.be.within now-1000, now+1000
should.exist json.body
json.body.should.be.instanceof Array
Loading…
Cancel
Save