2011-10-13 19:17:00 +08:00
|
|
|
var _ = require('underscore')
|
|
|
|
, Step = require('step')
|
2011-10-13 22:20:29 +08:00
|
|
|
, Windshaft = require('windshaft')
|
2013-12-18 00:35:12 +08:00
|
|
|
, SignedMaps = require('./signed_maps.js')
|
|
|
|
, TemplateMaps = require('./template_maps.js')
|
2014-02-19 22:27:20 +08:00
|
|
|
, Cache = require('./cache_validator')
|
|
|
|
, os = require('os')
|
2014-11-05 22:06:01 +08:00
|
|
|
, HealthCheck = require('./monitoring/health_check')
|
2014-02-19 22:27:20 +08:00
|
|
|
;
|
2011-10-13 19:17:00 +08:00
|
|
|
|
2014-05-07 22:19:22 +08:00
|
|
|
if ( ! process.env['PGAPPNAME'] )
|
|
|
|
process.env['PGAPPNAME']='cartodb_tiler';
|
|
|
|
|
2011-10-13 19:17:00 +08:00
|
|
|
var CartodbWindshaft = function(serverOptions) {
|
2014-02-11 23:34:43 +08:00
|
|
|
var debug = global.environment.debug;
|
2011-10-13 19:17:00 +08:00
|
|
|
|
2014-02-19 22:27:20 +08:00
|
|
|
// Perform keyword substitution in statsd
|
|
|
|
// See https://github.com/CartoDB/Windshaft-cartodb/issues/153
|
|
|
|
if ( global.environment.statsd ) {
|
|
|
|
if ( global.environment.statsd.prefix ) {
|
2014-02-22 00:25:10 +08:00
|
|
|
var host_token = os.hostname().split('.').reverse().join('.');
|
|
|
|
global.environment.statsd.prefix = global.environment.statsd.prefix.replace(/:host/, host_token);
|
2014-02-19 22:27:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-24 17:42:36 +08:00
|
|
|
var redisPool = serverOptions.redis.pool
|
|
|
|
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
|
|
|
|
|
|
|
|
var cartoData = require('cartodb-redis')({pool: redisPool});
|
|
|
|
|
2011-12-01 02:59:28 +08:00
|
|
|
if(serverOptions.cache_enabled) {
|
2011-12-01 03:00:41 +08:00
|
|
|
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
|
2014-02-12 22:34:19 +08:00
|
|
|
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
|
2011-12-01 02:59:28 +08:00
|
|
|
serverOptions.afterStateChange = function(req, data, callback) {
|
2012-05-03 02:32:54 +08:00
|
|
|
Cache.invalidate_db(req.params.dbname, req.params.table);
|
2011-12-01 02:59:28 +08:00
|
|
|
callback(null, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-14 22:15:41 +08:00
|
|
|
serverOptions.beforeStateChange = function(req, callback) {
|
|
|
|
var err = null;
|
2014-02-08 01:00:13 +08:00
|
|
|
if ( ! req.params.hasOwnProperty('_authorizedByApiKey') ) {
|
2012-08-14 22:15:41 +08:00
|
|
|
err = new Error("map state cannot be changed by unauthenticated request!");
|
|
|
|
}
|
|
|
|
callback(err, req);
|
2014-09-24 17:42:53 +08:00
|
|
|
};
|
2012-08-14 22:15:41 +08:00
|
|
|
|
2014-03-04 17:46:15 +08:00
|
|
|
// This is for Templated maps
|
|
|
|
//
|
|
|
|
// "named" is the official, "template" is for backward compatibility up to 1.6.x
|
|
|
|
//
|
|
|
|
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
|
|
|
|
|
2013-12-18 00:35:12 +08:00
|
|
|
serverOptions.signedMaps = new SignedMaps(redisPool);
|
2014-02-13 22:01:58 +08:00
|
|
|
var templateMapsOpts = {
|
2014-02-13 21:55:31 +08:00
|
|
|
max_user_templates: global.environment.maxUserTemplates
|
2014-09-24 17:42:53 +08:00
|
|
|
};
|
2014-02-13 22:01:58 +08:00
|
|
|
var templateMaps = new TemplateMaps(redisPool, serverOptions.signedMaps, templateMapsOpts);
|
2013-12-18 00:35:12 +08:00
|
|
|
|
2011-10-13 22:20:29 +08:00
|
|
|
// boot
|
|
|
|
var ws = new Windshaft.Server(serverOptions);
|
2011-11-30 04:19:10 +08:00
|
|
|
|
2012-10-08 23:26:12 +08:00
|
|
|
// Override getVersion to include cartodb-specific versions
|
|
|
|
var wsversion = ws.getVersion;
|
|
|
|
ws.getVersion = function() {
|
|
|
|
var version = wsversion();
|
|
|
|
version.windshaft_cartodb = require('../../package.json').version;
|
|
|
|
return version;
|
2014-09-24 17:42:53 +08:00
|
|
|
};
|
2012-10-08 23:26:12 +08:00
|
|
|
|
2014-02-19 17:08:25 +08:00
|
|
|
var ws_sendResponse = ws.sendResponse;
|
2014-03-04 21:26:41 +08:00
|
|
|
// GET routes for which we don't want to request any caching.
|
|
|
|
// POST/PUT/DELETE requests are never cached anyway.
|
|
|
|
var noCacheGETRoutes = [
|
|
|
|
'/',
|
|
|
|
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
|
2014-03-04 17:46:15 +08:00
|
|
|
serverOptions.base_url_mapconfig,
|
|
|
|
template_baseurl + '/:template_id/jsonp'
|
|
|
|
];
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse = function(res, args) {
|
|
|
|
var that = this;
|
|
|
|
var thatArgs = arguments;
|
|
|
|
var statusCode;
|
2014-03-21 20:58:20 +08:00
|
|
|
if ( res._windshaftStatusCode ) {
|
|
|
|
// Added by our override of sendError
|
|
|
|
statusCode = res._windshaftStatusCode;
|
|
|
|
} else {
|
|
|
|
if ( args.length > 2 ) statusCode = args[2];
|
|
|
|
else {
|
|
|
|
statusCode = args[1] || 200;
|
|
|
|
}
|
2014-02-19 17:08:25 +08:00
|
|
|
}
|
|
|
|
var req = res.req;
|
|
|
|
Step (
|
|
|
|
function addCacheChannel() {
|
|
|
|
if ( ! req ) {
|
|
|
|
// having no associated request can happen when
|
|
|
|
// using fake response objects for testing layergroup
|
|
|
|
// creation
|
|
|
|
return false;
|
|
|
|
}
|
2014-02-25 00:34:00 +08:00
|
|
|
if ( ! req.params ) {
|
|
|
|
// service requests (/version, /)
|
|
|
|
// have no need for an X-Cache-Channel
|
|
|
|
return false;
|
|
|
|
}
|
2014-02-19 17:08:25 +08:00
|
|
|
if ( statusCode != 200 ) {
|
|
|
|
// We do not want to cache
|
|
|
|
// unsuccessful responses
|
|
|
|
return false;
|
|
|
|
}
|
2014-03-04 21:26:41 +08:00
|
|
|
if ( _.contains(noCacheGETRoutes, req.route.path) ) {
|
2014-03-04 17:46:15 +08:00
|
|
|
//console.log("Skipping cache channel in route:\n" + req.route.path);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" + mapCreateRoutes.join("\n"));
|
2014-02-19 17:08:25 +08:00
|
|
|
serverOptions.addCacheChannel(that, req, this);
|
|
|
|
},
|
|
|
|
function sendResponse(err, added) {
|
2014-03-04 17:46:15 +08:00
|
|
|
if ( err ) console.log(err + err.stack);
|
2014-02-19 17:08:25 +08:00
|
|
|
ws_sendResponse.apply(that, thatArgs);
|
2014-03-05 01:04:58 +08:00
|
|
|
return null;
|
|
|
|
},
|
|
|
|
function finish(err) {
|
|
|
|
if ( err ) console.log(err + err.stack);
|
2014-02-19 17:08:25 +08:00
|
|
|
}
|
|
|
|
);
|
2014-01-13 18:20:02 +08:00
|
|
|
};
|
|
|
|
|
2014-03-21 20:58:20 +08:00
|
|
|
var ws_sendError = ws.sendError;
|
|
|
|
ws.sendError = function() {
|
|
|
|
var res = arguments[0];
|
|
|
|
var statusCode = arguments[2];
|
|
|
|
res._windshaftStatusCode = statusCode;
|
|
|
|
ws_sendError.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
2011-10-13 22:20:29 +08:00
|
|
|
/**
|
|
|
|
* Helper to allow access to the layer to be used in the maps infowindow popup.
|
|
|
|
*/
|
|
|
|
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
|
2012-08-14 20:48:10 +08:00
|
|
|
ws.doCORS(res);
|
2011-10-13 22:20:29 +08:00
|
|
|
Step(
|
|
|
|
function(){
|
|
|
|
serverOptions.getInfowindow(req, this);
|
|
|
|
},
|
|
|
|
function(err, data){
|
|
|
|
if (err){
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, {error: err.message}, 500, 'GET INFOWINDOW', err);
|
2014-02-19 17:08:25 +08:00
|
|
|
//ws.sendResponse(res, [{error: err.message}, 500]);
|
2011-10-13 22:20:29 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [{infowindow: data}, 200]);
|
2011-10-13 22:20:29 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
2011-10-13 19:17:00 +08:00
|
|
|
|
2011-12-01 02:59:28 +08:00
|
|
|
|
2013-12-17 00:17:55 +08:00
|
|
|
/**
|
|
|
|
* Helper to allow access to metadata to be used in embedded maps.
|
|
|
|
*/
|
|
|
|
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
|
|
|
|
ws.doCORS(res);
|
|
|
|
Step(
|
|
|
|
function(){
|
|
|
|
serverOptions.getMapMetadata(req, this);
|
|
|
|
},
|
|
|
|
function(err, data){
|
|
|
|
if (err){
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, {error: err.message}, 500, 'GET MAP_METADATA', err);
|
2014-02-19 17:08:25 +08:00
|
|
|
//ws.sendResponse(res, [err.message, 500]);
|
2013-12-17 00:17:55 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [{map_metadata: data}, 200]);
|
2013-12-17 00:17:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2012-05-03 02:32:54 +08:00
|
|
|
/**
|
|
|
|
* Helper API to allow per table tile cache (and sql cache) to be invalidated remotely.
|
|
|
|
* TODO: Move?
|
|
|
|
*/
|
|
|
|
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
|
2014-03-03 23:24:20 +08:00
|
|
|
if ( req.profiler && req.profiler.statsd_client ) {
|
|
|
|
req.profiler.start('windshaft-cartodb.flush_cache');
|
|
|
|
}
|
2012-08-14 20:48:10 +08:00
|
|
|
ws.doCORS(res);
|
2012-05-03 02:32:54 +08:00
|
|
|
Step(
|
2013-03-16 02:25:13 +08:00
|
|
|
function flushCache(){
|
2012-10-05 22:55:58 +08:00
|
|
|
serverOptions.flushCache(req, serverOptions.cache_enabled ? Cache : null, this);
|
2012-05-03 02:32:54 +08:00
|
|
|
},
|
2013-03-16 02:25:13 +08:00
|
|
|
function sendResponse(err, data){
|
2012-05-03 02:32:54 +08:00
|
|
|
if (err){
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, {error: err.message}, 500, 'DELETE CACHE', err);
|
2014-02-19 17:08:25 +08:00
|
|
|
//ws.sendResponse(res, [500]);
|
2012-05-03 02:32:54 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [{status: 'ok'}, 200]);
|
2012-05-03 02:32:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
2013-12-18 00:35:12 +08:00
|
|
|
|
|
|
|
// ---- Template maps interface starts @{
|
|
|
|
|
|
|
|
ws.userByReq = function(req) {
|
|
|
|
return serverOptions.userByReq(req);
|
2014-09-24 17:42:53 +08:00
|
|
|
};
|
2013-12-18 00:35:12 +08:00
|
|
|
|
|
|
|
// Add a template
|
|
|
|
ws.post(template_baseurl, function(req, res) {
|
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
Step(
|
|
|
|
function checkPerms(){
|
|
|
|
serverOptions.authorizedByAPIKey(req, this);
|
|
|
|
},
|
|
|
|
function addTemplate(err, authenticated) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
if (authenticated !== 1) {
|
|
|
|
err = new Error("Only authenticated user can create templated maps");
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
var next = this;
|
|
|
|
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
|
|
|
throw new Error('template POST data must be of type application/json');
|
|
|
|
var cfg = req.body;
|
|
|
|
templateMaps.addTemplate(cdbuser, cfg, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err, tpl_id){
|
|
|
|
if ( err ) throw err;
|
|
|
|
// NOTE: might omit "cdbuser" if == dbowner ...
|
|
|
|
return { template_id: cdbuser + '@' + tpl_id };
|
|
|
|
},
|
|
|
|
function finish(err, response){
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) {
|
2014-08-04 18:53:15 +08:00
|
|
|
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
2014-02-15 15:06:41 +08:00
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
if (err){
|
|
|
|
response = { error: ''+err };
|
|
|
|
var statusCode = 400;
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'POST TEMPLATE', err);
|
2013-12-18 00:35:12 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [response, 200]);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update a template
|
|
|
|
ws.put(template_baseurl + '/:template_id', function(req, res) {
|
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
var template;
|
|
|
|
var tpl_id;
|
|
|
|
Step(
|
|
|
|
function checkPerms(){
|
|
|
|
serverOptions.authorizedByAPIKey(req, this);
|
|
|
|
},
|
|
|
|
function updateTemplate(err, authenticated) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
if (authenticated !== 1) {
|
|
|
|
err = new Error("Only authenticated user can list templated maps");
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
|
|
|
throw new Error('template PUT data must be of type application/json');
|
|
|
|
template = req.body;
|
|
|
|
tpl_id = req.params.template_id.split('@');
|
|
|
|
if ( tpl_id.length > 1 ) {
|
|
|
|
if ( tpl_id[0] != cdbuser ) {
|
|
|
|
err = new Error("Invalid template id '"
|
|
|
|
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
|
|
|
err.http_status = 404;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
tpl_id = tpl_id[1];
|
|
|
|
}
|
|
|
|
templateMaps.updTemplate(cdbuser, tpl_id, template, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err){
|
|
|
|
if ( err ) throw err;
|
|
|
|
return { template_id: cdbuser + '@' + tpl_id };
|
|
|
|
},
|
|
|
|
function finish(err, response){
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) {
|
2014-08-04 18:53:15 +08:00
|
|
|
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
2014-02-15 15:06:41 +08:00
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
if (err){
|
|
|
|
var statusCode = 400;
|
|
|
|
response = { error: ''+err };
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'PUT TEMPLATE', err);
|
2013-12-18 00:35:12 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [response, 200]);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Get a specific template
|
|
|
|
ws.get(template_baseurl + '/:template_id', function(req, res) {
|
2014-03-03 23:24:20 +08:00
|
|
|
if ( req.profiler && req.profiler.statsd_client ) {
|
|
|
|
req.profiler.start('windshaft-cartodb.get_template');
|
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
var template;
|
|
|
|
var tpl_id;
|
|
|
|
Step(
|
|
|
|
function checkPerms(){
|
|
|
|
serverOptions.authorizedByAPIKey(req, this);
|
|
|
|
},
|
|
|
|
function updateTemplate(err, authenticated) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
if (authenticated !== 1) {
|
|
|
|
err = new Error("Only authenticated users can get template maps");
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
tpl_id = req.params.template_id.split('@');
|
|
|
|
if ( tpl_id.length > 1 ) {
|
|
|
|
if ( tpl_id[0] != cdbuser ) {
|
|
|
|
var err = new Error("Cannot get template id '"
|
|
|
|
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
|
|
|
err.http_status = 404;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
tpl_id = tpl_id[1];
|
|
|
|
}
|
|
|
|
templateMaps.getTemplate(cdbuser, tpl_id, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err, tpl_val){
|
|
|
|
if ( err ) throw err;
|
|
|
|
if ( ! tpl_val ) {
|
|
|
|
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
|
|
|
|
err.http_status = 404;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
// auth_id was added by ourselves,
|
|
|
|
// so we remove it before returning to the user
|
|
|
|
delete tpl_val.auth_id;
|
|
|
|
return { template: tpl_val };
|
|
|
|
},
|
|
|
|
function finish(err, response){
|
|
|
|
if (err){
|
|
|
|
var statusCode = 400;
|
|
|
|
response = { error: ''+err };
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'GET TEMPLATE', err);
|
2013-12-18 00:35:12 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [response, 200]);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Delete a specific template
|
2014-03-03 23:24:20 +08:00
|
|
|
ws.del(template_baseurl + '/:template_id', function(req, res) {
|
|
|
|
if ( req.profiler && req.profiler.statsd_client ) {
|
|
|
|
req.profiler.start('windshaft-cartodb.delete_template');
|
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
var template;
|
|
|
|
var tpl_id;
|
|
|
|
Step(
|
|
|
|
function checkPerms(){
|
|
|
|
serverOptions.authorizedByAPIKey(req, this);
|
|
|
|
},
|
|
|
|
function updateTemplate(err, authenticated) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
if (authenticated !== 1) {
|
|
|
|
err = new Error("Only authenticated users can delete template maps");
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
tpl_id = req.params.template_id.split('@');
|
|
|
|
if ( tpl_id.length > 1 ) {
|
|
|
|
if ( tpl_id[0] != cdbuser ) {
|
|
|
|
var err = new Error("Cannot find template id '"
|
|
|
|
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
|
|
|
err.http_status = 404;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
tpl_id = tpl_id[1];
|
|
|
|
}
|
|
|
|
templateMaps.delTemplate(cdbuser, tpl_id, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err, tpl_val){
|
|
|
|
if ( err ) throw err;
|
|
|
|
return { status: 'ok' };
|
|
|
|
},
|
|
|
|
function finish(err, response){
|
|
|
|
if (err){
|
|
|
|
var statusCode = 400;
|
|
|
|
response = { error: ''+err };
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'DELETE TEMPLATE', err);
|
2013-12-18 00:35:12 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, ['', 204]);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Get a list of owned templates
|
|
|
|
ws.get(template_baseurl, function(req, res) {
|
2014-03-03 23:24:20 +08:00
|
|
|
if ( req.profiler && req.profiler.statsd_client ) {
|
|
|
|
req.profiler.start('windshaft-cartodb.get_template_list');
|
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
Step(
|
|
|
|
function checkPerms(){
|
|
|
|
serverOptions.authorizedByAPIKey(req, this);
|
|
|
|
},
|
|
|
|
function listTemplates(err, authenticated) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
if (authenticated !== 1) {
|
|
|
|
err = new Error("Only authenticated user can list templated maps");
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
templateMaps.listTemplates(cdbuser, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err, tpl_ids){
|
|
|
|
if ( err ) throw err;
|
|
|
|
// NOTE: might omit "cbduser" if == dbowner ...
|
2014-09-24 17:42:53 +08:00
|
|
|
var ids = _.map(tpl_ids, function(id) { return cdbuser + '@' + id; });
|
2013-12-18 00:35:12 +08:00
|
|
|
return { template_ids: ids };
|
|
|
|
},
|
|
|
|
function finish(err, response){
|
|
|
|
var statusCode = 200;
|
|
|
|
if (err){
|
|
|
|
response = { error: ''+err };
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'GET TEMPLATE LIST', err);
|
2013-12-18 00:35:12 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [response, statusCode]);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
ws.setDBParams = function(cdbuser, params, callback) {
|
|
|
|
Step(
|
|
|
|
function setAuth() {
|
|
|
|
serverOptions.setDBAuth(cdbuser, params, this);
|
|
|
|
},
|
|
|
|
function setConn(err) {
|
|
|
|
if ( err ) throw err;
|
|
|
|
serverOptions.setDBConn(cdbuser, params, this);
|
|
|
|
},
|
|
|
|
function finish(err) {
|
|
|
|
callback(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2014-01-23 02:10:09 +08:00
|
|
|
ws.options(template_baseurl + '/:template_id', function(req, res) {
|
|
|
|
ws.doCORS(res, "Content-Type");
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
|
2013-12-18 00:35:12 +08:00
|
|
|
// Instantiate a template
|
2014-01-29 21:30:27 +08:00
|
|
|
function instanciateTemplate(req, res, template_params, callback) {
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.doCORS(res);
|
|
|
|
var that = this;
|
|
|
|
var response = {};
|
|
|
|
var template;
|
|
|
|
var signedMaps = serverOptions.signedMaps;
|
|
|
|
var layergroup;
|
|
|
|
var layergroupid;
|
|
|
|
var fakereq; // used for call to createLayergroup
|
|
|
|
var cdbuser = ws.userByReq(req);
|
|
|
|
// Format of template_id: [<template_owner>]@<template_id>
|
|
|
|
var tpl_id = req.params.template_id.split('@');
|
|
|
|
if ( tpl_id.length > 1 ) {
|
2014-02-28 23:05:46 +08:00
|
|
|
if ( tpl_id[0] && tpl_id[0] != cdbuser ) {
|
|
|
|
var err = new Error('Cannot instanciate map of user "'
|
|
|
|
+ tpl_id[0] + '" on database of user "'
|
2014-09-24 17:42:53 +08:00
|
|
|
+ cdbuser + '"');
|
2014-02-28 23:05:46 +08:00
|
|
|
err.http_status = 403;
|
|
|
|
callback(err);
|
|
|
|
return;
|
|
|
|
}
|
2013-12-18 00:35:12 +08:00
|
|
|
tpl_id = tpl_id[1];
|
|
|
|
}
|
|
|
|
var auth_token = req.query.auth_token;
|
|
|
|
Step(
|
|
|
|
function getTemplate(){
|
|
|
|
templateMaps.getTemplate(cdbuser, tpl_id, this);
|
|
|
|
},
|
|
|
|
function checkAuthorized(err, data) {
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('getTemplate');
|
2013-12-18 00:35:12 +08:00
|
|
|
if ( err ) throw err;
|
|
|
|
if ( ! data ) {
|
|
|
|
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
|
|
|
|
err.http_status = 404;
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
template = data;
|
|
|
|
var cert = templateMaps.getTemplateCertificate(template);
|
|
|
|
var authorized = false;
|
|
|
|
try {
|
|
|
|
// authorizedByCert will throw if unauthorized
|
|
|
|
authorized = signedMaps.authorizedByCert(cert, auth_token);
|
|
|
|
} catch (err) {
|
|
|
|
// we catch to add http_status
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
if ( ! authorized ) {
|
|
|
|
err = new Error('Unauthorized template instanciation');
|
2014-03-04 00:18:06 +08:00
|
|
|
err.http_status = 403;
|
2013-12-18 00:35:12 +08:00
|
|
|
throw err;
|
|
|
|
}
|
2014-01-29 20:11:37 +08:00
|
|
|
/*if ( (! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') && req.query.callback === undefined) {
|
2013-12-18 00:35:12 +08:00
|
|
|
throw new Error('template POST data must be of type application/json, it is instead ');
|
2014-01-29 20:11:37 +08:00
|
|
|
}*/
|
|
|
|
//var template_params = req.body;
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('authorizedByCert');
|
2013-12-18 00:35:12 +08:00
|
|
|
return templateMaps.instance(template, template_params);
|
|
|
|
},
|
|
|
|
function prepareParams(err, instance){
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
|
2013-12-18 00:35:12 +08:00
|
|
|
if ( err ) throw err;
|
|
|
|
layergroup = instance;
|
2014-02-17 15:50:12 +08:00
|
|
|
fakereq = { query: {}, params: {}, headers: _.clone(req.headers),
|
2014-06-24 21:52:47 +08:00
|
|
|
method: req.method,
|
|
|
|
res: res,
|
2014-02-17 15:50:12 +08:00
|
|
|
profiler: req.profiler
|
|
|
|
};
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.setDBParams(cdbuser, fakereq.params, this);
|
|
|
|
},
|
2014-01-28 19:36:38 +08:00
|
|
|
function setApiKey(err){
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('setDBParams');
|
2013-12-18 00:35:12 +08:00
|
|
|
if ( err ) throw err;
|
2014-01-28 19:36:38 +08:00
|
|
|
cartoData.getUserMapKey(cdbuser, this);
|
|
|
|
},
|
|
|
|
function createLayergroup(err, val) {
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('getUserMapKey');
|
2014-01-28 19:36:38 +08:00
|
|
|
if ( err ) throw err;
|
|
|
|
fakereq.params.api_key = val;
|
2013-12-18 00:35:12 +08:00
|
|
|
ws.createLayergroup(layergroup, fakereq, this);
|
|
|
|
},
|
|
|
|
function signLayergroup(err, resp) {
|
2014-02-17 15:50:12 +08:00
|
|
|
// NOTE: createLayergroup uses profiler.start()/end() internally
|
|
|
|
//if ( req.profiler ) req.profiler.done('createLayergroup');
|
2013-12-18 00:35:12 +08:00
|
|
|
if ( err ) throw err;
|
|
|
|
response = resp;
|
|
|
|
var signer = cdbuser;
|
|
|
|
var map_id = response.layergroupid.split(':')[0]; // dropping last_updated
|
|
|
|
var crt_id = template.auth_id; // check ?
|
|
|
|
if ( ! crt_id ) {
|
|
|
|
var errmsg = "Template '" + tpl_id + "' of user '" + cdbuser + "' has no signature";
|
|
|
|
// Is this really illegal ?
|
|
|
|
// Maybe we could just return an unsigned layergroupid
|
|
|
|
// in this case...
|
|
|
|
err = new Error(errmsg);
|
|
|
|
err.http_status = 403; // Forbidden, we refuse to respond to this
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
signedMaps.signMap(signer, map_id, crt_id, this);
|
|
|
|
},
|
|
|
|
function prepareResponse(err) {
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('signMap');
|
2013-12-18 00:35:12 +08:00
|
|
|
if ( err ) throw err;
|
|
|
|
//console.log("Response from createLayergroup: "); console.dir(response);
|
|
|
|
// Add the signature part to the token!
|
2014-02-10 22:30:35 +08:00
|
|
|
var tplhash = templateMaps.fingerPrint(template).substring(0,8);
|
2014-02-15 15:06:41 +08:00
|
|
|
if ( req.profiler ) req.profiler.done('fingerPrint');
|
2014-02-10 22:30:35 +08:00
|
|
|
response.layergroupid = cdbuser + '@' + tplhash + '@' + response.layergroupid;
|
2013-12-18 00:35:12 +08:00
|
|
|
return response;
|
|
|
|
},
|
2014-01-29 20:11:37 +08:00
|
|
|
callback
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-02-15 15:06:41 +08:00
|
|
|
function finish_instanciation(err, response, res, req) {
|
|
|
|
if ( req.profiler ) {
|
2014-08-04 18:53:15 +08:00
|
|
|
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
2014-02-15 15:06:41 +08:00
|
|
|
}
|
2014-01-29 20:11:37 +08:00
|
|
|
if (err) {
|
|
|
|
var statusCode = 400;
|
|
|
|
response = { error: ''+err };
|
|
|
|
if ( ! _.isUndefined(err.http_status) ) {
|
|
|
|
statusCode = err.http_status;
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
2014-02-04 21:58:21 +08:00
|
|
|
if(debug) {
|
|
|
|
response.stack = err.stack;
|
|
|
|
}
|
2014-02-10 18:11:35 +08:00
|
|
|
ws.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err);
|
2014-01-29 20:11:37 +08:00
|
|
|
} else {
|
2014-02-19 17:08:25 +08:00
|
|
|
ws.sendResponse(res, [response, 200]);
|
2014-01-29 20:11:37 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ws.post(template_baseurl + '/:template_id', function(req, res) {
|
2014-02-15 15:23:43 +08:00
|
|
|
if ( req.profiler && req.profiler.statsd_client) {
|
|
|
|
req.profiler.start('windshaft-cartodb.instance_template_post');
|
|
|
|
}
|
2014-01-29 20:11:37 +08:00
|
|
|
Step(
|
|
|
|
function() {
|
|
|
|
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') {
|
|
|
|
throw new Error('template POST data must be of type application/json, it is instead ');
|
|
|
|
}
|
2014-01-29 21:30:27 +08:00
|
|
|
instanciateTemplate(req, res, req.body, this);
|
2014-01-29 20:11:37 +08:00
|
|
|
}, function(err, response) {
|
2014-02-15 15:06:41 +08:00
|
|
|
finish_instanciation(err, response, res, req);
|
2014-01-29 20:11:37 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* jsonp endpoint, allows to instanciate a template with a json call.
|
|
|
|
* callback query argument is mandartoy
|
|
|
|
*/
|
|
|
|
ws.get(template_baseurl + '/:template_id/jsonp', function(req, res) {
|
2014-02-15 15:23:43 +08:00
|
|
|
if ( req.profiler && req.profiler.statsd_client) {
|
|
|
|
req.profiler.start('windshaft-cartodb.instance_template_get');
|
|
|
|
}
|
2014-01-29 20:11:37 +08:00
|
|
|
Step(
|
|
|
|
function() {
|
|
|
|
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
|
|
|
throw new Error('callback parameter should be present and be a function name');
|
|
|
|
}
|
|
|
|
var config = {};
|
|
|
|
if(req.query.config) {
|
|
|
|
try {
|
|
|
|
config = JSON.parse(req.query.config);
|
|
|
|
} catch(e) {
|
|
|
|
throw new Error('badformed config parameter, should be a valid JSON');
|
|
|
|
}
|
|
|
|
}
|
2014-01-29 21:30:27 +08:00
|
|
|
instanciateTemplate(req, res, config, this);
|
2014-01-29 20:11:37 +08:00
|
|
|
}, function(err, response) {
|
2014-02-15 15:06:41 +08:00
|
|
|
finish_instanciation(err, response, res, req);
|
2013-12-18 00:35:12 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ---- Template maps interface ends @}
|
|
|
|
|
2014-11-05 22:06:01 +08:00
|
|
|
var healthCheck = new HealthCheck(cartoData, serverOptions.queryTablesApi, Windshaft.tilelive);
|
|
|
|
ws.get('/health', function(req, res) {
|
|
|
|
var healthConfig = global.environment.health || {};
|
|
|
|
|
|
|
|
if (!!healthConfig.enabled) {
|
|
|
|
var startTime = Date.now();
|
|
|
|
healthCheck.check(healthConfig, function(err, result) {
|
|
|
|
var ok = !err;
|
|
|
|
var response = {
|
|
|
|
enabled: true,
|
|
|
|
ok: ok,
|
|
|
|
elapsed: Date.now() - startTime,
|
|
|
|
result: result
|
|
|
|
};
|
|
|
|
if (err) {
|
|
|
|
response.err = err.message;
|
|
|
|
}
|
|
|
|
res.send(response, ok ? 200 : 503);
|
|
|
|
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res.send({enabled: false, ok: true}, 200);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2011-10-13 22:20:29 +08:00
|
|
|
return ws;
|
2014-09-24 17:42:53 +08:00
|
|
|
};
|
2011-10-13 19:17:00 +08:00
|
|
|
|
|
|
|
module.exports = CartodbWindshaft;
|