diff --git a/.jshintrc b/.jshintrc index f03eef65..fa18ac74 100644 --- a/.jshintrc +++ b/.jshintrc @@ -7,8 +7,8 @@ // // Enforcing // "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) // "camelcase" : false, // true: Identifiers must be in camelCase -// "curly" : true, // true: Require {} for every new block or scope -// "eqeqeq" : true, // true: Require triple equals (===) for comparison + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` @@ -31,7 +31,7 @@ // "maxparams" : false, // {int} Max number of formal params allowed per function // "maxdepth" : false, // {int} Max depth of nested blocks (within functions) // "maxstatements" : false, // {int} Max number statements per function - "maxcomplexity" : 8, // {int} Max cyclomatic complexity per function + "maxcomplexity" : 6, // {int} Max cyclomatic complexity per function "maxlen" : 120, // {int} Max number of characters per line // // // Relaxing diff --git a/Makefile b/Makefile index b572bb2b..3f6ebf43 100644 --- a/Makefile +++ b/Makefile @@ -25,13 +25,9 @@ test: config/environments/test.js test/unit/cartodb/cache/model/*.js \ test/integration/*.js \ test/acceptance/*.js \ - test/acceptance/cache/*.js - -test-ported: config/environments/test.js - @echo "***tests ported***" - @$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \ - test/unit/cartodb/ported/*.js \ - test/acceptance/ported/*.js + test/acceptance/cache/*.js \ + test/acceptance/ported/*.js \ + test/unit/cartodb/ported/*.js test-unit: config/environments/test.js @echo "***tests***" @@ -54,7 +50,7 @@ jshint: @echo "***jshint***" @./node_modules/.bin/jshint lib/ test/ app.js -test-all: jshint test test-ported +test-all: jshint test coverage: @RUNTESTFLAGS=--with-coverage make test diff --git a/NEWS.md b/NEWS.md index b7a68599..8d7966aa 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,64 @@ # Changelog +## 2.12.1 + +Released 2015-mm-dd + + + +## 2.12.0 + +Released 2015-08-27 + +Announcements: + - Upgrades windshaft to [0.51.0](https://github.com/CartoDB/Windshaft/releases/tag/0.51.0) + +New features: + - Make http and https globalAgent options configurable + * If config is not provided it configures them with default values + + +## 2.11.0 + +Released 2015-08-26 + +Announcements: + - Upgrades windshaft to [0.50.0](https://github.com/CartoDB/Windshaft/releases/tag/0.50.0) + + +## 2.10.0 + +Released 2015-08-18 + +New features: + - Exposes metatile cache configuration for tilelive-mapnik, see configuration sample files for more information. + +Announcements: + - Upgrades windshaft to [0.49.0](https://github.com/CartoDB/Windshaft/releases/tag/0.49.0) + + +## 2.9.0 + +Released 2015-08-06 + +New features: + - Send memory usage stats + + +## 2.8.0 + +Released 2015-07-15 + +Announcements: + - Upgrades windshaft to [0.48.0](https://github.com/CartoDB/Windshaft/releases/tag/0.48.0) + + ## 2.7.2 -Released 2015-mm-dd +Released 2015-07-14 + +Enhancements: + - Replaces `CDB_QueryTables` with `CDB_QueryTablesText` to avoid issues with long schema+table names ## 2.7.1 diff --git a/app.js b/app.js index 1b71d0dc..018893ad 100755 --- a/app.js +++ b/app.js @@ -1,6 +1,10 @@ +var http = require('http'); +var https = require('https'); var path = require('path'); var fs = require('fs'); +var _ = require('underscore'); + var ENVIRONMENT; if ( process.argv[2] ) { ENVIRONMENT = process.argv[2]; @@ -38,6 +42,17 @@ if (global.environment.uv_threadpool_size) { process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size; } +// set global HTTP and HTTPS agent default configurations +// ref https://nodejs.org/api/http.html#http_new_agent_options +var agentOptions = _.defaults(global.environment.httpAgent || {}, { + keepAlive: false, + keepAliveMsecs: 1000, + maxSockets: Infinity, + maxFreeSockets: 256 +}); +http.globalAgent = new http.Agent(agentOptions); +https.globalAgent = new https.Agent(agentOptions); + if ( global.environment.log_filename ) { var logdir = path.dirname(global.environment.log_filename); // See cwd inlog4js.configure call below @@ -79,12 +94,19 @@ server.listen(serverOptions.bind.port, serverOptions.bind.host); var version = require("./package").version; server.on('listening', function() { - console.log( - "Windshaft tileserver %s started on %s:%s (%s)", - version, serverOptions.bind.host, serverOptions.bind.port, ENVIRONMENT - ); + console.log( + "Windshaft tileserver %s started on %s:%s PID=%d (%s)", + version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT + ); }); +setInterval(function() { + var memoryUsage = process.memoryUsage(); + Object.keys(memoryUsage).forEach(function(k) { + global.statsClient.gauge('windshaft.memory.' + k, memoryUsage[k]); + }); +}, 5000); + process.on('SIGHUP', function() { global.log4js.clearAndShutdownAppenders(function() { global.log4js.configure(log4js_config); diff --git a/config/environments/development.js.example b/config/environments/development.js.example index 3fcf78f9..bf98ca23 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -2,6 +2,9 @@ var config = { environment: 'development' ,port: 8181 ,host: '127.0.0.1' + // Size of the threadpool which can be used to run user code and get notified in the loop thread + // Its default size is 4, but it can be changed at startup time (the absolute maximum is 128). + // See http://docs.libuv.org/en/latest/threadpool.html ,uv_threadpool_size: undefined // Regular expression pattern to extract username // from hostname. Must have a single grabbing block. @@ -86,8 +89,10 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mapnik: { - // The size of the pool of internal mapnik renderers - // Check the configuration of uv_threadpool_size to use suitable value + // The size of the pool of internal mapnik backend + // This pool size is per mapnik renderer created in Windshaft's RendererFactory + // See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js + // Important: check the configuration of uv_threadpool_size to use suitable value poolSize: 8, // Metatile is the number of tiles-per-side that are going @@ -96,6 +101,17 @@ var config = { // wasted time. metatile: 2, + // tilelive-mapnik uses an internal cache to store tiles/grids + // generated when using metatile. This options allow to tune + // the behaviour for that internal cache. + metatileCache: { + // Time an object must stay in the cache until is removed + ttl: 0, + // Whether an object must be removed after the first hit + // Usually you want to use `true` here when ttl>0. + deleteOnHit: false + }, + // Override metatile behaviour depending on the format formatMetatile: { png: 2, @@ -146,6 +162,16 @@ var config = { type: 'fs', // 'fs' and 'url' supported src: __dirname + '/../../assets/default-placeholder.png' } + }, + torque: { + dbPoolParams: { + // maximum number of resources to create at any given time + size: 16, + // max milliseconds a resource can go unused before it should be destroyed + idleTimeout: 3000, + // frequency to check for idle resources + reapInterval: 1000 + } } } ,millstone: { @@ -180,6 +206,13 @@ var config = { unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161 noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading } + // For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options + ,httpAgent: { + keepAlive: true, + keepAliveMsecs: 1000, + maxSockets: 25, + maxFreeSockets: 256 + } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/production.js.example b/config/environments/production.js.example index c0a63be9..386f4433 100644 --- a/config/environments/production.js.example +++ b/config/environments/production.js.example @@ -2,6 +2,9 @@ var config = { environment: 'production' ,port: 8181 ,host: '127.0.0.1' + // Size of the threadpool which can be used to run user code and get notified in the loop thread + // Its default size is 4, but it can be changed at startup time (the absolute maximum is 128). + // See http://docs.libuv.org/en/latest/threadpool.html ,uv_threadpool_size: undefined // Regular expression pattern to extract username // from hostname. Must have a single grabbing block. @@ -80,8 +83,10 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mapnik: { - // The size of the pool of internal mapnik renderers - // Check the configuration of uv_threadpool_size to use suitable value + // The size of the pool of internal mapnik backend + // This pool size is per mapnik renderer created in Windshaft's RendererFactory + // See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js + // Important: check the configuration of uv_threadpool_size to use suitable value poolSize: 8, // Metatile is the number of tiles-per-side that are going @@ -90,6 +95,17 @@ var config = { // wasted time. metatile: 2, + // tilelive-mapnik uses an internal cache to store tiles/grids + // generated when using metatile. This options allow to tune + // the behaviour for that internal cache. + metatileCache: { + // Time an object must stay in the cache until is removed + ttl: 0, + // Whether an object must be removed after the first hit + // Usually you want to use `true` here when ttl>0. + deleteOnHit: false + }, + // Override metatile behaviour depending on the format formatMetatile: { png: 2, @@ -140,6 +156,16 @@ var config = { type: 'fs', // 'fs' and 'url' supported src: __dirname + '/../../assets/default-placeholder.png' } + }, + torque: { + dbPoolParams: { + // maximum number of resources to create at any given time + size: 16, + // max milliseconds a resource can go unused before it should be destroyed + idleTimeout: 3000, + // frequency to check for idle resources + reapInterval: 1000 + } } } ,millstone: { @@ -174,6 +200,13 @@ var config = { unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161 noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading } + // For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options + ,httpAgent: { + keepAlive: true, + keepAliveMsecs: 1000, + maxSockets: 25, + maxFreeSockets: 256 + } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/staging.js.example b/config/environments/staging.js.example index 69fa635b..f692d2ba 100644 --- a/config/environments/staging.js.example +++ b/config/environments/staging.js.example @@ -2,6 +2,9 @@ var config = { environment: 'production' ,port: 8181 ,host: '127.0.0.1' + // Size of the threadpool which can be used to run user code and get notified in the loop thread + // Its default size is 4, but it can be changed at startup time (the absolute maximum is 128). + // See http://docs.libuv.org/en/latest/threadpool.html ,uv_threadpool_size: undefined // Regular expression pattern to extract username // from hostname. Must have a single grabbing block. @@ -80,8 +83,10 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mapnik: { - // The size of the pool of internal mapnik renderers - // Check the configuration of uv_threadpool_size to use suitable value + // The size of the pool of internal mapnik backend + // This pool size is per mapnik renderer created in Windshaft's RendererFactory + // See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js + // Important: check the configuration of uv_threadpool_size to use suitable value poolSize: 8, // Metatile is the number of tiles-per-side that are going @@ -90,6 +95,17 @@ var config = { // wasted time. metatile: 2, + // tilelive-mapnik uses an internal cache to store tiles/grids + // generated when using metatile. This options allow to tune + // the behaviour for that internal cache. + metatileCache: { + // Time an object must stay in the cache until is removed + ttl: 0, + // Whether an object must be removed after the first hit + // Usually you want to use `true` here when ttl>0. + deleteOnHit: false + }, + // Override metatile behaviour depending on the format formatMetatile: { png: 2, @@ -140,6 +156,16 @@ var config = { type: 'fs', // 'fs' and 'url' supported src: __dirname + '/../../assets/default-placeholder.png' } + }, + torque: { + dbPoolParams: { + // maximum number of resources to create at any given time + size: 16, + // max milliseconds a resource can go unused before it should be destroyed + idleTimeout: 3000, + // frequency to check for idle resources + reapInterval: 1000 + } } } ,millstone: { @@ -174,6 +200,13 @@ var config = { unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161 noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading } + // For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options + ,httpAgent: { + keepAlive: true, + keepAliveMsecs: 1000, + maxSockets: 25, + maxFreeSockets: 256 + } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/test.js.example b/config/environments/test.js.example index 5597509a..13132e9a 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -2,6 +2,9 @@ var config = { environment: 'test' ,port: 8888 ,host: '127.0.0.1' + // Size of the threadpool which can be used to run user code and get notified in the loop thread + // Its default size is 4, but it can be changed at startup time (the absolute maximum is 128). + // See http://docs.libuv.org/en/latest/threadpool.html ,uv_threadpool_size: undefined // Regular expression pattern to extract username // from hostname. Must have a single grabbing block. @@ -80,8 +83,10 @@ var config = { cache_ttl: 60000, statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status mapnik: { - // The size of the pool of internal mapnik renderers - // Check the configuration of uv_threadpool_size to use suitable value + // The size of the pool of internal mapnik backend + // This pool size is per mapnik renderer created in Windshaft's RendererFactory + // See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js + // Important: check the configuration of uv_threadpool_size to use suitable value poolSize: 8, // Metatile is the number of tiles-per-side that are going @@ -90,6 +95,17 @@ var config = { // wasted time. metatile: 2, + // tilelive-mapnik uses an internal cache to store tiles/grids + // generated when using metatile. This options allow to tune + // the behaviour for that internal cache. + metatileCache: { + // Time an object must stay in the cache until is removed + ttl: 0, + // Whether an object must be removed after the first hit + // Usually you want to use `true` here when ttl>0. + deleteOnHit: false + }, + // Override metatile behaviour depending on the format formatMetatile: { png: 2, @@ -142,6 +158,16 @@ var config = { type: 'fs', // 'fs' and 'url' supported src: __dirname + '/../../assets/default-placeholder.png' } + }, + torque: { + dbPoolParams: { + // maximum number of resources to create at any given time + size: 16, + // max milliseconds a resource can go unused before it should be destroyed + idleTimeout: 3000, + // frequency to check for idle resources + reapInterval: 1000 + } } } ,millstone: { @@ -176,6 +202,13 @@ var config = { unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161 noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading } + // For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options + ,httpAgent: { + keepAlive: true, + keepAliveMsecs: 1000, + maxSockets: 25, + maxFreeSockets: 256 + } ,varnish: { host: '', port: null, // the por for the telnet interface where varnish is listening to diff --git a/lib/cartodb/api/auth_api.js b/lib/cartodb/api/auth_api.js new file mode 100644 index 00000000..68533fe9 --- /dev/null +++ b/lib/cartodb/api/auth_api.js @@ -0,0 +1,141 @@ +var assert = require('assert'); +var step = require('step'); + +/** + * + * @param {PgConnection} pgConnection + * @param metadataBackend + * @param {MapStore} mapStore + * @param {TemplateMaps} templateMaps + * @constructor + * @type {AuthApi} + */ +function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) { + this.pgConnection = pgConnection; + this.metadataBackend = metadataBackend; + this.mapStore = mapStore; + this.templateMaps = templateMaps; +} + +module.exports = AuthApi; + +// Check if a request is authorized by a signer +// +// @param req express request object +// @param callback function(err, signed_by) signed_by will be +// null if the request is not signed by anyone +// or will be a string cartodb username otherwise. +// +AuthApi.prototype.authorizedBySigner = function(req, callback) { + if ( ! req.params.token || ! req.params.signer ) { + return callback(null, false); // no signer requested + } + + var self = this; + + var layergroup_id = req.params.token; + var auth_token = req.params.auth_token; + + this.mapStore.load(layergroup_id, function(err, mapConfig) { + if (err) { + return callback(err); + } + + var authorized = self.templateMaps.isAuthorized(mapConfig.obj().template, auth_token); + + return callback(null, authorized); + }); +}; + +// Check if a request is authorized by api_key +// +// @param user +// @param req express request object +// @param callback function(err, authorized) +// NOTE: authorized is expected to be 0 or 1 (integer) +// +AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) { + var givenKey = req.query.api_key || req.query.map_key; + if ( ! givenKey && req.body ) { + // check also in request body + givenKey = req.body.api_key || req.body.map_key; + } + if ( ! givenKey ) { + return callback(null, 0); // no api key, no authorization... + } + + var self = this; + + step( + function () { + self.metadataBackend.getUserMapKey(user, this); + }, + function checkApiKey(err, val){ + assert.ifError(err); + return val && givenKey === val; + }, + function finish(err, authorized) { + callback(err, authorized); + } + ); +}; + +/** + * Check access authorization + * + * @param req - standard req object. Importantly contains table and host information + * @param callback function(err, allowed) is access allowed not? + */ +AuthApi.prototype.authorize = function(req, callback) { + var self = this; + var user = req.context.user; + + step( + function () { + self.authorizedByAPIKey(user, req, this); + }, + function checkApiKey(err, authorized){ + if (req.profiler) { + req.profiler.done('authorizedByAPIKey'); + } + assert.ifError(err); + + // if not authorized by api_key, continue + if (!authorized) { + // not authorized by api_key, check if authorized by signer + return self.authorizedBySigner(req, this); + } + + // authorized by api key, login as the given username and stop + self.pgConnection.setDBAuth(user, req.params, function(err) { + callback(err, true); // authorized (or error) + }); + }, + function checkSignAuthorized(err, authorized) { + if (err) { + return callback(err); + } + + if ( ! authorized ) { + // request not authorized by signer. + + // if no signer name was given, let dbparams and + // PostgreSQL do the rest. + // + if ( ! req.params.signer ) { + return callback(null, true); // authorized so far + } + + // if signer name was given, return no authorization + return callback(null, false); + } + + self.pgConnection.setDBAuth(user, req.params, function(err) { + if (req.profiler) { + req.profiler.done('setDBAuth'); + } + callback(err, true); // authorized (or error) + }); + } + ); +}; diff --git a/lib/cartodb/api/query_tables_api.js b/lib/cartodb/api/query_tables_api.js index fc0e2932..f7667bfe 100644 --- a/lib/cartodb/api/query_tables_api.js +++ b/lib/cartodb/api/query_tables_api.js @@ -14,7 +14,7 @@ module.exports = QueryTablesApi; QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) { - var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)'; + var query = 'SELECT CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$)'; this.pgQueryRunner.run(username, query, handleAffectedTablesInQueryRows, callback); }; @@ -25,9 +25,9 @@ function handleAffectedTablesInQueryRows(err, rows, callback) { callback(new Error('could not fetch source tables: ' + msg)); return; } - var qtables = rows[0].cdb_querytables; - var tableNames = qtables.split(/^\{(.*)\}$/)[1]; - tableNames = tableNames ? tableNames.split(',') : []; + + // This is an Array, so no need to split into parts + var tableNames = rows[0].cdb_querytablestext; callback(null, tableNames); } @@ -35,7 +35,7 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam var query = [ 'WITH querytables AS (', - 'SELECT * FROM CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$) as tablenames', + 'SELECT * FROM CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$) as tablenames', ')', 'SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max', 'FROM CDB_TableMetadata m', @@ -48,14 +48,14 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) { if (err || rows.length === 0) { var msg = err.message ? err.message : err; - callback(new Error('could not fetch affected tables and last updated time: ' + msg)); + callback(new Error('could not fetch affected tables or last updated time: ' + msg)); return; } var result = rows[0]; - var tableNames = result.tablenames.split(/^\{(.*)\}$/)[1]; - tableNames = tableNames ? tableNames.split(',') : []; + // This is an Array, so no need to split into parts + var tableNames = result.tablenames; var lastUpdatedTime = result.max || 0; @@ -65,6 +65,35 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) { }); } +QueryTablesApi.prototype.getLastUpdatedTime = function (username, tableNames, callback) { + if (!Array.isArray(tableNames) || tableNames.length === 0) { + return callback(null, 0); + } + + var query = [ + 'SELECT EXTRACT(EPOCH FROM max(updated_at)) as max', + 'FROM CDB_TableMetadata m WHERE m.tabname = any (ARRAY[', + tableNames.map(function(t) { return "'" + t + "'::regclass"; }).join(','), + '])' + ].join(' '); + + this.pgQueryRunner.run(username, query, handleLastUpdatedTimeRows, callback); +}; + +function handleLastUpdatedTimeRows(err, rows, callback) { + if (err) { + var msg = err.message ? err.message : err; + return callback(new Error('could not fetch affected tables or last updated time: ' + msg)); + } + // when the table has not updated_at means it hasn't been changed so a default last_updated is set + var lastUpdated = 0; + if (rows.length !== 0) { + lastUpdated = rows[0].max || 0; + } + + return callback(null, lastUpdated*1000); +} + function prepareSql(sql) { return sql .replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)') diff --git a/lib/cartodb/backends/pg_connection.js b/lib/cartodb/backends/pg_connection.js index 7e4aad15..2ca56f5b 100644 --- a/lib/cartodb/backends/pg_connection.js +++ b/lib/cartodb/backends/pg_connection.js @@ -1,3 +1,4 @@ +var assert = require('assert'); var step = require('step'); var _ = require('underscore'); @@ -29,19 +30,21 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) { self.metadataBackend.getUserId(username, this); }, function(err, user_id) { - if (err) throw err; + assert.ifError(err); user_params.user_id = user_id; var dbuser = _.template(auth_user, user_params); _.extend(params, {dbuser:dbuser}); // skip looking up user_password if postgres_auth_pass // doesn't contain the "user_password" label - if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) return null; + if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) { + return null; + } self.metadataBackend.getUserDBPass(username, this); }, function(err, user_password) { - if (err) throw err; + assert.ifError(err); user_params.user_password = user_password; if ( auth_pass ) { var dbpass = _.template(auth_pass, user_params); @@ -81,12 +84,14 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) { self.metadataBackend.getUserDBConnectionParams(dbowner, this); }, function extendParams(err, dbParams){ - if (err) throw err; + assert.ifError(err); // we don't want null values or overwrite a non public user - if (params.dbuser != 'publicuser' || !dbParams.dbuser) { + if (params.dbuser !== 'publicuser' || !dbParams.dbuser) { delete dbParams.dbuser; } - if ( dbParams ) _.extend(params, dbParams); + if ( dbParams ) { + _.extend(params, dbParams); + } return null; }, function finish(err) { diff --git a/lib/cartodb/backends/pg_query_runner.js b/lib/cartodb/backends/pg_query_runner.js index ed6c88db..0ba40c3e 100644 --- a/lib/cartodb/backends/pg_query_runner.js +++ b/lib/cartodb/backends/pg_query_runner.js @@ -1,3 +1,4 @@ +var assert = require('assert'); var PSQL = require('cartodb-psql'); var step = require('step'); @@ -18,15 +19,11 @@ PgQueryRunner.prototype.run = function(username, query, queryHandler, callback) self.pgConnection.setDBAuth(username, params, this); }, function setConn(err) { - if (err) { - throw err; - } + assert.ifError(err); self.pgConnection.setDBConn(username, params, this); }, function executeQuery(err) { - if (err) { - throw err; - } + assert.ifError(err); var psql = new PSQL({ user: params.dbuser, pass: params.dbpass, diff --git a/lib/cartodb/backends/template_maps.js b/lib/cartodb/backends/template_maps.js index d026fa7e..b550cb53 100644 --- a/lib/cartodb/backends/template_maps.js +++ b/lib/cartodb/backends/template_maps.js @@ -1,3 +1,4 @@ +var assert = require('assert'); var crypto = require('crypto'); var step = require('step'); var _ = require('underscore'); @@ -21,7 +22,9 @@ var util = require('util'); // // function TemplateMaps(redis_pool, opts) { - if (!(this instanceof TemplateMaps)) return new TemplateMaps(); + if (!(this instanceof TemplateMaps)) { + return new TemplateMaps(); + } EventEmitter.call(this); @@ -76,13 +79,15 @@ o._redisCmd = function(redisFunc, redisArgs, callback) { that.redis_pool.acquire(db, this); }, function executeQuery(err, data) { - if ( err ) throw err; + assert.ifError(err); redisClient = data; redisArgs.push(this); redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs); }, function releaseRedisClient(err, data) { - if ( ! _.isUndefined(redisClient) ) that.redis_pool.release(db, redisClient); + if ( ! _.isUndefined(redisClient) ) { + that.redis_pool.release(db, redisClient); + } callback(err, data); } ); @@ -92,7 +97,7 @@ var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i; var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i; // jshint maxcomplexity:15 o._checkInvalidTemplate = function(template) { - if ( template.version != '0.0.1' ) { + if ( template.version !== '0.0.1' ) { return new Error("Unsupported template version " + template.version); } var tplname = template.name; @@ -131,10 +136,12 @@ o._checkInvalidTemplate = function(template) { case 'open': break; case 'token': - if ( ! _.isArray(auth.valid_tokens) ) + if ( ! _.isArray(auth.valid_tokens) ) { return new Error("Invalid 'token' authentication: missing valid_tokens"); - if ( ! auth.valid_tokens.length ) + } + if ( ! auth.valid_tokens.length ) { return new Error("Invalid 'token' authentication: no valid_tokens"); + } break; default: return new Error("Unsupported authentication method: " + auth.method); @@ -214,9 +221,7 @@ o.addTemplate = function(owner, template, callback) { self._redisCmd('HLEN', [ userTemplatesKey ], this); }, function installTemplateIfDoesNotExist(err, numberOfTemplates) { - if ( err ) { - throw err; - } + assert.ifError(err); if ( limit && numberOfTemplates >= limit ) { throw new Error("User '" + owner + "' reached limit on number of templates " + "("+ numberOfTemplates + "/" + limit + ")"); @@ -224,9 +229,7 @@ o.addTemplate = function(owner, template, callback) { self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this); }, function validateInstallation(err, wasSet) { - if ( err ) { - throw err; - } + assert.ifError(err); if ( ! wasSet ) { throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists"); } @@ -259,9 +262,7 @@ o.delTemplate = function(owner, tpl_id, callback) { self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this); }, function handleDeletion(err, deleted) { - if (err) { - throw err; - } + assert.ifError(err); if (!deleted) { throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist"); } @@ -306,7 +307,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) { var templateName = template.name; - if ( tpl_id != templateName ) { + if ( tpl_id !== templateName ) { return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')")); } @@ -317,18 +318,14 @@ o.updTemplate = function(owner, tpl_id, template, callback) { self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this); }, function updateTemplate(err, currentTemplate) { - if (err) { - throw err; - } + assert.ifError(err); if (!currentTemplate) { throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist"); } self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this); }, function handleTemplateUpdate(err, didSetNewField) { - if (err) { - throw err; - } + assert.ifError(err); if (didSetNewField) { console.warn('New template created on update operation'); } @@ -372,7 +369,7 @@ o.getTemplate = function(owner, tpl_id, callback) { self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this); }, function parseTemplate(err, tpl_val) { - if ( err ) throw err; + assert.ifError(err); return JSON.parse(tpl_val); }, function finish(err, tpl) { @@ -472,8 +469,12 @@ o.instance = function(template, params) { var layergroup = JSON.parse(JSON.stringify(template.layergroup)); for (var i=0; i affected tables cache + this.cache = new LruCache({ max: 2000 }); +} + +module.exports = LayergroupAffectedTables; + +LayergroupAffectedTables.prototype.hasAffectedTables = function(dbName, layergroupId) { + return this.cache.has(createKey(dbName, layergroupId)); +}; + +LayergroupAffectedTables.prototype.set = function(dbName, layergroupId, affectedTables) { + this.cache.set(createKey(dbName, layergroupId), affectedTables); +}; + +LayergroupAffectedTables.prototype.get = function(dbName, layergroupId) { + return this.cache.get(createKey(dbName, layergroupId)); +}; + +function createKey(dbName, layergroupId) { + return dbName + ':' + layergroupId; +} diff --git a/lib/cartodb/cache/model/database_tables_entry.js b/lib/cartodb/cache/model/database_tables_entry.js new file mode 100644 index 00000000..4d269137 --- /dev/null +++ b/lib/cartodb/cache/model/database_tables_entry.js @@ -0,0 +1,24 @@ +var crypto = require('crypto'); + +function DatabaseTables(dbName, tableNames) { + this.namespace = 't'; + this.dbName = dbName; + this.tableNames = tableNames; +} + +module.exports = DatabaseTables; + + +DatabaseTables.prototype.key = function() { + return this.tableNames.map(function(tableName) { + return this.namespace + ':' + shortHashKey(this.dbName + ':' + tableName); + }.bind(this)); +}; + +DatabaseTables.prototype.getCacheChannel = function() { + return this.dbName + ':' + this.tableNames.join(','); +}; + +function shortHashKey(target) { + return crypto.createHash('sha256').update(target).digest('base64').substring(0,6); +} diff --git a/lib/cartodb/cache/named_map_provider_cache.js b/lib/cartodb/cache/named_map_provider_cache.js new file mode 100644 index 00000000..e4c3dccc --- /dev/null +++ b/lib/cartodb/cache/named_map_provider_cache.js @@ -0,0 +1,62 @@ +var _ = require('underscore'); +var dot = require('dot'); +var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); +var templateName = require('../backends/template_maps').templateName; + +var LruCache = require("lru-cache"); + +function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, queryTablesApi) { + this.templateMaps = templateMaps; + this.pgConnection = pgConnection; + this.userLimitsApi = userLimitsApi; + this.queryTablesApi = queryTablesApi; + + this.providerCache = new LruCache({ max: 2000 }); +} + +module.exports = NamedMapProviderCache; + +NamedMapProviderCache.prototype.get = function(user, templateId, config, authToken, params) { + var namedMapKey = createNamedMapKey(user, templateId); + var namedMapProviders = this.providerCache.get(namedMapKey) || {}; + + var providerKey = createProviderKey(config, authToken, params); + if (!namedMapProviders.hasOwnProperty(providerKey)) { + namedMapProviders[providerKey] = new NamedMapMapConfigProvider( + this.templateMaps, + this.pgConnection, + this.userLimitsApi, + this.queryTablesApi, + user, + templateId, + config, + authToken, + params + ); + this.providerCache.set(namedMapKey, namedMapProviders); + } + + return namedMapProviders[providerKey]; +}; + +NamedMapProviderCache.prototype.invalidate = function(user, templateId) { + this.providerCache.del(createNamedMapKey(user, templateId)); +}; + +function createNamedMapKey(user, templateId) { + return user + ':' + templateName(templateId); +} + +var providerKey = '{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}'; +var providerKeyTpl = dot.template(providerKey); + +function createProviderKey(config, authToken, params) { + var tplValues = _.defaults({}, params, { + authToken: authToken || '', + configHash: NamedMapMapConfigProvider.configHash(config), + layer: '', + format: '', + scale_factor: 1 + }); + return providerKeyTpl(tplValues); +} diff --git a/lib/cartodb/cache/surrogate_keys_cache.js b/lib/cartodb/cache/surrogate_keys_cache.js index 77147917..237ac906 100644 --- a/lib/cartodb/cache/surrogate_keys_cache.js +++ b/lib/cartodb/cache/surrogate_keys_cache.js @@ -16,9 +16,21 @@ module.exports = SurrogateKeysCache; * @param cacheObject should respond to `key() -> String` method */ SurrogateKeysCache.prototype.tag = function(response, cacheObject) { - response.header('Surrogate-Key', cacheObject.key()); + var newKey = cacheObject.key(); + response.header('Surrogate-Key', appendSurrogateKey( + response.header('Surrogate-Key'), + Array.isArray(newKey) ? cacheObject.key().join(' ') : newKey + )); + }; +function appendSurrogateKey(currentKey, newKey) { + if (!!currentKey) { + newKey = currentKey + ' ' + newKey; + } + return newKey; +} + /** * @param cacheObject should respond to `key() -> String` method * @param {Function} callback diff --git a/lib/cartodb/controllers/layergroup.js b/lib/cartodb/controllers/layergroup.js index b20b0ebc..e0aa6b44 100644 --- a/lib/cartodb/controllers/layergroup.js +++ b/lib/cartodb/controllers/layergroup.js @@ -4,6 +4,7 @@ var step = require('step'); var cors = require('../middleware/cors'); var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider'); +var TablesCacheEntry = require('../cache/model/database_tables_entry'); /** * @param app @@ -11,16 +12,23 @@ var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider' * @param {TileBackend} tileBackend * @param {PreviewBackend} previewBackend * @param {AttributesBackend} attributesBackend - * @param {{UserLimitsApi}} userLimitsApi + * @param {SurrogateKeysCache} surrogateKeysCache + * @param {UserLimitsApi} userLimitsApi + * @param {QueryTablesApi} queryTablesApi + * @param {LayergroupAffectedTables} layergroupAffectedTables * @constructor */ -function LayergroupController(app, mapStore, tileBackend, previewBackend, attributesBackend, userLimitsApi) { +function LayergroupController(app, mapStore, tileBackend, previewBackend, attributesBackend, surrogateKeysCache, + userLimitsApi, queryTablesApi, layergroupAffectedTables) { this.app = app; this.mapStore = mapStore; this.tileBackend = tileBackend; this.previewBackend = previewBackend; this.attributesBackend = attributesBackend; + this.surrogateKeysCache = surrogateKeysCache; this.userLimitsApi = userLimitsApi; + this.queryTablesApi = queryTablesApi; + this.layergroupAffectedTables = layergroupAffectedTables; } module.exports = LayergroupController; @@ -62,7 +70,7 @@ LayergroupController.prototype.attributes = function(req, res) { var statusCode = self.app.findStatusCode(err); self.app.sendError(res, { errors: [errMsg] }, statusCode, 'GET ATTRIBUTES', err); } else { - self.app.sendResponse(res, [tile, 200]); + self.sendResponse(req, res, [tile, 200]); } } ); @@ -87,8 +95,6 @@ LayergroupController.prototype.layer = function(req, res, next) { LayergroupController.prototype.tileOrLayer = function (req, res) { var self = this; - console.log(req.context.user); - step( function mapController$prepareParams() { self.app.req2params(req, this); @@ -149,7 +155,7 @@ LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, t global.statsClient.increment('windshaft.tiles.error'); global.statsClient.increment('windshaft.tiles.' + formatStat + '.error'); } else { - this.app.sendWithHeaders(res, tile, 200, headers); + this.sendResponse(req, res, [tile, headers, 200]); global.statsClient.increment('windshaft.tiles.success'); global.statsClient.increment('windshaft.tiles.' + formatStat + '.success'); } @@ -206,8 +212,95 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo self.app.sendError(res, {errors: ['' + err] }, self.app.findStatusCode(err), 'STATIC_MAP', err); } else { res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format); - self.app.sendResponse(res, [image, 200]); + self.sendResponse(req, res, [image, 200]); } } ); }; + +LayergroupController.prototype.sendResponse = function(req, res, args) { + var self = this; + + res.header('Cache-Control', 'public,max-age=31536000'); + + // Set Last-Modified header + var lastUpdated; + if (req.params.cache_buster) { + // Assuming cache_buster is a timestamp + lastUpdated = new Date(parseInt(req.params.cache_buster)); + } else { + lastUpdated = new Date(); + } + res.header('Last-Modified', lastUpdated.toUTCString()); + + var dbName = req.params.dbname; + step( + function getAffectedTables() { + self.getAffectedTables(req.context.user, dbName, req.params.token, this); + }, + function sendResponse(err, affectedTables) { + req.profiler.done('affectedTables'); + if (err) { + console.log('ERROR generating cache channel: ' + err); + } + if (!!affectedTables) { + var tablesCacheEntry = new TablesCacheEntry(dbName, affectedTables); + res.header('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); + self.surrogateKeysCache.tag(res, tablesCacheEntry); + } + self.app.sendResponse(res, args); + } + ); + +}; + +LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) { + + if (this.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId)) { + return callback(null, this.layergroupAffectedTables.get(dbName, layergroupId)); + } + + var self = this; + step( + function extractSQL() { + step( + function loadFromStore() { + self.mapStore.load(layergroupId, this); + }, + function getSQL(err, mapConfig) { + assert.ifError(err); + + var queries = mapConfig.getLayers() + .map(function(lyr) { + return lyr.options.sql; + }) + .filter(function(sql) { + return !!sql; + }); + + return queries.length ? queries.join(';') : null; + }, + this + ); + }, + function findAffectedTables(err, sql) { + assert.ifError(err); + + if ( ! sql ) { + throw new Error("this request doesn't need an X-Cache-Channel generated"); + } + + self.queryTablesApi.getAffectedTablesInQuery(user, sql, this); // in addCacheChannel + }, + function buildCacheChannel(err, tableNames) { + assert.ifError(err); + + self.layergroupAffectedTables.set(dbName, layergroupId, tableNames); + + return tableNames; + }, + function finish(err, affectedTables) { + callback(err, affectedTables); + } + ); +}; diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index d5b43425..45f17797 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -9,6 +9,7 @@ var MapConfig = windshaft.model.MapConfig; var Datasource = windshaft.model.Datasource; var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); +var TablesCacheEntry = require('../cache/model/database_tables_entry'); var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); @@ -22,11 +23,12 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_laye * @param metadataBackend * @param {QueryTablesApi} queryTablesApi * @param {SurrogateKeysCache} surrogateKeysCache - * @param {{UserLimitsApi}} userLimitsApi + * @param {UserLimitsApi} userLimitsApi + * @param {LayergroupAffectedTables} layergroupAffectedTables * @constructor */ function MapController(app, pgConnection, templateMaps, mapBackend, metadataBackend, queryTablesApi, - surrogateKeysCache, userLimitsApi) { + surrogateKeysCache, userLimitsApi, layergroupAffectedTables) { this.app = app; this.pgConnection = pgConnection; this.templateMaps = templateMaps; @@ -35,6 +37,8 @@ function MapController(app, pgConnection, templateMaps, mapBackend, metadataBack this.queryTablesApi = queryTablesApi; this.surrogateKeysCache = surrogateKeysCache; this.userLimitsApi = userLimitsApi; + this.layergroupAffectedTables = layergroupAffectedTables; + this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); } @@ -147,13 +151,14 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { }, function afterLayergroupCreate(err, layergroup) { assert.ifError(err); - self.afterLayergroupCreate(req, mapConfig, layergroup, this); + self.afterLayergroupCreate(req, res, mapConfig, layergroup, this); }, function finish(err, layergroup) { if (err) { var statusCode = self.app.findStatusCode(err); self.app.sendError(res, { errors: [ err.message ] }, statusCode, 'ANONYMOUS LAYERGROUP', err); } else { + res.header('X-Layergroup-Id', layergroup.layergroupid); self.app.sendResponse(res, [layergroup, 200]); } } @@ -169,6 +174,9 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn var mapConfig; step( + function setupParams(){ + self.app.req2params(req, this); + }, function getTemplateParams() { prepareParamsFn(this); }, @@ -178,6 +186,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn self.templateMaps, self.pgConnection, self.userLimitsApi, + self.queryTablesApi, cdbuser, req.params.template_id, templateParams, @@ -197,7 +206,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn }, function afterLayergroupCreate(err, layergroup) { assert.ifError(err); - self.afterLayergroupCreate(req, mapConfig, layergroup, this); + self.afterLayergroupCreate(req, res, mapConfig, layergroup, this); }, function finishTemplateInstantiation(err, layergroup) { if (err) { @@ -217,7 +226,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn }; -MapController.prototype.afterLayergroupCreate = function(req, mapconfig, layergroup, callback) { +MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, callback) { var self = this; var username = req.context.user; @@ -258,34 +267,47 @@ MapController.prototype.afterLayergroupCreate = function(req, mapconfig, layergr }).join(';'); var dbName = req.params.dbname; - var cacheKey = dbName + ':' + layergroup.layergroupid; + var layergroupId = layergroup.layergroupid; step( - function getAffectedTablesAndLastUpdatedTime() { - self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this); + function checkCachedAffectedTables() { + return self.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId); + }, + function getAffectedTablesAndLastUpdatedTime(err, hasCache) { + assert.ifError(err); + if (hasCache) { + var next = this; + var affectedTables = self.layergroupAffectedTables.get(dbName, layergroupId); + self.queryTablesApi.getLastUpdatedTime(username, affectedTables, function(err, lastUpdatedTime) { + if (err) { + return next(err); + } + return next(null, { affectedTables: affectedTables, lastUpdatedTime: lastUpdatedTime }); + }); + } else { + self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this); + } }, function handleAffectedTablesAndLastUpdatedTime(err, result) { if (req.profiler) { req.profiler.done('queryTablesAndLastUpdated'); } assert.ifError(err); - var cacheChannel = self.app.buildCacheChannel(dbName, result.affectedTables); - self.app.channelCache[cacheKey] = cacheChannel; + self.layergroupAffectedTables.set(dbName, layergroupId, result.affectedTables); // last update for layergroup cache buster layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime; layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString(); - var res = req.res; - if (res) { - if (req.method === 'GET') { - var ttl = global.environment.varnish.layergroupTtl || 86400; - res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); - res.header('Last-Modified', (new Date()).toUTCString()); - res.header('X-Cache-Channel', cacheChannel); + if (req.method === 'GET') { + var tableCacheEntry = new TablesCacheEntry(dbName, result.affectedTables); + var ttl = global.environment.varnish.layergroupTtl || 86400; + res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); + res.header('Last-Modified', (new Date()).toUTCString()); + res.header('X-Cache-Channel', tableCacheEntry.getCacheChannel()); + if (result.affectedTables && result.affectedTables.length > 0) { + self.surrogateKeysCache.tag(res, tableCacheEntry); } - - res.header('X-Layergroup-Id', layergroup.layergroupid); } return null; diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 0e0ce057..8dbc27d1 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -4,18 +4,16 @@ var _ = require('underscore'); var NamedMapsCacheEntry = require('../cache/model/named_maps_entry'); var cors = require('../middleware/cors'); -var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); +var TablesCacheEntry = require('../cache/model/database_tables_entry'); -function NamedMapsController(app, pgConnection, templateMaps, tileBackend, previewBackend, surrogateKeysCache, - tablesExtentApi, userLimitsApi) { +function NamedMapsController(app, namedMapProviderCache, tileBackend, previewBackend, surrogateKeysCache, + tablesExtentApi) { this.app = app; - this.pgConnection = pgConnection; - this.templateMaps = templateMaps; + this.namedMapProviderCache = namedMapProviderCache; this.tileBackend = tileBackend; this.previewBackend = previewBackend; this.surrogateKeysCache = surrogateKeysCache; this.tablesExtentApi = tablesExtentApi; - this.userLimitsApi = userLimitsApi; } module.exports = NamedMapsController; @@ -27,6 +25,46 @@ NamedMapsController.prototype.register = function(app) { ); }; +NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) { + this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName())); + res.header('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png'); + res.header('Cache-Control', 'public,max-age=7200,must-revalidate'); + + var self = this; + + var dbName = req.params.dbname; + step( + function getAffectedTablesAndLastUpdatedTime() { + namedMapProvider.getAffectedTablesAndLastUpdatedTime(this); + }, + function sendResponse(err, result) { + req.profiler.done('affectedTables'); + if (err) { + console.log('ERROR generating cache channel: ' + err); + } + if (!result || !!result.affectedTables) { + // we increase cache control as we can invalidate it + res.header('Cache-Control', 'public,max-age=31536000'); + + var lastModifiedDate; + if (Number.isFinite(result.lastUpdatedTime)) { + lastModifiedDate = new Date(result.lastUpdatedTime); + } else { + lastModifiedDate = new Date(); + } + res.header('Last-Modified', lastModifiedDate.toUTCString()); + + var tablesCacheEntry = new TablesCacheEntry(dbName, result.affectedTables); + res.header('X-Cache-Channel', tablesCacheEntry.getCacheChannel()); + if (result.affectedTables.length > 0) { + self.surrogateKeysCache.tag(res, tablesCacheEntry); + } + } + self.app.sendResponse(res, [resource, 200]); + } + ); +}; + NamedMapsController.prototype.tile = function(req, res) { var self = this; @@ -38,10 +76,7 @@ NamedMapsController.prototype.tile = function(req, res) { self.app.req2params(req, this); }, function getTile() { - namedMapProvider = new NamedMapMapConfigProvider( - self.templateMaps, - self.pgConnection, - self.userLimitsApi, + namedMapProvider = self.namedMapProviderCache.get( cdbUser, req.params.template_id, req.query.config, @@ -60,10 +95,7 @@ NamedMapsController.prototype.tile = function(req, res) { } self.app.sendError(res, err, self.app.findStatusCode(err), 'NAMED_MAP_TILE', err); } else { - self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, namedMapProvider.getTemplateName())); - res.setHeader('Content-Type', headers['Content-Type']); - res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate'); - self.app.sendWithHeaders(res, tile, 200, headers); + self.sendResponse(req, res, tile, headers, namedMapProvider); } } ); @@ -85,10 +117,7 @@ NamedMapsController.prototype.staticMap = function(req, res) { }, function getTemplate(err) { assert.ifError(err); - namedMapProvider = new NamedMapMapConfigProvider( - self.templateMaps, - self.pgConnection, - self.userLimitsApi, + namedMapProvider = self.namedMapProviderCache.get( cdbUser, req.params.template_id, req.query.config, @@ -158,10 +187,7 @@ NamedMapsController.prototype.staticMap = function(req, res) { } self.app.sendError(res, err, self.app.findStatusCode(err), 'STATIC_VIZ_MAP', err); } else { - self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, namedMapProvider.getTemplateName())); - res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format); - res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate'); - self.app.sendResponse(res, [image, 200]); + self.sendResponse(req, res, image, headers, namedMapProvider); } } ); diff --git a/lib/cartodb/controllers/named_maps_admin.js b/lib/cartodb/controllers/named_maps_admin.js index db92f3ae..f2fb1c8a 100644 --- a/lib/cartodb/controllers/named_maps_admin.js +++ b/lib/cartodb/controllers/named_maps_admin.js @@ -5,9 +5,16 @@ var templateName = require('../backends/template_maps').templateName; var cors = require('../middleware/cors'); -function NamedMapsAdminController(app, templateMaps) { +/** + * @param app + * @param {TemplateMaps} templateMaps + * @param {AuthApi} authApi + * @constructor + */ +function NamedMapsAdminController(app, templateMaps, authApi) { this.app = app; this.templateMaps = templateMaps; + this.authApi = authApi; } module.exports = NamedMapsAdminController; @@ -28,7 +35,7 @@ NamedMapsAdminController.prototype.create = function(req, res) { step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function addTemplate(err, authenticated) { assert.ifError(err); @@ -53,7 +60,7 @@ NamedMapsAdminController.prototype.update = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function updateTemplate(err, authenticated) { assert.ifError(err); @@ -84,7 +91,7 @@ NamedMapsAdminController.prototype.retrieve = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function getTemplate(err, authenticated) { assert.ifError(err); @@ -94,7 +101,7 @@ NamedMapsAdminController.prototype.retrieve = function(req, res) { self.templateMaps.getTemplate(cdbuser, tpl_id, this); }, function prepareResponse(err, tpl_val) { - if ( err ) throw err; + assert.ifError(err); if ( ! tpl_val ) { err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'"); err.http_status = 404; @@ -120,7 +127,7 @@ NamedMapsAdminController.prototype.destroy = function(req, res) { var tpl_id; step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function deleteTemplate(err, authenticated) { assert.ifError(err); @@ -130,7 +137,7 @@ NamedMapsAdminController.prototype.destroy = function(req, res) { self.templateMaps.delTemplate(cdbuser, tpl_id, this); }, function prepareResponse(err/*, tpl_val*/){ - if ( err ) throw err; + assert.ifError(err); return { status: 'ok' }; }, finishFn(self.app, res, 'DELETE TEMPLATE', ['', 204]) @@ -147,7 +154,7 @@ NamedMapsAdminController.prototype.list = function(req, res) { step( function checkPerms(){ - self.app.authorizedByAPIKey(cdbuser, req, this); + self.authApi.authorizedByAPIKey(cdbuser, req, this); }, function listTemplates(err, authenticated) { assert.ifError(err); diff --git a/lib/cartodb/controllers/server_info.js b/lib/cartodb/controllers/server_info.js index 49d3f340..a6bc040e 100644 --- a/lib/cartodb/controllers/server_info.js +++ b/lib/cartodb/controllers/server_info.js @@ -15,7 +15,7 @@ var versions = { function ServerInfoController() { this.healthConfig = global.environment.health || {}; - this.healthCheck = new HealthCheck(); + this.healthCheck = new HealthCheck(global.environment.disabled_file); } module.exports = ServerInfoController; @@ -37,13 +37,12 @@ ServerInfoController.prototype.version = function(req, res) { ServerInfoController.prototype.health = function(req, res) { if (!!this.healthConfig.enabled) { var startTime = Date.now(); - this.healthCheck.check(this.healthConfig, function(err, result) { + this.healthCheck.check(function(err) { var ok = !err; var response = { enabled: true, ok: ok, - elapsed: Date.now() - startTime, - result: result + elapsed: Date.now() - startTime }; if (err) { response.err = err.message; diff --git a/lib/cartodb/models/mapconfig/named_map_provider.js b/lib/cartodb/models/mapconfig/named_map_provider.js index 3ee5b18e..f1565c66 100644 --- a/lib/cartodb/models/mapconfig/named_map_provider.js +++ b/lib/cartodb/models/mapconfig/named_map_provider.js @@ -10,20 +10,25 @@ var templateName = require('../../backends/template_maps').templateName; * @constructor * @type {NamedMapMapConfigProvider} */ -function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, owner, templateId, config, authToken, - params) { +function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, queryTablesApi, + owner, templateId, config, authToken, params) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; this.userLimitsApi = userLimitsApi; + this.queryTablesApi = queryTablesApi; this.owner = owner; this.templateName = templateName(templateId); this.config = config; this.authToken = authToken; this.params = params; + this.cacheBuster = Date.now(); + // use template after call to mapConfig this.template = null; + this.affectedTablesAndLastUpdate = null; + // providing this.err = null; this.mapConfig = null; @@ -144,7 +149,7 @@ NamedMapMapConfigProvider.prototype.getKey = function() { }; NamedMapMapConfigProvider.prototype.getCacheBuster = function() { - return 0; + return this.cacheBuster; }; NamedMapMapConfigProvider.prototype.filter = function(key) { @@ -153,7 +158,7 @@ NamedMapMapConfigProvider.prototype.filter = function(key) { }; // Configure bases for cache keys suitable for string interpolation -var baseKey = '{{=it.dbname}}:{{=it.owner}}:{{=it.templateName}}'; +var baseKey = '{{=it.dbname}}:{{=it.owner}}:{{=it.templateName}}'; var rendererKey = baseKey + ':{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}'; var baseKeyTpl = dot.template(baseKey); @@ -179,6 +184,8 @@ function configHash(config) { return crypto.createHash('md5').update(JSON.stringify(config)).digest('hex').substring(0,8); } +module.exports.configHash = configHash; + NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, callback) { var self = this; step( @@ -186,7 +193,7 @@ NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, call self.pgConnection.setDBAuth(cdbuser, params, this); }, function setConn(err) { - if ( err ) throw err; + assert.ifError(err); self.pgConnection.setDBConn(cdbuser, params, this); }, function finish(err) { @@ -198,3 +205,31 @@ NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, call NamedMapMapConfigProvider.prototype.getTemplateName = function() { return this.templateName; }; + +NamedMapMapConfigProvider.prototype.getAffectedTablesAndLastUpdatedTime = function(callback) { + var self = this; + + if (this.affectedTablesAndLastUpdate !== null) { + return callback(null, this.affectedTablesAndLastUpdate); + } + + step( + function getMapConfig() { + self.getMapConfig(this); + }, + function getSql(err, mapConfig) { + assert.ifError(err); + return mapConfig.getLayers().map(function(layer) { + return layer.options.sql; + }).join(';'); + }, + function getAffectedTables(err, sql) { + assert.ifError(err); + self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(self.owner, sql, this); + }, + function finish(err, result) { + self.affectedTablesAndLastUpdate = result; + return callback(err, result); + } + ); +}; diff --git a/lib/cartodb/monitoring/health_check.js b/lib/cartodb/monitoring/health_check.js index fc1765cd..69425c2e 100644 --- a/lib/cartodb/monitoring/health_check.js +++ b/lib/cartodb/monitoring/health_check.js @@ -1,29 +1,20 @@ var fs = require('fs'); var step = require('step'); -function HealthCheck() { +function HealthCheck(disableFile) { + this.disableFile = disableFile; } module.exports = HealthCheck; -HealthCheck.prototype.check = function(config, callback) { +HealthCheck.prototype.check = function(callback) { - var result = { - redis: { - ok: false - }, - mapnik: { - ok: false - }, - tile: { - ok: false - } - }; + var self = this; step( function getManualDisable() { - fs.readFile(global.environment.disabled_file, this); + fs.readFile(self.disableFile, this); }, function handleDisabledFile(err, data) { var next = this; @@ -37,7 +28,7 @@ HealthCheck.prototype.check = function(config, callback) { } }, function handleResult(err) { - callback(err, result); + return callback(err); } ); }; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index ffd71219..e7c74cd0 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -19,6 +19,9 @@ var mapnik = windshaft.mapnik; var TemplateMaps = require('./backends/template_maps.js'); var QueryTablesApi = require('./api/query_tables_api'); var UserLimitsApi = require('./api/user_limits_api'); +var AuthApi = require('./api/auth_api'); +var LayergroupAffectedTablesCache = require('./cache/layergroup_affected_tables'); +var NamedMapProviderCache = require('./cache/named_map_provider_cache'); var PgQueryRunner = require('./backends/pg_query_runner'); var PgConnection = require('./backends/pg_connection'); @@ -73,12 +76,6 @@ module.exports = function(serverOptions) { max_user_templates: global.environment.maxUserTemplates }); - // 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)'; - var surrogateKeysCacheBackends = []; if (serverOptions.varnish_purge_enabled) { @@ -131,7 +128,6 @@ module.exports = function(serverOptions) { pool: redisPool, expire_time: serverOptions.grainstore.default_layergroup_ttl }); - app.mapStore = mapStore; var onTileErrorStrategy; if (global.environment.enabledFeatures.onTileErrorStrategy !== false) { @@ -167,6 +163,16 @@ module.exports = function(serverOptions) { var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend); var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend); + var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache(); + app.layergroupAffectedTablesCache = layergroupAffectedTablesCache; + + var namedMapProviderCache = new NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, queryTablesApi); + ['update', 'delete'].forEach(function(eventType) { + templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache)); + }); + + var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps); + app.findStatusCode = function(err) { var statusCode; if ( err.http_status ) { @@ -195,7 +201,10 @@ module.exports = function(serverOptions) { tileBackend, previewBackend, attributesBackend, - userLimitsApi + surrogateKeysCache, + userLimitsApi, + queryTablesApi, + layergroupAffectedTablesCache ).register(app); new controller.Map( @@ -206,21 +215,20 @@ module.exports = function(serverOptions) { metadataBackend, queryTablesApi, surrogateKeysCache, - userLimitsApi + userLimitsApi, + layergroupAffectedTablesCache ).register(app); new controller.NamedMaps( app, - pgConnection, - templateMaps, + namedMapProviderCache, tileBackend, previewBackend, surrogateKeysCache, - tablesExtentApi, - userLimitsApi + tablesExtentApi ).register(app); - new controller.NamedMapsAdmin(app, templateMaps).register(app); + new controller.NamedMapsAdmin(app, templateMaps, authApi).register(app); new controller.ServerInfo().register(app); @@ -241,100 +249,33 @@ module.exports = function(serverOptions) { } }); - // GET routes for which we don't want to request any caching. - // POST/PUT/DELETE requests are never cached anyway. - var noCacheGETRoutes = [ - '/', - '/version', - // See https://github.com/CartoDB/Windshaft-cartodb/issues/176 - serverOptions.base_url_mapconfig, - serverOptions.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', - template_baseurl, - template_baseurl + '/:template_id', - template_baseurl + '/:template_id/jsonp' - ]; - app.sendResponse = function(res, args) { - var that = this; + var req = res.req; - var statusCode; - 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; + if (global.environment && global.environment.api_hostname) { + res.header('X-Served-By-Host', global.environment.api_hostname); + } + + if (req && req.params && req.params.dbhost) { + res.header('X-Served-By-DB-Host', req.params.dbhost); + } + + if ( req && req.profiler ) { + res.header('X-Tiler-Profiler', req.profiler.toJSONString()); + } + +// res.send(body|status[, headers|status[, status]]) + res.send.apply(res, args); + + if ( req && req.profiler ) { + try { + // May throw due to dns, see + // See http://github.com/CartoDB/Windshaft/issues/166 + req.profiler.sendStats(); + } catch (err) { + console.error("error sending profiling stats: " + err); } } - 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; - } - if ( ! req.params ) { - // service requests (/version, /) - // have no need for an X-Cache-Channel - return false; - } - if ( statusCode != 200 ) { - // We do not want to cache - // unsuccessful responses - return false; - } - if ( _.contains(noCacheGETRoutes, req.route.path) ) { -//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")); - app.addCacheChannel(that, req, this); - }, - function sendResponse(err/*, added*/) { - if ( err ) console.log(err + err.stack); - // When using custom results from tryFetch* methods, - // there is no "req" link in the result object. - // In those cases we don't want to send stats now - // as they will be sent at the real end of request - var req = res.req; - - if (global.environment && global.environment.api_hostname) { - res.header('X-Served-By-Host', global.environment.api_hostname); - } - - if (req && req.params && req.params.dbhost) { - res.header('X-Served-By-DB-Host', req.params.dbhost); - } - - if ( req && req.profiler ) { - res.header('X-Tiler-Profiler', req.profiler.toJSONString()); - } - - res.send.apply(res, args); - - if ( req && req.profiler ) { - try { - // May throw due to dns, see - // See http://github.com/CartoDB/Windshaft/issues/166 - req.profiler.sendStats(); - } catch (err) { - console.error("error sending profiling stats: " + err); - } - } - return null; - }, - function finish(err) { - if ( err ) console.log(err + err.stack); - } - ); - }; - - app.sendWithHeaders = function(res, what, status, headers) { - app.sendResponse(res, [what, headers, status]); }; app.sendError = function(res, err, statusCode, label, tolog) { @@ -453,9 +394,9 @@ module.exports = function(serverOptions) { step( function getPrivacy(){ - app.authorize(req, this); + authApi.authorize(req, this); }, - function gatekeep(err, authorized){ + function validateAuthorization(err, authorized) { if (req.profiler) { req.profiler.done('authorize'); } @@ -472,7 +413,9 @@ module.exports = function(serverOptions) { pgConnection.setDBConn(user, req.params, this); }, function finishSetup(err) { - if ( err ) { callback(err, req); return; } + if ( err ) { + return callback(err, req); + } // Add default database connection parameters // if none given @@ -488,260 +431,6 @@ module.exports = function(serverOptions) { ); }; - // TODO: review lifetime of elements of this cache - // NOTE: by-token indices should only be dropped when - // the corresponding layegroup is dropped, because - // we have no SQL after layer creation. - app.channelCache = {}; - - app.buildCacheChannel = function (dbName, tableNames){ - return dbName + ':' + tableNames.join(','); - }; - - app.generateCacheChannel = function(app, req, callback){ - // Build channelCache key - var dbName = req.params.dbname; - var cacheKey = [ dbName, req.params.token ].join(':'); - - // no token means no tables associated - if (!req.params.token) { - return callback(null, this.buildCacheChannel(dbName, [])); - } - - step( - function checkCached() { - if ( app.channelCache.hasOwnProperty(cacheKey) ) { - return callback(null, app.channelCache[cacheKey]); - } - return null; - }, - function extractSQL(err) { - assert.ifError(err); - - // TODO: cached cache channel for token-based access should - // be constructed at renderer cache creation time - // See http://github.com/CartoDB/Windshaft-cartodb/issues/152 - if ( ! app.mapStore ) { - throw new Error('missing channel cache for token ' + req.params.token); - } - var mapStore = app.mapStore; - - step( - function loadFromStore() { - mapStore.load(req.params.token, this); - }, - function getSQL(err, mapConfig) { - if (req.profiler) { - req.profiler.done('mapStore_load'); - } - assert.ifError(err); - - var queries = mapConfig.getLayers() - .map(function(lyr) { - return lyr.options.sql; - }) - .filter(function(sql) { - return !!sql; - }); - - return queries.length ? queries.join(';') : null; - }, - this - ); - }, - function findAffectedTables(err, sql) { - assert.ifError(err); - - if ( ! sql ) { - throw new Error("this request doesn't need an X-Cache-Channel generated"); - } - - queryTablesApi.getAffectedTablesInQuery(req.context.user, sql, this); // in addCacheChannel - }, - function buildCacheChannel(err, tableNames) { - assert.ifError(err); - - if (req.profiler) { - req.profiler.done('affectedTables'); - } - - var cacheChannel = app.buildCacheChannel(dbName,tableNames); - app.channelCache[cacheKey] = cacheChannel; - - return cacheChannel; - }, - function finish(err, cacheChannel) { - callback(err, cacheChannel); - } - ); - }; - - // Set the cache chanel info to invalidate the cache on the frontend server - // - // @param req The request object. - // The function will have no effect unless req.res exists. - // It is expected that req.params contains 'table' and 'dbname' - // - // @param cb function(err, channel) will be called when ready. - // the channel parameter will be null if nothing was added - // - app.addCacheChannel = function(app, req, cb) { - // skip non-GET requests, or requests for which there's no response - if ( req.method != 'GET' || ! req.res ) { cb(null, null); return; } - if (req.profiler) { - req.profiler.start('addCacheChannel'); - } - var res = req.res; - if ( req.params.token ) { - res.header('Cache-Control', 'public,max-age=31536000'); // 1 year - } else { - var ttl = global.environment.varnish.ttl || 86400; - res.header('Cache-Control', 'no-cache,max-age='+ttl+',must-revalidate, public'); - } - - // Set Last-Modified header - var lastUpdated; - if ( req.params.cache_buster ) { - // Assuming cache_buster is a timestamp - // FIXME: store lastModified in the cache channel instead - lastUpdated = new Date(parseInt(req.params.cache_buster)); - } else { - lastUpdated = new Date(); - } - res.header('Last-Modified', lastUpdated.toUTCString()); - - app.generateCacheChannel(app, req, function(err, channel){ - if (req.profiler) { - req.profiler.done('generateCacheChannel'); - req.profiler.end(); - } - if ( ! err ) { - res.header('X-Cache-Channel', channel); - cb(null, channel); - } else { - console.log('ERROR generating cache channel: ' + ( err.message ? err.message : err )); - // TODO: evaluate if we should bubble up the error instead - cb(null, 'ERROR'); - } - }); - }; - - // Check if a request is authorized by a signer - // - // @param req express request object - // @param callback function(err, signed_by) signed_by will be - // null if the request is not signed by anyone - // or will be a string cartodb username otherwise. - // - app.authorizedBySigner = function(req, callback) { - if ( ! req.params.token || ! req.params.signer ) { - return callback(null, false); // no signer requested - } - - var layergroup_id = req.params.token; - var auth_token = req.params.auth_token; - - mapStore.load(layergroup_id, function(err, mapConfig) { - if (err) { - return callback(err); - } - - var authorized = templateMaps.isAuthorized(mapConfig.obj().template, auth_token); - - return callback(null, authorized); - }); - }; - - // Check if a request is authorized by api_key - // - // @param user - // @param req express request object - // @param callback function(err, authorized) - // NOTE: authorized is expected to be 0 or 1 (integer) - // - app.authorizedByAPIKey = function(user, req, callback) { - var givenKey = req.query.api_key || req.query.map_key; - if ( ! givenKey && req.body ) { - // check also in request body - givenKey = req.body.api_key || req.body.map_key; - } - if ( ! givenKey ) { - return callback(null, 0); // no api key, no authorization... - } - step( - function () { - metadataBackend.getUserMapKey(user, this); - }, - function checkApiKey(err, val){ - assert.ifError(err); - return val && givenKey == val; - }, - function finish(err, authorized) { - callback(err, authorized); - } - ); - }; - - /** - * Check access authorization - * - * @param req - standard req object. Importantly contains table and host information - * @param callback function(err, allowed) is access allowed not? - */ - app.authorize = function(req, callback) { - var self = this; - var user = req.context.user; - - step( - function () { - self.authorizedByAPIKey(user, req, this); - }, - function checkApiKey(err, authorized){ - if (req.profiler) { - req.profiler.done('authorizedByAPIKey'); - } - assert.ifError(err); - - // if not authorized by api_key, continue - if (!authorized) { - // not authorized by api_key, check if authorized by signer - return self.authorizedBySigner(req, this); - } - - // authorized by api key, login as the given username and stop - pgConnection.setDBAuth(user, req.params, function(err) { - callback(err, true); // authorized (or error) - }); - }, - function checkSignAuthorized(err, authorized) { - if (err) { - return callback(err); - } - - if ( ! authorized ) { - // request not authorized by signer. - - // if no signer name was given, let dbparams and - // PostgreSQL do the rest. - // - if ( ! req.params.signer ) { - return callback(null, true); // authorized so far - } - - // if signer name was given, return no authorization - return callback(null, false); - } - - pgConnection.setDBAuth(user, req.params, function(err) { - if (req.profiler) { - req.profiler.done('setDBAuth'); - } - callback(err, true); // authorized (or error) - }); - } - ); - }; - return app; }; diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index c0c60270..c8af69d8 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -62,6 +62,7 @@ module.exports = { }, renderer: { mapnik: rendererConfig.mapnik, + torque: rendererConfig.torque, http: rendererConfig.http }, // Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161 diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e3f17931..95048e46 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "2.7.2", + "version": "2.12.1", "dependencies": { "cartodb-psql": { "version": "0.4.0", @@ -110,52 +110,63 @@ "from": "fastly-purge@~1.0.0", "dependencies": { "request": { - "version": "2.58.0", + "version": "2.61.0", "from": "request@^2.55.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.58.0.tgz", + "resolved": "https://registry.npmjs.org/request/-/request-2.61.0.tgz", "dependencies": { "bl": { - "version": "0.9.4", - "from": "bl@~0.9.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz", + "version": "1.0.0", + "from": "bl@~1.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz", "dependencies": { "readable-stream": { - "version": "1.0.33", - "from": "readable-stream@~1.0.26", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "version": "2.0.2", + "from": "readable-stream@~2.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz", "dependencies": { "core-util-is": { "version": "1.0.1", "from": "core-util-is@~1.0.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, "isarray": { "version": "0.0.1", "from": "isarray@0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, + "process-nextick-args": { + "version": "1.0.2", + "from": "process-nextick-args@~1.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.2.tgz" + }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@~0.10.x", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, - "inherits": { - "version": "2.0.1", - "from": "inherits@~2.0.1" + "util-deprecate": { + "version": "1.0.1", + "from": "util-deprecate@~1.0.1", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz" } } } } }, "caseless": { - "version": "0.10.0", - "from": "caseless@~0.10.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.10.0.tgz" + "version": "0.11.0", + "from": "caseless@~0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" }, "extend": { - "version": "2.0.1", - "from": "extend@~2.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-2.0.1.tgz" + "version": "3.0.0", + "from": "extend@~3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" }, "forever-agent": { "version": "0.6.1", @@ -163,26 +174,14 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" }, "form-data": { - "version": "1.0.0-rc1", + "version": "1.0.0-rc3", "from": "form-data@~1.0.0-rc1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc1.tgz", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz", "dependencies": { "async": { - "version": "1.3.0", - "from": "async@^1.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-1.3.0.tgz" - }, - "mime-types": { - "version": "2.1.2", - "from": "mime-types@^2.1.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.2.tgz", - "dependencies": { - "mime-db": { - "version": "1.14.0", - "from": "mime-db@~1.14.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.14.0.tgz" - } - } + "version": "1.4.2", + "from": "async@^1.4.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz" } } }, @@ -192,14 +191,14 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" }, "mime-types": { - "version": "2.0.14", - "from": "mime-types@~2.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "version": "2.1.6", + "from": "mime-types@~2.1.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.6.tgz", "dependencies": { "mime-db": { - "version": "1.12.0", - "from": "mime-db@~1.12.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz" + "version": "1.18.0", + "from": "mime-db@~1.18.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.18.0.tgz" } } }, @@ -209,9 +208,9 @@ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" }, "qs": { - "version": "3.1.0", - "from": "qs@~3.1.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-3.1.0.tgz" + "version": "4.0.0", + "from": "qs@~4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz" }, "tunnel-agent": { "version": "0.4.1", @@ -251,9 +250,9 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz" }, "hawk": { - "version": "2.3.1", - "from": "hawk@~2.3.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "version": "3.1.0", + "from": "hawk@~3.1.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz", "dependencies": { "hoek": { "version": "2.14.0", @@ -262,7 +261,7 @@ }, "boom": { "version": "2.8.0", - "from": "boom@2.x.x", + "from": "boom@^2.8.x", "resolved": "https://registry.npmjs.org/boom/-/boom-2.8.0.tgz" }, "cryptiles": { @@ -310,14 +309,14 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", "dependencies": { "bluebird": { - "version": "2.9.33", + "version": "2.9.34", "from": "bluebird@^2.9.30", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.33.tgz" + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz" }, "chalk": { - "version": "1.1.0", + "version": "1.1.1", "from": "chalk@^1.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz", "dependencies": { "ansi-styles": { "version": "2.1.0", @@ -373,9 +372,9 @@ } }, "is-my-json-valid": { - "version": "2.12.0", + "version": "2.12.2", "from": "is-my-json-valid@^2.12.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.0.tgz", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz", "dependencies": { "generate-function": { "version": "2.0.0", @@ -395,9 +394,9 @@ } }, "jsonpointer": { - "version": "1.1.0", - "from": "jsonpointer@^1.1.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz" + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" }, "xtend": { "version": "4.0.0", @@ -444,7 +443,8 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@~2.0.1" + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, @@ -460,6 +460,11 @@ } } }, + "lru-cache": { + "version": "2.6.5", + "from": "lru-cache@2.6.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz" + }, "lzma": { "version": "1.3.7", "from": "lzma@~1.3.7", @@ -519,7 +524,7 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" }, "windshaft": { - "version": "0.47.1", + "version": "0.52.0", "from": "https://github.com/CartoDB/Windshaft/tarball/backend-foundations-mvt", "resolved": "https://github.com/CartoDB/Windshaft/tarball/backend-foundations-mvt", "dependencies": { @@ -709,9 +714,9 @@ } }, "srs": { - "version": "0.4.8", + "version": "0.4.9", "from": "srs@~0.4.1", - "resolved": "https://registry.npmjs.org/srs/-/srs-0.4.8.tgz", + "resolved": "https://registry.npmjs.org/srs/-/srs-0.4.9.tgz", "dependencies": { "nan": { "version": "1.8.4", @@ -719,49 +724,49 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz" }, "node-pre-gyp": { - "version": "0.6.7", - "from": "node-pre-gyp@~0.6.7", + "version": "0.6.9", + "from": "node-pre-gyp@>=0.6.7 <0.7.0", "dependencies": { "nopt": { - "version": "3.0.1", - "from": "nopt@>=3.0.1 <3.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.1.tgz", + "version": "3.0.3", + "from": "nopt@~3.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.3.tgz", "dependencies": { "abbrev": { - "version": "1.0.5", - "from": "abbrev@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz" + "version": "1.0.7", + "from": "abbrev@1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" } } }, "npmlog": { - "version": "1.2.0", - "from": "npmlog@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.0.tgz", + "version": "1.2.1", + "from": "npmlog@~1.2.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz", "dependencies": { "ansi": { "version": "0.3.0", - "from": "ansi@>=0.3.0 <0.4.0", + "from": "ansi@~0.3.0", "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz" }, "are-we-there-yet": { "version": "1.0.4", - "from": "are-we-there-yet@>=1.0.0 <1.1.0", + "from": "are-we-there-yet@~1.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.4.tgz", "dependencies": { "delegates": { "version": "0.1.0", - "from": "delegates@>=0.1.0 <0.2.0", + "from": "delegates@^0.1.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-0.1.0.tgz" }, "readable-stream": { "version": "1.1.13", - "from": "readable-stream@>=1.1.13 <2.0.0", + "from": "readable-stream@^1.1.13", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", "dependencies": { "core-util-is": { "version": "1.0.1", - "from": "core-util-is@>=1.0.0 <1.1.0", + "from": "core-util-is@~1.0.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, "isarray": { @@ -771,12 +776,12 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", + "from": "string_decoder@~0.10.x", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@~2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -784,34 +789,34 @@ } }, "gauge": { - "version": "1.2.0", - "from": "gauge@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.0.tgz", + "version": "1.2.2", + "from": "gauge@~1.2.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.2.tgz", "dependencies": { "has-unicode": { "version": "1.0.0", - "from": "has-unicode@>=1.0.0 <2.0.0", + "from": "has-unicode@^1.0.0", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-1.0.0.tgz" }, "lodash.pad": { - "version": "3.1.0", - "from": "lodash.pad@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-3.1.0.tgz", + "version": "3.1.1", + "from": "lodash.pad@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-3.1.1.tgz", "dependencies": { "lodash._basetostring": { - "version": "3.0.0", - "from": "lodash._basetostring@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" }, "lodash._createpadding": { - "version": "3.6.0", - "from": "lodash._createpadding@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.0.tgz", + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", "dependencies": { "lodash.repeat": { - "version": "3.0.0", - "from": "lodash.repeat@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" } } } @@ -819,23 +824,23 @@ }, "lodash.padleft": { "version": "3.1.1", - "from": "lodash.padleft@>=3.0.0 <4.0.0", + "from": "lodash.padleft@^3.0.0", "resolved": "https://registry.npmjs.org/lodash.padleft/-/lodash.padleft-3.1.1.tgz", "dependencies": { "lodash._basetostring": { - "version": "3.0.0", - "from": "lodash._basetostring@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" }, "lodash._createpadding": { - "version": "3.6.0", - "from": "lodash._createpadding@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.0.tgz", + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", "dependencies": { "lodash.repeat": { - "version": "3.0.0", - "from": "lodash.repeat@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" } } } @@ -843,23 +848,23 @@ }, "lodash.padright": { "version": "3.1.1", - "from": "lodash.padright@>=3.0.0 <4.0.0", + "from": "lodash.padright@^3.0.0", "resolved": "https://registry.npmjs.org/lodash.padright/-/lodash.padright-3.1.1.tgz", "dependencies": { "lodash._basetostring": { - "version": "3.0.0", - "from": "lodash._basetostring@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" }, "lodash._createpadding": { - "version": "3.6.0", - "from": "lodash._createpadding@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.0.tgz", + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", "dependencies": { "lodash.repeat": { - "version": "3.0.0", - "from": "lodash.repeat@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.0.tgz" + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" } } } @@ -870,111 +875,126 @@ } }, "request": { - "version": "2.55.0", - "from": "request@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", + "version": "2.60.0", + "from": "request@2.x", + "resolved": "https://registry.npmjs.org/request/-/request-2.60.0.tgz", "dependencies": { "bl": { - "version": "0.9.4", - "from": "bl@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.4.tgz", + "version": "1.0.0", + "from": "bl@~1.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz", "dependencies": { "readable-stream": { - "version": "1.0.33", - "from": "readable-stream@>=1.0.26 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "version": "2.0.2", + "from": "readable-stream@~2.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz", "dependencies": { "core-util-is": { "version": "1.0.1", - "from": "core-util-is@>=1.0.0 <1.1.0", + "from": "core-util-is@~1.0.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, "isarray": { "version": "0.0.1", "from": "isarray@0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, + "process-nextick-args": { + "version": "1.0.2", + "from": "process-nextick-args@~1.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.2.tgz" + }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", + "from": "string_decoder@~0.10.x", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, - "inherits": { - "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + "util-deprecate": { + "version": "1.0.1", + "from": "util-deprecate@~1.0.1", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz" } } } } }, "caseless": { - "version": "0.9.0", - "from": "caseless@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz" + "version": "0.11.0", + "from": "caseless@~0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "extend": { + "version": "3.0.0", + "from": "extend@~3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" }, "forever-agent": { "version": "0.6.1", - "from": "forever-agent@>=0.6.0 <0.7.0", + "from": "forever-agent@~0.6.0", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" }, "form-data": { - "version": "0.2.0", - "from": "form-data@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "version": "1.0.0-rc2", + "from": "form-data@~1.0.0-rc1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc2.tgz", "dependencies": { "async": { - "version": "0.9.0", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + "version": "1.4.0", + "from": "async@^1.2.1", + "resolved": "https://registry.npmjs.org/async/-/async-1.4.0.tgz" } } }, "json-stringify-safe": { - "version": "5.0.0", - "from": "json-stringify-safe@>=5.0.0 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" + "version": "5.0.1", + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" }, "mime-types": { - "version": "2.0.10", - "from": "mime-types@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.10.tgz", + "version": "2.1.3", + "from": "mime-types@~2.1.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.3.tgz", "dependencies": { "mime-db": { - "version": "1.8.0", - "from": "mime-db@>=1.8.0 <1.9.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.8.0.tgz" + "version": "1.15.0", + "from": "mime-db@~1.15.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.15.0.tgz" } } }, "node-uuid": { "version": "1.4.3", - "from": "node-uuid@>=1.4.0 <1.5.0", + "from": "node-uuid@~1.4.0", "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" }, "qs": { - "version": "2.4.1", - "from": "qs@>=2.4.0 <2.5.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.1.tgz" + "version": "4.0.0", + "from": "qs@~4.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-4.0.0.tgz" }, "tunnel-agent": { - "version": "0.4.0", - "from": "tunnel-agent@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" + "version": "0.4.1", + "from": "tunnel-agent@~0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz" }, "tough-cookie": { - "version": "1.1.0", + "version": "2.0.0", "from": "tough-cookie@>=0.12.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-1.1.0.tgz" + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.0.0.tgz" }, "http-signature": { - "version": "0.10.1", - "from": "http-signature@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "version": "0.11.0", + "from": "http-signature@~0.11.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz", "dependencies": { "assert-plus": { "version": "0.1.5", - "from": "assert-plus@>=0.1.5 <0.2.0", + "from": "assert-plus@^0.1.5", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" }, "asn1": { @@ -990,167 +1010,162 @@ } }, "oauth-sign": { - "version": "0.6.0", - "from": "oauth-sign@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz" + "version": "0.8.0", + "from": "oauth-sign@~0.8.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz" }, "hawk": { - "version": "2.3.1", - "from": "hawk@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "version": "3.1.0", + "from": "hawk@~3.1.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz", "dependencies": { "hoek": { - "version": "2.13.0", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.13.0.tgz" + "version": "2.14.0", + "from": "hoek@2.x.x", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.14.0.tgz" }, "boom": { - "version": "2.7.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.7.1.tgz" + "version": "2.8.0", + "from": "boom@^2.8.x", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.8.0.tgz" }, "cryptiles": { "version": "2.0.4", - "from": "cryptiles@>=2.0.0 <3.0.0", + "from": "cryptiles@2.x.x", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.4.tgz" }, "sntp": { "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", + "from": "sntp@1.x.x", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" } } }, "aws-sign2": { "version": "0.5.0", - "from": "aws-sign2@>=0.5.0 <0.6.0", + "from": "aws-sign2@~0.5.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" }, "stringstream": { "version": "0.0.4", - "from": "stringstream@>=0.0.4 <0.1.0", + "from": "stringstream@~0.0.4", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" }, "combined-stream": { - "version": "0.0.7", - "from": "combined-stream@>=0.0.5 <0.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "version": "1.0.5", + "from": "combined-stream@~1.0.1", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "dependencies": { "delayed-stream": { - "version": "0.0.5", - "from": "delayed-stream@0.0.5", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" + "version": "1.0.0", + "from": "delayed-stream@~1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" } } }, "isstream": { "version": "0.1.2", - "from": "isstream@>=0.1.1 <0.2.0", + "from": "isstream@~0.1.1", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" }, "har-validator": { - "version": "1.7.0", - "from": "har-validator@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.7.0.tgz", + "version": "1.8.0", + "from": "har-validator@^1.6.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", "dependencies": { "bluebird": { - "version": "2.9.25", - "from": "bluebird@>=2.9.25 <3.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.25.tgz" + "version": "2.9.34", + "from": "bluebird@^2.9.30", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.9.34.tgz" }, "chalk": { - "version": "1.0.0", - "from": "chalk@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.0.0.tgz", + "version": "1.1.0", + "from": "chalk@^1.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.0.tgz", "dependencies": { "ansi-styles": { - "version": "2.0.1", - "from": "ansi-styles@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.0.1.tgz" + "version": "2.1.0", + "from": "ansi-styles@^2.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz" }, "escape-string-regexp": { "version": "1.0.3", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "from": "escape-string-regexp@^1.0.2", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" }, "has-ansi": { - "version": "1.0.3", - "from": "has-ansi@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-1.0.3.tgz", + "version": "2.0.0", + "from": "has-ansi@^2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "dependencies": { "ansi-regex": { - "version": "1.1.1", - "from": "ansi-regex@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" - }, - "get-stdin": { - "version": "4.0.1", - "from": "get-stdin@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + "version": "2.0.0", + "from": "ansi-regex@^2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" } } }, "strip-ansi": { - "version": "2.0.1", - "from": "strip-ansi@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", + "version": "3.0.0", + "from": "strip-ansi@^3.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz", "dependencies": { "ansi-regex": { - "version": "1.1.1", - "from": "ansi-regex@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" + "version": "2.0.0", + "from": "ansi-regex@^2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" } } }, "supports-color": { - "version": "1.3.1", - "from": "supports-color@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.3.1.tgz" + "version": "2.0.0", + "from": "supports-color@^2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" } } }, "commander": { "version": "2.8.1", - "from": "commander@>=2.8.1 <3.0.0", + "from": "commander@^2.8.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "dependencies": { "graceful-readlink": { "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", + "from": "graceful-readlink@>= 1.0.0", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" } } }, "is-my-json-valid": { - "version": "2.10.1", - "from": "is-my-json-valid@>=2.10.1 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.10.1.tgz", + "version": "2.12.1", + "from": "is-my-json-valid@^2.12.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.1.tgz", "dependencies": { "generate-function": { "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", + "from": "generate-function@^2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" }, "generate-object-property": { - "version": "1.1.1", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.1.1.tgz", + "version": "1.2.0", + "from": "generate-object-property@^1.1.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "dependencies": { "is-property": { "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", + "from": "is-property@^1.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" } } }, "jsonpointer": { "version": "1.1.0", - "from": "jsonpointer@>=1.1.0 <2.0.0", + "from": "jsonpointer@^1.1.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz" }, "xtend": { "version": "4.0.0", - "from": "xtend@>=4.0.0 <5.0.0", + "from": "xtend@^4.0.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" } } @@ -1160,42 +1175,42 @@ } }, "semver": { - "version": "4.3.3", - "from": "semver@>=4.3.2 <4.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.3.tgz" + "version": "5.0.1", + "from": "semver@~5.0.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.1.tgz" }, "tar": { - "version": "2.1.0", - "from": "tar@>=2.1.0 <2.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.1.0.tgz", + "version": "2.1.1", + "from": "tar@~2.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.1.1.tgz", "dependencies": { "block-stream": { - "version": "0.0.7", + "version": "0.0.8", "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.7.tgz" + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" }, "fstream": { - "version": "1.0.4", - "from": "fstream@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.4.tgz", + "version": "1.0.7", + "from": "fstream@^1.0.2", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.7.tgz", "dependencies": { "graceful-fs": { - "version": "3.0.6", - "from": "graceful-fs@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.6.tgz" + "version": "3.0.8", + "from": "graceful-fs@3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz" } } }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@2", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "tar-pack": { "version": "2.0.0", - "from": "tar-pack@>=2.0.0 <2.1.0", + "from": "tar-pack@~2.0.0", "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-2.0.0.tgz", "dependencies": { "uid-number": { @@ -1205,49 +1220,49 @@ }, "once": { "version": "1.1.1", - "from": "once@>=1.1.1 <1.2.0", + "from": "once@~1.1.1", "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz" }, "debug": { "version": "0.7.4", - "from": "debug@>=0.7.2 <0.8.0", + "from": "debug@~0.7.2", "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" }, "rimraf": { "version": "2.2.8", - "from": "rimraf@>=2.2.0 <2.3.0", + "from": "rimraf@~2.2.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" }, "fstream": { "version": "0.1.31", - "from": "fstream@>=0.1.22 <0.2.0", + "from": "fstream@~0.1.22", "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", "dependencies": { "graceful-fs": { - "version": "3.0.6", - "from": "graceful-fs@>=3.0.2 <3.1.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.6.tgz" + "version": "3.0.8", + "from": "graceful-fs@~3.0.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <2.1.0", + "from": "inherits@~2.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "tar": { "version": "0.1.20", - "from": "tar@>=0.1.17 <0.2.0", + "from": "tar@~0.1.17", "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", "dependencies": { "block-stream": { - "version": "0.0.7", + "version": "0.0.8", "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.7.tgz" + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@2", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -1259,36 +1274,36 @@ "dependencies": { "minimatch": { "version": "0.2.14", - "from": "minimatch@>=0.2.0 <0.3.0", + "from": "minimatch@~0.2.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "dependencies": { "lru-cache": { - "version": "2.6.2", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.2.tgz" + "version": "2.6.5", + "from": "lru-cache@2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz" }, "sigmund": { - "version": "1.0.0", - "from": "sigmund@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.0.tgz" + "version": "1.0.1", + "from": "sigmund@~1.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" } } }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@~2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "readable-stream": { "version": "1.0.33", - "from": "readable-stream@>=1.0.2 <1.1.0", + "from": "readable-stream@~1.0.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", "dependencies": { "core-util-is": { "version": "1.0.1", - "from": "core-util-is@>=1.0.0 <1.1.0", + "from": "core-util-is@~1.0.0", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, "isarray": { @@ -1298,27 +1313,27 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", + "from": "string_decoder@~0.10.x", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@~2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "graceful-fs": { "version": "1.2.3", - "from": "graceful-fs@>=1.2.0 <1.3.0", + "from": "graceful-fs@1.2", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" } } }, "mkdirp": { - "version": "0.5.0", - "from": "mkdirp@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "version": "0.5.1", + "from": "mkdirp@~0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dependencies": { "minimist": { "version": "0.0.8", @@ -1328,72 +1343,72 @@ } }, "rc": { - "version": "1.0.1", - "from": "rc@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.0.1.tgz", + "version": "1.1.0", + "from": "rc@~1.1.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.0.tgz", "dependencies": { "minimist": { - "version": "0.0.10", - "from": "minimist@>=0.0.7 <0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + "version": "1.1.2", + "from": "minimist@^1.1.2", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.2.tgz" }, "deep-extend": { "version": "0.2.11", - "from": "deep-extend@>=0.2.5 <0.3.0", + "from": "deep-extend@~0.2.5", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz" }, "strip-json-comments": { "version": "0.1.3", - "from": "strip-json-comments@>=0.1.0 <0.2.0", + "from": "strip-json-comments@0.1.x", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz" }, "ini": { - "version": "1.3.3", - "from": "ini@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.3.tgz" + "version": "1.3.4", + "from": "ini@~1.3.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" } } }, "rimraf": { - "version": "2.3.3", - "from": "rimraf@>=2.3.2 <2.4.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.3.3.tgz", + "version": "2.4.2", + "from": "rimraf@~2.4.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.2.tgz", "dependencies": { "glob": { - "version": "4.5.3", - "from": "glob@>=4.4.2 <5.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "version": "5.0.14", + "from": "glob@^5.0.14", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.14.tgz", "dependencies": { "inflight": { "version": "1.0.4", - "from": "inflight@>=1.0.4 <2.0.0", + "from": "inflight@^1.0.4", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", "dependencies": { "wrappy": { "version": "1.0.1", - "from": "wrappy@>=1.0.0 <2.0.0", + "from": "wrappy@1", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" } } }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@~2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, "minimatch": { - "version": "2.0.7", - "from": "minimatch@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.7.tgz", + "version": "2.0.10", + "from": "minimatch@2.0.x", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", "dependencies": { "brace-expansion": { "version": "1.1.0", - "from": "brace-expansion@>=1.0.0 <2.0.0", + "from": "brace-expansion@^1.0.0", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.0.tgz", "dependencies": { "balanced-match": { "version": "0.2.0", - "from": "balanced-match@>=0.2.0 <0.3.0", + "from": "balanced-match@^0.2.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz" }, "concat-map": { @@ -1406,16 +1421,21 @@ } }, "once": { - "version": "1.3.1", - "from": "once@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.1.tgz", + "version": "1.3.2", + "from": "once@^1.3.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz", "dependencies": { "wrappy": { "version": "1.0.1", - "from": "wrappy@>=1.0.0 <2.0.0", + "from": "wrappy@1", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" } } + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@^1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" } } } @@ -2267,6 +2287,18 @@ } } }, + "debug": { + "version": "2.2.0", + "from": "debug@~2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "dependencies": { + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + } + } + }, "tilelive": { "version": "4.5.3", "from": "tilelive@~4.5.3", @@ -2293,8 +2325,8 @@ }, "tilelive-mapnik": { "version": "0.6.15", - "from": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb", - "resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb", + "from": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.x-cdb1", + "resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.x-cdb1", "dependencies": { "generic-pool": { "version": "2.1.1", @@ -2313,6 +2345,391 @@ "from": "https://github.com/CartoDB/tilelive-bridge/tarball/cdb", "resolved": "https://github.com/CartoDB/tilelive-bridge/tarball/cdb", "dependencies": { + "mapnik": { + "version": "1.4.15-cdb1", + "from": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1", + "resolved": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1", + "dependencies": { + "nan": { + "version": "1.2.0", + "from": "nan@~1.2.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.2.0.tgz" + }, + "mapnik-vector-tile": { + "version": "0.5.5", + "from": "mapnik-vector-tile@0.5.5", + "resolved": "https://registry.npmjs.org/mapnik-vector-tile/-/mapnik-vector-tile-0.5.5.tgz" + }, + "node-pre-gyp": { + "version": "0.5.25", + "from": "node-pre-gyp@0.5.25", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.5.25.tgz", + "dependencies": { + "nopt": { + "version": "3.0.1", + "from": "nopt@~3.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.1.tgz", + "dependencies": { + "abbrev": { + "version": "1.0.5", + "from": "abbrev@1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.5.tgz" + } + } + }, + "npmlog": { + "version": "0.1.1", + "from": "npmlog@~0.1.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-0.1.1.tgz", + "dependencies": { + "ansi": { + "version": "0.3.0", + "from": "ansi@~0.3.0", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz" + } + } + }, + "request": { + "version": "2.40.0", + "from": "request@2.x", + "resolved": "https://registry.npmjs.org/request/-/request-2.40.0.tgz", + "dependencies": { + "qs": { + "version": "1.0.2", + "from": "qs@~1.0.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-1.0.2.tgz" + }, + "json-stringify-safe": { + "version": "5.0.0", + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" + }, + "mime-types": { + "version": "1.0.2", + "from": "mime-types@~1.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz" + }, + "forever-agent": { + "version": "0.5.2", + "from": "forever-agent@~0.5.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz" + }, + "node-uuid": { + "version": "1.4.1", + "from": "node-uuid@~1.4.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.1.tgz" + }, + "tough-cookie": { + "version": "0.12.1", + "from": "tough-cookie@>=0.12.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", + "dependencies": { + "punycode": { + "version": "1.3.1", + "from": "punycode@>=0.2.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.1.tgz" + } + } + }, + "form-data": { + "version": "0.1.4", + "from": "form-data@~0.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", + "dependencies": { + "combined-stream": { + "version": "0.0.5", + "from": "combined-stream@~0.0.4", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.5.tgz", + "dependencies": { + "delayed-stream": { + "version": "0.0.5", + "from": "delayed-stream@0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz" + } + } + }, + "mime": { + "version": "1.2.11", + "from": "mime@~1.2.11", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz" + }, + "async": { + "version": "0.9.0", + "from": "async@~0.9.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz" + } + } + }, + "tunnel-agent": { + "version": "0.4.0", + "from": "tunnel-agent@~0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" + }, + "http-signature": { + "version": "0.10.0", + "from": "http-signature@~0.10.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.0.tgz", + "dependencies": { + "assert-plus": { + "version": "0.1.2", + "from": "assert-plus@0.1.2", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.2.tgz" + }, + "asn1": { + "version": "0.1.11", + "from": "asn1@0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" + }, + "ctype": { + "version": "0.5.2", + "from": "ctype@0.5.2", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.2.tgz" + } + } + }, + "oauth-sign": { + "version": "0.3.0", + "from": "oauth-sign@~0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz" + }, + "hawk": { + "version": "1.1.1", + "from": "hawk@1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "dependencies": { + "hoek": { + "version": "0.9.1", + "from": "hoek@0.9.x", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz" + }, + "boom": { + "version": "0.4.2", + "from": "boom@0.4.x", + "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz" + }, + "cryptiles": { + "version": "0.2.2", + "from": "cryptiles@0.2.x", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz" + }, + "sntp": { + "version": "0.2.4", + "from": "sntp@0.2.x", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz" + } + } + }, + "aws-sign2": { + "version": "0.5.0", + "from": "aws-sign2@~0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" + }, + "stringstream": { + "version": "0.0.4", + "from": "stringstream@~0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" + } + } + }, + "semver": { + "version": "3.0.1", + "from": "semver@~3.0.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-3.0.1.tgz" + }, + "tar": { + "version": "1.0.1", + "from": "tar@~1.0.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-1.0.1.tgz", + "dependencies": { + "block-stream": { + "version": "0.0.7", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.7.tgz" + }, + "fstream": { + "version": "1.0.2", + "from": "fstream@^1.0.2", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.2.tgz", + "dependencies": { + "graceful-fs": { + "version": "3.0.2", + "from": "graceful-fs@3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.2.tgz" + } + } + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "tar-pack": { + "version": "2.0.0", + "from": "tar-pack@~2.0.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-2.0.0.tgz", + "dependencies": { + "uid-number": { + "version": "0.0.3", + "from": "uid-number@0.0.3", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.3.tgz" + }, + "once": { + "version": "1.1.1", + "from": "once@~1.1.1", + "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz" + }, + "debug": { + "version": "0.7.4", + "from": "debug@~0.7.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" + }, + "fstream": { + "version": "0.1.31", + "from": "fstream@~0.1.22", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", + "dependencies": { + "graceful-fs": { + "version": "3.0.2", + "from": "graceful-fs@~3.0.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.2.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "tar": { + "version": "0.1.20", + "from": "tar@~0.1.17", + "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", + "dependencies": { + "block-stream": { + "version": "0.0.7", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.7.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "fstream-ignore": { + "version": "0.0.7", + "from": "fstream-ignore@0.0.7", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-0.0.7.tgz", + "dependencies": { + "minimatch": { + "version": "0.2.14", + "from": "minimatch@~0.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "dependencies": { + "lru-cache": { + "version": "2.5.0", + "from": "lru-cache@2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz" + }, + "sigmund": { + "version": "1.0.0", + "from": "sigmund@~1.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.0.tgz" + } + } + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "readable-stream": { + "version": "1.0.31", + "from": "readable-stream@~1.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.31.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "from": "graceful-fs@1.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" + } + } + }, + "mkdirp": { + "version": "0.5.0", + "from": "mkdirp@~0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "rc": { + "version": "0.5.1", + "from": "rc@~0.5.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.10", + "from": "minimist@~0.0.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + }, + "deep-extend": { + "version": "0.2.11", + "from": "deep-extend@~0.2.5", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz" + }, + "strip-json-comments": { + "version": "0.1.3", + "from": "strip-json-comments@0.1.x", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz" + }, + "ini": { + "version": "1.1.0", + "from": "ini@~1.1.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.1.0.tgz" + } + } + }, + "rimraf": { + "version": "2.2.8", + "from": "rimraf@~2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + } + } + } + } + }, "mapnik-pool": { "version": "0.1.1", "from": "mapnik-pool@0.1.x", @@ -2333,9 +2750,9 @@ } }, "mapnik": { - "version": "1.4.15-cdb1", - "from": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1", - "resolved": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1", + "version": "1.4.15-cdb2", + "from": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb2", + "resolved": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb2", "dependencies": { "nan": { "version": "1.2.0", @@ -2718,14 +3135,14 @@ } }, "canvas": { - "version": "1.2.1", - "from": "canvas@1.2.1", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-1.2.1.tgz", + "version": "1.2.7-cdb1", + "from": "https://github.com/CartoDB/node-canvas/tarball/1.2.7-cdb1", + "resolved": "https://github.com/CartoDB/node-canvas/tarball/1.2.7-cdb1", "dependencies": { "nan": { - "version": "1.5.3", - "from": "nan@~1.5.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-1.5.3.tgz" + "version": "1.9.0", + "from": "nan@^1.8.4", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.9.0.tgz" } } }, @@ -2800,7 +3217,8 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@~2.0.1" + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } } diff --git a/package.json b/package.json index fd98adbf..348142de 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "2.7.2", + "version": "2.12.1", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" @@ -33,6 +33,7 @@ "cartodb-psql": "~0.4.0", "fastly-purge": "~1.0.0", "redis-mpool": "~0.4.0", + "lru-cache": "2.6.5", "lzma": "~1.3.7", "log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb" }, diff --git a/test/acceptance/health_check.js b/test/acceptance/health_check.js index ba6340a7..68ca3346 100644 --- a/test/acceptance/health_check.js +++ b/test/acceptance/health_check.js @@ -60,7 +60,7 @@ describe('health checks', function () { callback(null, "Maintenance"); }; - healthCheck.check(null, function(err/*, result*/) { + healthCheck.check(function(err) { assert.equal(err.message, "Maintenance"); assert.equal(err.http_status, 503); done(); diff --git a/test/acceptance/multilayer.js b/test/acceptance/multilayer.js index b23521ce..17609b9c 100644 --- a/test/acceptance/multilayer.js +++ b/test/acceptance/multilayer.js @@ -17,6 +17,8 @@ var serverOptions = require('../../lib/cartodb/server_options'); var server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); +var TablesCacheEntry = require('../../lib/cartodb/cache/model/database_tables_entry'); + ['/api/v1/map', '/user/localhost/api/v1/map'].forEach(function(layergroup_url) { var suiteName = 'multilayer:postgres=layergroup_url=' + layergroup_url; @@ -67,17 +69,14 @@ suite(suiteName, function() { assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); assert.equal(parsedBody.last_updated, expected_last_updated); - if ( expected_token ) { - assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); - assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid); - } - else expected_token = parsedBody.layergroupid.split(':')[0]; + assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid); + expected_token = parsedBody.layergroupid.split(':')[0]; next(null, res); }); }, function do_get_tile(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png', @@ -122,7 +121,7 @@ suite(suiteName, function() { // See https://github.com/CartoDB/Windshaft-cartodb/issues/170 function do_get_tile_nosignature(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + '/localhost@' + expected_token + ':cb0/0/0/0.png', @@ -139,7 +138,7 @@ suite(suiteName, function() { }, function do_get_grid_layer0(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json', @@ -156,7 +155,7 @@ suite(suiteName, function() { }, function do_get_grid_layer1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json', @@ -178,12 +177,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } }); }); } @@ -226,17 +233,23 @@ suite(suiteName, function() { test("get creation requests has cache", function(done) { - var layergroup = { - version: '1.0.0', - layers: [ - { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + - ' from test_table limit 2', - cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', - cartocss_version: '2.0.1' - } } - ] - }; + var layergroup = { + version: '1.0.0', + layers: [ + { options: { + sql: 'select cartodb_id, the_geom_webmercator from test_table', + cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', + cartocss_version: '2.0.1', + interactivity: 'cartodb_id' + } }, + { options: { + sql: 'select cartodb_id, the_geom_webmercator from test_table_2', + cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }', + cartocss_version: '2.0.2', + interactivity: 'cartodb_id' + } } + ] + }; var expected_token; step( @@ -250,11 +263,15 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function do_check_create(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); expected_token = parsedBody.layergroupid.split(':')[0]; helper.checkCache(res); + helper.checkSurrogateKey(res, new TablesCacheEntry('test_windshaft_cartodb_user_1_db', [ + 'public.test_table', + 'public.test_table_2' + ]).key().join(' ')); return null; }, function finish(err) { @@ -264,12 +281,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } }); }); } @@ -353,13 +378,15 @@ suite(suiteName, function() { if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); } - else expected_token = parsedBody.layergroupid.split(':')[0]; + else { + expected_token = parsedBody.layergroupid.split(':')[0]; + } next(null, res); }); }, function do_get_tile1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb10/1/0/0.png', @@ -398,7 +425,7 @@ suite(suiteName, function() { }, function do_get_tile4(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb11/4/0/0.png', @@ -437,7 +464,7 @@ suite(suiteName, function() { }, function do_get_grid1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/0/1/0/0.grid.json', @@ -454,7 +481,7 @@ suite(suiteName, function() { }, function do_get_grid4(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/0/4/0/0.grid.json', @@ -476,12 +503,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } }); }); } @@ -511,13 +546,17 @@ suite(suiteName, function() { { var next = this; redis_stats_client.select(redis_stats_db, function(err) { - if ( err ) next(err); - else redis_stats_client.del(statskey+':global', next); + if ( err ) { + next(err); + } + else { + redis_stats_client.del(statskey+':global', next); + } }); }, function do_post_1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url, @@ -531,12 +570,12 @@ suite(suiteName, function() { }); }, function check_global_stats_1(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":global to be 1, got " + val); redis_stats_client.zscore(statskey+':stat_tag:random_tag', now, this); }, function check_tag_stats_1_do_post_2(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val); var next = this; @@ -553,19 +592,21 @@ suite(suiteName, function() { }, function check_global_stats_2(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":global to be 2, got " + val); redis_stats_client.zscore(statskey+':stat_tag:' + layergroup.stat_tag, now, this); }, function check_tag_stats_2(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 2, got " + val); return 1; }, function cleanup_map_style(err) { - if ( err ) errors.push('' + err); + if ( err ) { + errors.push('' + err); + } var next = this; // trip epoch expected_token = expected_token.split(':')[0]; @@ -574,13 +615,21 @@ suite(suiteName, function() { }); }, function cleanup_stats(err) { - if ( err ) errors.push('' + err); + if ( err ) { + errors.push('' + err); + } redis_client.del([statskey+':global', statskey+':stat_tag:'+layergroup.stat_tag], this); }, function finish(err) { - if ( err ) errors.push('' + err); - if ( errors.length ) done(new Error(errors.join(','))); - else done(null); + if ( err ) { + errors.push('' + err); + } + if ( errors.length ) { + done(new Error(errors.join(','))); + } + else { + done(null); + } } ); }); @@ -678,13 +727,15 @@ suite(suiteName, function() { if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); } - else expected_token = parsedBody.layergroupid.split(':')[0]; + else { + expected_token = parsedBody.layergroupid.split(':')[0]; + } next(null, res); }); }, function do_get_tile(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234', @@ -705,7 +756,7 @@ suite(suiteName, function() { }, function do_get_grid_layer0(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json?map_key=1234', @@ -718,7 +769,7 @@ suite(suiteName, function() { }, function do_get_grid_layer1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json?map_key=1234', @@ -732,7 +783,7 @@ suite(suiteName, function() { }, function do_get_tile_unauth(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png', @@ -748,7 +799,7 @@ suite(suiteName, function() { }, function do_get_grid_layer0_unauth(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json', @@ -763,7 +814,7 @@ suite(suiteName, function() { }, function do_get_grid_layer1_unauth(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json', @@ -783,12 +834,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } }); }); } @@ -823,19 +882,21 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function check_post(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); assert.equal(parsedBody.last_updated, expected_last_updated); if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); } - else expected_token = parsedBody.layergroupid.split(':')[0]; + else { + expected_token = parsedBody.layergroupid.split(':')[0]; + } return null; }, function do_get0(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234', @@ -845,7 +906,7 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function do_check0(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "image/png"); @@ -857,14 +918,14 @@ suite(suiteName, function() { return null; }, function do_restart_server(err/*, res*/) { - if ( err ) throw err; + assert.ifError(err); // hack simulating restart... server = new CartodbWindshaft(serverOptions); return null; }, function do_get1(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234', @@ -874,7 +935,7 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function do_check1(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "image/png"); @@ -892,12 +953,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors.join(','))); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors.join(','))); + } + else { + done(null); + } }); }); } @@ -1024,7 +1093,7 @@ suite(suiteName, function() { }, function do_get_tile(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png', @@ -1048,12 +1117,20 @@ suite(suiteName, function() { console.log("Error: " + err); } redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( err ) { + errors.push(err.message); + } + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } }); }); } @@ -1087,7 +1164,7 @@ suite(suiteName, function() { }, {}, function(res) { next(null, res); }); }, function check_result(err, res) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var parsedBody = JSON.parse(res.body); @@ -1104,7 +1181,7 @@ suite(suiteName, function() { }, function do_get_tile(err) { - if ( err ) throw err; + assert.ifError(err); var next = this; assert.response(server, { url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?api_key=1234', @@ -1114,19 +1191,27 @@ suite(suiteName, function() { }, {}, function(res) { next(null, res); }); }, function check_get_tile(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); return null; }, function cleanup(err) { - if ( err ) errors.push(err.message); - if ( ! expected_token ) return null; + if ( err ) { + errors.push(err.message); + } + if ( ! expected_token ) { + return null; + } var next = this; redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } next(); }); }); @@ -1136,8 +1221,12 @@ suite(suiteName, function() { errors.push(err.message); console.log("Error: " + err); } - if ( errors.length ) done(new Error(errors)); - else done(null); + if ( errors.length ) { + done(new Error(errors)); + } + else { + done(null); + } } ); }); @@ -1146,11 +1235,14 @@ suite(suiteName, function() { // See https://github.com/CartoDB/Windshaft-cartodb/issues/111 test("sql string can be very long", function(done){ var long_val = 'pretty'; - for (var i=0; i<1024; ++i) long_val += ' long'; + for (var i=0; i<1024; ++i) { + long_val += ' long'; + } long_val += ' string'; var sql = "SELECT "; - for (i=0; i<16; ++i) + for (i=0; i<16; ++i) { sql += "'" + long_val + "'::text as pretty_long_field_name_" + i + ", "; + } sql += "cartodb_id, the_geom_webmercator FROM gadm4 g"; var layergroup = { version: '1.0.0', @@ -1178,7 +1270,7 @@ suite(suiteName, function() { }, {}, function(res) { next(null, res); }); }, function check_result(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); var parsedBody = JSON.parse(res.body); var token_components = parsedBody.layergroupid.split(':'); @@ -1186,22 +1278,36 @@ suite(suiteName, function() { return null; }, function cleanup(err) { - if ( err ) errors.push('' + err); - if ( ! expected_token ) return null; + if ( err ) { + errors.push('' + err); + } + if ( ! expected_token ) { + return null; + } var next = this; redis_client.keys("map_cfg|" + expected_token, function(err, matches) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } assert.equal(matches.length, 1, "Missing expected token " + expected_token + " from redis: " + matches); redis_client.del(matches, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } next(); }); }); }, function finish(err) { - if ( err ) errors.push('' + err); - if ( errors.length ) done(new Error(errors.join(','))); - else done(null); + if ( err ) { + errors.push('' + err); + } + if ( errors.length ) { + done(new Error(errors.join(','))); + } + else { + done(null); + } } ); }); @@ -1232,7 +1338,7 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function check_post(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.errors, 'Missing "errors" in response: ' + JSON.stringify(parsed)); @@ -1274,7 +1380,7 @@ suite(suiteName, function() { }, {}, function(res, err) { next(err, res); }); }, function check_post(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.errors, 'Missing "errors" in response: ' + JSON.stringify(parsed)); diff --git a/test/acceptance/multilayer_server.js b/test/acceptance/multilayer_server.js index 4845545d..b8b5db2a 100644 --- a/test/acceptance/multilayer_server.js +++ b/test/acceptance/multilayer_server.js @@ -318,7 +318,7 @@ describe('tests from old api translated to multilayer', function() { var parsed = JSON.parse(res.body); assert.deepEqual(parsed, { - errors: ["Error: could not fetch affected tables and last updated time: fake error message"] + errors: ["Error: could not fetch affected tables or last updated time: fake error message"] }); done(); @@ -346,7 +346,7 @@ describe('tests from old api translated to multilayer', function() { }; // reset internal cacheChannel cache - server.channelCache = {}; + server.layergroupAffectedTablesCache.cache.reset(); assert.response(server, { diff --git a/test/acceptance/ported/support/ported_server_options.js b/test/acceptance/ported/support/ported_server_options.js index b17eef6d..b4ca6a5a 100644 --- a/test/acceptance/ported/support/ported_server_options.js +++ b/test/acceptance/ported/support/ported_server_options.js @@ -58,6 +58,10 @@ module.exports = _.extend({}, serverOptions, { _.extend(req.params, req.query); req.params.user = 'localhost'; req.context = {user: 'localhost'}; + + req.params.dbhost = global.environment.postgres.host; + req.params.dbport = req.params.dbport || global.environment.postgres.port; + req.params.dbuser = 'test_windshaft_publicuser'; if (req.params.dbname !== 'windshaft_test2') { req.params.dbuser = 'test_windshaft_cartodb_user_1'; diff --git a/test/acceptance/server.js b/test/acceptance/server.js index a9fa2e35..326e0329 100644 --- a/test/acceptance/server.js +++ b/test/acceptance/server.js @@ -23,7 +23,7 @@ suite('server', function() { },{}, function(res, err) { next(err,res); }); }, function doCheck(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.ok(res.statusCode, 200); var cc = res.headers['x-cache-channel']; assert.ok(!cc); diff --git a/test/acceptance/templates.js b/test/acceptance/templates.js index 79f24ac5..c1fa248b 100644 --- a/test/acceptance/templates.js +++ b/test/acceptance/templates.js @@ -3,7 +3,8 @@ var _ = require('underscore'); var redis = require('redis'); var step = require('step'); var strftime = require('strftime'); -var NamedMapsCacheEntry = require(__dirname + '/../../lib/cartodb/cache/model/named_maps_entry'); +var NamedMapsCacheEntry = require('../../lib/cartodb/cache/model/named_maps_entry'); +var TablesCacheEntry = require('../../lib/cartodb/cache/model/database_tables_entry'); var redis_stats_db = 5; // Pollute the PG environment to make sure @@ -20,7 +21,7 @@ var server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); describe('template_api', function() { - server.channelCache = {}; + server.layergroupAffectedTablesCache.cache.reset(); var redis_client = redis.createClient(global.environment.redis.port); @@ -85,7 +86,7 @@ describe('template_api', function() { }, function postTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), res.body); @@ -99,7 +100,7 @@ describe('template_api', function() { }, function rePostTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); var expectedBody = { template_id: expected_tpl_id }; @@ -110,7 +111,7 @@ describe('template_api', function() { }, function checkFailure(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -119,26 +120,35 @@ describe('template_api', function() { return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt)|/) ) - return m; + if ( m.match(/^map_(tpl|crt)|/) ) { + return m; + } }); if ( todrop.length !== 1 ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); } else { - if ( todrop.indexOf('map_tpl|localhost') == -1 ) { + if ( todrop.indexOf('map_tpl|localhost') === -1 ) { errors.push(new Error("Missing 'map_tpl|localhost' key in redis")); } } redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); }); } @@ -169,7 +179,7 @@ describe('template_api', function() { }, function checkFailure1(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -180,7 +190,7 @@ describe('template_api', function() { }, function postTemplate2(err) { - if ( err ) throw err; + assert.ifError(err); // clone the valid one and rename it var broken_template = JSON.parse(JSON.stringify(template_acceptance1)); broken_template.name = 'broken1'; @@ -199,7 +209,7 @@ describe('template_api', function() { }, function checkFailure2(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -210,7 +220,7 @@ describe('template_api', function() { }, function postTemplateValid(err) { - if ( err ) throw err; + assert.ifError(err); // clone the valid one and rename it var broken_template = JSON.parse(JSON.stringify(template_acceptance1)); broken_template.name = 'broken1'; @@ -226,7 +236,7 @@ describe('template_api', function() { }, function putTemplateInvalid(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -250,7 +260,7 @@ describe('template_api', function() { }, function deleteTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), @@ -269,24 +279,34 @@ describe('template_api', function() { function(res, err) { next(err, res); }); }, function checkDelete(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body); assert.ok(!res.body, 'Unexpected body in DELETE /template response'); return null; }, function finish(err) { var errors = []; - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt)|/) ) - return m; + if ( m.match(/^map_(tpl|crt)|/) ) { + return m; + } }); - if ( todrop.length ) - errors.push(new Error("Unexpected keys in redis: " + todrop)); - if ( errors.length ) done(new Error(errors.join(','))); - else done(); + if ( todrop.length ) { + errors.push(new Error("Unexpected keys in redis: " + todrop)); + } + if ( errors.length ) { + done(new Error(errors.join(','))); + } + else { + done(); + } }); } ); @@ -349,7 +369,7 @@ describe('template_api', function() { }); }, function deleteTemplate(err) { - if ( err ) throw err; + assert.ifError(err); var del_request = { url: '/api/v1/map/named/' + tmpl.name + '?api_key=1234', method: 'DELETE', @@ -383,7 +403,7 @@ describe('template_api', function() { }, function postTemplate2(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -405,7 +425,7 @@ describe('template_api', function() { }, function litsTemplatesUnauthenticated(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -422,7 +442,7 @@ describe('template_api', function() { }, function litsTemplates(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), @@ -440,40 +460,50 @@ describe('template_api', function() { }, function checkList(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_ids'), "Missing 'template_ids' from response body: " + res.body); var ids = parsed.template_ids; assert.equal(ids.length, 2); - assert.ok(ids.indexOf(tplid1) != -1, + assert.ok(ids.indexOf(tplid1) !== -1, 'Missing "' + tplid1 + "' from list response: " + ids.join(',')); - assert.ok(ids.indexOf(tplid2) != -1, + assert.ok(ids.indexOf(tplid2) !== -1, 'Missing "' + tplid2 + "' from list response: " + ids.join(',')); return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) - return m; + { + return m; + } }); if ( todrop.length !== 1 ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); } else { - if ( todrop.indexOf('map_tpl|localhost') == -1 ) { + if ( todrop.indexOf('map_tpl|localhost') === -1 ) { errors.push(new Error("Missing 'map_tpl|localhost' key in redis")); } } redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); }); } @@ -499,7 +529,7 @@ describe('template_api', function() { }, function putMisnamedTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -520,7 +550,7 @@ describe('template_api', function() { }, function putUnexistentTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -538,7 +568,7 @@ describe('template_api', function() { }, function putValidTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 400, res.statusCode + ": " + res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -556,7 +586,7 @@ describe('template_api', function() { }, function checkValidUpate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -565,26 +595,35 @@ describe('template_api', function() { return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt)|/) ) - return m; + if ( m.match(/^map_(tpl|crt)|/) ) { + return m; + } }); if ( todrop.length !== 1 ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); } else { - if ( todrop.indexOf('map_tpl|localhost') == -1 ) { + if ( todrop.indexOf('map_tpl|localhost') === -1 ) { errors.push(new Error("Missing 'map_tpl|localhost' key in redis")); } } redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); }); } @@ -610,7 +649,7 @@ describe('template_api', function() { }, function getTemplateUnauthorized(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -627,7 +666,7 @@ describe('template_api', function() { }, function getTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body); var parsedBody = JSON.parse(res.body); assert.ok(parsedBody.hasOwnProperty('errors'), res.body); @@ -644,7 +683,7 @@ describe('template_api', function() { }, function checkReturnTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template'), @@ -653,26 +692,35 @@ describe('template_api', function() { return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt)|/) ) - return m; + if ( m.match(/^map_(tpl|crt)|/) ) { + return m; + } }); if ( todrop.length !== 1 ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); } else { - if ( todrop.indexOf('map_tpl|localhost') == -1 ) { + if ( todrop.indexOf('map_tpl|localhost') === -1 ) { errors.push(new Error("Missing 'map_tpl|localhost' key in redis")); } } redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); }); } @@ -698,7 +746,7 @@ describe('template_api', function() { }, function getTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -715,7 +763,7 @@ describe('template_api', function() { }, function deleteTemplateUnauthorized(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template'), @@ -732,7 +780,7 @@ describe('template_api', function() { }, function deleteTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, res.statusCode + ": " + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), @@ -750,7 +798,7 @@ describe('template_api', function() { }, function getMissingTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body); assert.ok(!res.body, 'Unexpected body in DELETE /template response'); var get_request = { @@ -764,7 +812,7 @@ describe('template_api', function() { }, function checkGetFailure(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), @@ -774,27 +822,38 @@ describe('template_api', function() { return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt)|/) ) - return m; + if ( m.match(/^map_(tpl|crt)|/) ) { + return m; + } }); if ( todrop.length ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); } else { if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } } }); } @@ -841,7 +900,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -859,7 +918,7 @@ describe('template_api', function() { }, // See https://github.com/CartoDB/Windshaft-cartodb/issues/173 function instanciateForeignDB(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -879,7 +938,7 @@ describe('template_api', function() { }, function instanciateAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('errors'), "Missing 'errors' from response body: " + res.body); @@ -895,7 +954,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -918,7 +977,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -937,7 +996,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkTile(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body); assert.equal(res.headers['content-type'], "image/png"); @@ -945,7 +1004,7 @@ describe('template_api', function() { }, // See https://github.com/CartoDB/Windshaft-cartodb/issues/172 function fetchTileForeignSignature(err) { - if ( err ) throw err; + assert.ifError(err); var foreignsigned = layergroupid.replace(/[^@]*@/, 'foreign@'); var get_request = { url: '/api/v1/map/' + foreignsigned + '/0/0/0.png?auth_token=valid1', @@ -958,7 +1017,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkForeignSignerError(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body); var parsed = JSON.parse(res.body); @@ -970,7 +1029,7 @@ describe('template_api', function() { }, function deleteTemplate(err) { - if ( err ) throw err; + assert.ifError(err); var del_request = { url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', @@ -981,7 +1040,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileDeleted(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { @@ -995,33 +1054,45 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkTileAvailable(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Tile should be accessible'); assert.equal(res.headers['content-type'], "image/png"); return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) - return m; + { + return m; + } }); if ( todrop.length ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); } else { if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } } }); } @@ -1067,7 +1138,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1085,7 +1156,7 @@ describe('template_api', function() { }, function instanciateAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1104,7 +1175,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1127,7 +1198,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1146,7 +1217,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkTile_fetchOnRestart(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); @@ -1154,7 +1225,7 @@ describe('template_api', function() { assert.ok(cc); assert.ok(cc.match, /ciao/, cc); // hack simulating restart... - server.channelCache = {}; // need to clean channel cache + server.layergroupAffectedTablesCache.cache.reset(); // need to clean channel cache var get_request = { url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1', method: 'GET', @@ -1166,7 +1237,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkCacheChannel(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Unexpected error for authorized instance: ' + res.statusCode + ' -- ' + res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); @@ -1177,7 +1248,7 @@ describe('template_api', function() { }, function deleteTemplate(err) { - if ( err ) throw err; + assert.ifError(err); var del_request = { url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', @@ -1188,7 +1259,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchTileDeleted(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { @@ -1202,33 +1273,45 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkTorqueTileAvailable(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Torque tile should be accessible'); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) - return m; + { + return m; + } }); if ( todrop.length ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); } else { if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } } }); } @@ -1275,7 +1358,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1293,7 +1376,7 @@ describe('template_api', function() { }, function instanciateAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1312,7 +1395,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchAttributeNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1336,7 +1419,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchAttributeAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 403, 'Fetching tile with no auth: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1355,7 +1438,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkAttribute(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Unexpected error for authorized getAttributes: ' + res.statusCode + ' -- ' + res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); @@ -1363,7 +1446,7 @@ describe('template_api', function() { }, function deleteTemplate(err) { - if ( err ) throw err; + assert.ifError(err); var del_request = { url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', @@ -1374,7 +1457,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function fetchAttrDeleted(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { @@ -1388,33 +1471,45 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function checkLayerAttributesAvailable(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Layer attributes should be accessible'); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) - return m; + { + return m; + } }); if ( todrop.length ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); } else { if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } } }); } @@ -1459,7 +1554,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1478,7 +1573,7 @@ describe('template_api', function() { }, function instanciateAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Unexpected success instanciating template with no auth: ' + res.statusCode + ': ' + res.body); done(); @@ -1522,7 +1617,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1539,11 +1634,15 @@ describe('template_api', function() { }, function checkInstanciation(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); // See https://github.com/CartoDB/Windshaft-cartodb/issues/176 helper.checkCache(res); - helper.checkSurrogateKey(res, new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key()); + var expectedSurrogateKey = [ + new TablesCacheEntry('test_windshaft_cartodb_user_1_db', ['public.test_table_private_1']).key(), + new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key() + ].join(' '); + helper.checkSurrogateKey(res, expectedSurrogateKey); return null; }, function finish(err) { @@ -1591,7 +1690,7 @@ describe('template_api', function() { }, function instanciateNoAuth(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1608,11 +1707,15 @@ describe('template_api', function() { }, function checkInstanciation(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); // See https://github.com/CartoDB/Windshaft-cartodb/issues/176 helper.checkCache(res); - helper.checkSurrogateKey(res, new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key()); + var expectedSurrogateKey = [ + new TablesCacheEntry('test_windshaft_cartodb_user_1_db', ['public.test_table_private_1']).key(), + new NamedMapsCacheEntry('localhost', template_acceptance_open.name).key() + ].join(' '); + helper.checkSurrogateKey(res, expectedSurrogateKey); return null; }, function finish(err) { @@ -1650,13 +1753,17 @@ describe('template_api', function() { { var next = this; redis_stats_client.select(redis_stats_db, function(err) { - if ( err ) next(err); - else redis_stats_client.del(statskey+':global', next); + if ( err ) { + next(err); + } + else { + redis_stats_client.del(statskey+':global', next); + } }); }, function do_post_tempate(err) { - if ( err ) throw err; + assert.ifError(err); var post_request = { url: '/api/v1/map/named?api_key=1234', method: 'POST', @@ -1668,7 +1775,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function instantiateTemplate(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); template_id = JSON.parse(res.body).template_id; var post_request = { @@ -1682,7 +1789,7 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function check_global_stats(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1691,19 +1798,19 @@ describe('template_api', function() { redis_stats_client.ZSCORE(statskey + ":global", now, this); }, function check_tag_stats(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":global to be 1, got " + val); redis_stats_client.ZSCORE(statskey+':stat_tag:random_tag', now, this); }, function check_tag_stats_value(err, val) { - if ( err ) throw err; + assert.ifError(err); assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val); return null; }, function deleteTemplate(err) { - if ( err ) throw err; + assert.ifError(err); var del_request = { url: '/api/v1/map/named/' + template_id + '?api_key=1234', method: 'DELETE', @@ -1714,15 +1821,23 @@ describe('template_api', function() { function(res) { next(null, res); }); }, function cleanup_stats(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body); - if ( err ) errors.push('' + err); + if ( err ) { + errors.push('' + err); + } redis_client.del([statskey+':global', statskey+':stat_tag:'+layergroup.stat_tag], this); }, function finish(err) { - if ( err ) errors.push('' + err); - if ( errors.length ) done(new Error(errors.join(','))); - else done(null); + if ( err ) { + errors.push('' + err); + } + if ( errors.length ) { + done(new Error(errors.join(','))); + } + else { + done(null); + } } ); }); @@ -1767,7 +1882,7 @@ describe('template_api', function() { }, function instance1(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1784,7 +1899,7 @@ describe('template_api', function() { function(res, err) { next(err, res); }); }, function checkInstance1(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); @@ -1796,7 +1911,7 @@ describe('template_api', function() { }, function updateTemplate(err) { - if ( err ) throw err; + assert.ifError(err); // clone the valid one and rename it var changedTemplate = JSON.parse(JSON.stringify(template_acceptance2)); changedTemplate.auth.method = 'open'; @@ -1812,7 +1927,7 @@ describe('template_api', function() { }, function instance2(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('template_id'), @@ -1829,37 +1944,47 @@ describe('template_api', function() { function(res, err) { next(err, res); }); }, function checkInstance2(err, res) { - if ( err ) throw err; + assert.ifError(err); assert.equal(res.statusCode, 200, 'Instantiating template: ' + res.statusCode + ': ' + res.body); var parsed = JSON.parse(res.body); assert.ok(parsed.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from response body: " + res.body); - assert.ok(layergroupid != parsed.layergroupid); + assert.ok(layergroupid !== parsed.layergroupid); helper.checkSurrogateKey(res, new NamedMapsCacheEntry('localhost', template_acceptance2.name).key()); return null; }, function finish(err) { - if ( err ) errors.push(err); + if ( err ) { + errors.push(err); + } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) - return m; + { + return m; + } }); if ( todrop.length !== 1 ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); } else { - if ( todrop.indexOf('map_tpl|localhost') == -1 ) { + if ( todrop.indexOf('map_tpl|localhost') === -1 ) { errors.push(new Error("Missing 'map_tpl|localhost' key in redis")); } } redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); }); } @@ -2018,25 +2143,35 @@ describe('template_api', function() { errors.push(err); } redis_client.keys("map_*|localhost", function(err, keys) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } var todrop = _.map(keys, function(m) { if ( m.match(/^map_(tpl|crt)|/) ) + { return m; + } }); if ( todrop.length ) { errors.push(new Error("Unexpected keys in redis: " + todrop)); redis_client.del(todrop, function(err) { - if ( err ) errors.push(err.message); + if ( err ) { + errors.push(err.message); + } if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } }); } else { if ( errors.length ) { done(new Error(errors)); } - else done(null); + else { + done(null); + } } }); } @@ -2111,7 +2246,9 @@ describe('template_api', function() { // 'map_style|null|publicuser|my_table', redis_client.keys("map_*", function(err, keys) { var todrop = _.map(keys, function(m) { - if ( m.match(/^map_(tpl|crt|sig)|/) ) return m; + if ( m.match(/^map_(tpl|crt|sig)|/) ) { + return m; + } }); redis_client.del(todrop, function() { redis_client.select(5, function() { diff --git a/test/support/sql/CDB_QueryTables.sql b/test/support/sql/CDB_QueryTables.sql index ac61281d..c7cfa64b 100644 --- a/test/support/sql/CDB_QueryTables.sql +++ b/test/support/sql/CDB_QueryTables.sql @@ -41,11 +41,11 @@ BEGIN xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x, xpath('//x:Relation-Name/../x:Schema/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as s ) - SELECT unnest(x) as p, unnest(s) as sc from inp + SELECT unnest(x)::text as p, unnest(s)::text as sc from inp LOOP -- RAISE DEBUG 'tab: %', rec2.p; -- RAISE DEBUG 'sc: %', rec2.sc; - tables := array_append(tables, (rec2.sc || '.' || rec2.p)); + tables := array_append(tables, format('%s.%s', quote_ident(rec2.sc), quote_ident(rec2.p))); END LOOP; -- RAISE DEBUG 'Tables: %', tables; diff --git a/test/support/test_helper.js b/test/support/test_helper.js index f7924dad..00351eee 100644 --- a/test/support/test_helper.js +++ b/test/support/test_helper.js @@ -34,6 +34,7 @@ function lzma_compress_to_base64(payload, mode, callback) { // Throws on failure function checkNoCache(res) { assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); + assert.ok(!res.headers.hasOwnProperty('surrogate-key')); assert.ok(!res.headers.hasOwnProperty('cache-control')); // is this correct ? assert.ok(!res.headers.hasOwnProperty('last-modified')); // is this correct ? } diff --git a/test/unit/cartodb/template_maps.test.js b/test/unit/cartodb/template_maps.test.js index f2203715..472d57b8 100644 --- a/test/unit/cartodb/template_maps.test.js +++ b/test/unit/cartodb/template_maps.test.js @@ -202,7 +202,7 @@ describe('template_maps', function() { tmap.addTemplate('me', tpl, this); }, function addOmonimousTemplate(err, id) { - if ( err ) throw err; + assert.ifError(err); tpl_id = id; assert.equal(tpl_id, 'first'); expected_failure = true; @@ -210,13 +210,15 @@ describe('template_maps', function() { tmap.addTemplate('me', tpl, this); }, function getTemplate(err) { - if ( ! expected_failure && err ) throw err; + if ( ! expected_failure && err ) { + throw err; + } assert.ok(err); assert.ok(err.message.match(/already exists/i), err); tmap.getTemplate('me', tpl_id, this); }, function delTemplate(err, got_tpl) { - if ( err ) throw err; + assert.ifError(err); assert.deepEqual(got_tpl, _.extend({}, tpl, {auth: {method: 'open'}, placeholders: {}})); tmap.delTemplate('me', tpl_id, this); }, @@ -238,31 +240,35 @@ describe('template_maps', function() { tmap.addTemplate('me', tpl1, this); }, function addTemplate2(err, id) { - if ( err ) throw err; + assert.ifError(err); tpl1_id = id; tmap.addTemplate('me', tpl2, this); }, function listTemplates(err, id) { - if ( err ) throw err; + assert.ifError(err); tpl2_id = id; tmap.listTemplates('me', this); }, function checkTemplates(err, ids) { - if ( err ) throw err; + assert.ifError(err); assert.equal(ids.length, 2); - assert.ok(ids.indexOf(tpl1_id) != -1, ids.join(',')); - assert.ok(ids.indexOf(tpl2_id) != -1, ids.join(',')); + assert.ok(ids.indexOf(tpl1_id) !== -1, ids.join(',')); + assert.ok(ids.indexOf(tpl2_id) !== -1, ids.join(',')); return null; }, function delTemplate1(err) { if ( tpl1_id ) { var next = this; tmap.delTemplate('me', tpl1_id, function(e) { - if ( err || e ) next(new Error(err + '; ' + e)); - else next(); + if ( err || e ) { + next(new Error(err + '; ' + e)); + } + else { + next(); + } }); } else { - if ( err ) throw err; + assert.ifError(err); return null; } }, @@ -270,11 +276,15 @@ describe('template_maps', function() { if ( tpl2_id ) { var next = this; tmap.delTemplate('me', tpl2_id, function(e) { - if ( err || e ) next(new Error(err + '; ' + e)); - else next(); + if ( err || e ) { + next(new Error(err + '; ' + e)); + } + else { + next(); + } }); } else { - if ( err ) throw err; + assert.ifError(err); return null; } }, @@ -301,14 +311,16 @@ describe('template_maps', function() { }, // Updating template name should fail function updateTemplateName(err, id) { - if ( err ) throw err; + assert.ifError(err); tpl_id = id; expected_failure = true; tpl.name = 'second'; tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateTemplateAuth(err) { - if ( err && ! expected_failure) throw err; + if ( err && ! expected_failure) { + throw err; + } expected_failure = false; assert.ok(err); tpl.name = 'first'; @@ -317,13 +329,15 @@ describe('template_maps', function() { tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateTemplateWithInvalid(err) { - if ( err ) throw err; + assert.ifError(err); tpl.version = '999.999.999'; expected_failure = true; tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateUnexistentTemplate(err) { - if ( err && ! expected_failure) throw err; + if ( err && ! expected_failure) { + throw err; + } assert.ok(err); assert.ok(err.message.match(/unsupported.*version/i), err); tpl.version = '0.0.1'; @@ -331,7 +345,9 @@ describe('template_maps', function() { tmap.updTemplate(owner, 'unexistent', tpl, this); }, function delTemplate(err) { - if ( err && ! expected_failure) throw err; + if ( err && ! expected_failure) { + throw err; + } expected_failure = false; assert.ok(err); assert.ok(err.message.match(/cannot update name/i), err); @@ -344,6 +360,7 @@ describe('template_maps', function() { }); it('instanciate templates', function() { + // jshint maxcomplexity:7 var tmap = new TemplateMaps(redis_pool); assert.ok(tmap); @@ -456,14 +473,14 @@ describe('template_maps', function() { tmap.addTemplate('me', tpl, this); }, function twoForMe(err, id) { - if ( err ) throw err; + assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'twoForMe'; tmap.addTemplate('me', tpl, this); }, function threeForMe(err, id) { - if ( err ) throw err; + assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'threeForMe'; @@ -471,37 +488,39 @@ describe('template_maps', function() { tmap.addTemplate('me', tpl, this); }, function errForMe(err/*, id*/) { - if ( err && ! expectErr ) throw err; + if ( err && ! expectErr ) { + throw err; + } expectErr = false; assert.ok(err); assert.ok(err.message.match(/limit.*template/), err); return null; }, function delOneMe(err) { - if ( err ) throw err; + assert.ifError(err); tmap.delTemplate('me', idMe.shift(), this); }, function threeForMeRetry(err) { - if ( err ) throw err; + assert.ifError(err); tpl.name = 'threeForMe'; tmap.addTemplate('me', tpl, this); }, function oneForYou(err, id) { - if ( err ) throw err; + assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'oneForYou'; tmap.addTemplate('you', tpl, this); }, function twoForYou(err, id) { - if ( err ) throw err; + assert.ifError(err); assert.ok(id); idYou.push(id); tpl.name = 'twoForYou'; tmap.addTemplate('you', tpl, this); }, function threeForYou(err, id) { - if ( err ) throw err; + assert.ifError(err); assert.ok(id); idYou.push(id); tpl.name = 'threeForYou'; @@ -509,7 +528,9 @@ describe('template_maps', function() { tmap.addTemplate('you', tpl, this); }, function errForYou(err/*, id*/) { - if ( err && ! expectErr ) throw err; + if ( err && ! expectErr ) { + throw err; + } expectErr = false; assert.ok(err); assert.ok(err.message.match(/limit.*template/), err);