Merge branch 'master' into 257-remove-old-api

Conflicts:
	lib/cartodb/cartodb_windshaft.js
	lib/cartodb/server_options.js
	package.json
This commit is contained in:
Raul Ochoa 2015-03-23 12:24:10 +01:00
commit 5f6185dd51
26 changed files with 602 additions and 355 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ logs/
pids/ pids/
redis.pid redis.pid
test.log test.log
npm-debug.log

92
.jshintrc Normal file
View File

@ -0,0 +1,92 @@
{
// // JSHint Default Configuration File (as on JSHint website)
// // See http://jshint.com/docs/ for more details
//
// "maxerr" : 50, // {int} Maximum error before stopping
//
// // 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
"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 () { } ());`
// "indent" : 4, // {int} Number of spaces to use for indentation
// "latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
// "noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
// "plusplus" : false, // true: Prohibit use of `++` & `--`
// "quotmark" : false, // Quotation mark consistency:
// // false : do nothing (default)
// // true : ensure whatever is used is consistent
// // "single" : require single quotes
// // "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
// "strict" : true, // true: Requires all functions run in ES5 Strict Mode
// "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
"maxlen" : 120, // {int} Max number of characters per line
//
// // Relaxing
// "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
// "boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
// "eqnull" : false, // true: Tolerate use of `== null`
// "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
// "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
// "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// // (ex: `for each`, multiple try/catch, function expression…)
// "evil" : false, // true: Tolerate use of `eval` and `new Function()`
// "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
// "funcscope" : false, // true: Tolerate defining variables inside control statements
// "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
// "iterator" : false, // true: Tolerate using the `__iterator__` property
// "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
// "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
// "laxcomma" : false, // true: Tolerate comma-first style coding
// "loopfunc" : false, // true: Tolerate functions being defined in loops
// "multistr" : false, // true: Tolerate multi-line strings
// "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
// "notypeof" : false, // true: Tolerate invalid typeof operator values
// "proto" : false, // true: Tolerate using the `__proto__` property
// "scripturl" : false, // true: Tolerate script-targeted URLs
// "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
// "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
// "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
// "validthis" : false, // true: Tolerate using this in a non-constructor function
//
// // Environments
// "browser" : true, // Web Browser (window, document, etc)
// "browserify" : false, // Browserify (node.js code in the browser)
// "couch" : false, // CouchDB
// "devel" : true, // Development/debugging (alert, confirm, etc)
// "dojo" : false, // Dojo Toolkit
// "jasmine" : false, // Jasmine
// "jquery" : false, // jQuery
// "mocha" : true, // Mocha
// "mootools" : false, // MooTools
"node" : true, // Node.js
// "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
// "prototypejs" : false, // Prototype and Scriptaculous
// "qunit" : false, // QUnit
// "rhino" : false, // Rhino
// "shelljs" : false, // ShellJS
// "worker" : false, // Web Workers
// "wsh" : false, // Windows Scripting Host
// "yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : { // additional predefined global variables
"suite": true,
"suiteSetup": true,
"test": true,
"suiteTeardown": true
}
}

View File

@ -1,7 +1,10 @@
srcdir=$(shell pwd) SHELL=/bin/bash
pre-install:
@$(SHELL) ./scripts/check-node-canvas.sh
all: all:
npm install @$(SHELL) ./scripts/install.sh
clean: clean:
rm -rf node_modules/* rm -rf node_modules/*
@ -15,24 +18,21 @@ config.status--test:
config/environments/test.js: config.status--test config/environments/test.js: config.status--test
./config.status--test ./config.status--test
check-local: config/environments/test.js test: config/environments/test.js
./run_tests.sh ${RUNTESTFLAGS} \ @echo "***tests***"
test/unit/cartodb/*.js \ @$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \
test/unit/cartodb/cache/model/*.js \ test/unit/cartodb/*.js \
test/integration/*.js \ test/unit/cartodb/cache/model/*.js \
test/acceptance/*.js \ test/integration/*.js \
test/acceptance/cache/*.js test/acceptance/*.js \
test/acceptance/cache/*.js
check-submodules: jshint:
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \ @echo "***jshint***"
for sub in windshaft grainstore node-varnish mapnik; do \ @./node_modules/.bin/jshint lib/
if test -e node_modules/$${sub}; then \
echo "Testing submodule $${sub}"; \
make -C node_modules/$${sub} check || exit 1; \
fi; \
done
check-full: check-local check-submodules test-all: jshint test
check: check-local check: test
.PHONY: pre-install test

16
NEWS.md
View File

@ -1,7 +1,21 @@
1.28.6 -- 2015-mm-dd 1.30.1 -- 2015-mm-dd
-------------------- --------------------
1.30.0 -- 2015-03-11
--------------------
Announcements:
- Upgrades windshaft to [0.40.0](https://github.com/CartoDB/Windshaft/releases/tag/0.40.0)
1.29.0 -- 2015-03-09
--------------------
Announcements:
- Upgrades windshaft to [0.39.0](https://github.com/CartoDB/Windshaft/releases/tag/0.39.0)
1.28.5 -- 2015-02-20 1.28.5 -- 2015-02-20
-------------------- --------------------

View File

@ -3,36 +3,36 @@ Windshaft-CartoDB
[![Build Status](https://travis-ci.org/CartoDB/Windshaft-cartodb.svg?branch=master)](https://travis-ci.org/CartoDB/Windshaft-cartodb) [![Build Status](https://travis-ci.org/CartoDB/Windshaft-cartodb.svg?branch=master)](https://travis-ci.org/CartoDB/Windshaft-cartodb)
This is the CartoDB map tiler. It extends Windshaft with some extra This is the [CartoDB Maps API](http://docs.cartodb.com/cartodb-platform/maps-api.html) tiler. It extends
functionality and custom filters for authentication [Windshaft](https://github.com/CartoDB/Windshaft) with some extra functionality and custom filters for authentication.
* reads dbname from subdomain and cartodb redis for pretty tile urls * reads dbname from subdomain and cartodb redis for pretty tile urls
* configures windshaft to publish ``cartodb_id`` as the interactivity layer * configures windshaft to publish `cartodb_id` as the interactivity layer
* gets the default geometry type from the cartodb redis store * gets the default geometry type from the cartodb redis store
* allows tiles to be styled individually * allows tiles to be styled individually
* provides a link to varnish high speed cache * provides a link to varnish high speed cache
* provides a ``infowindow`` endpoint for windshaft (DEPRECATED) * provides a ``infowindow`` endpoint for windshaft (DEPRECATED)
* provides a ``map_metadata`` endpoint for windshaft (DEPRECATED) * provides a ``map_metadata`` endpoint for windshaft (DEPRECATED)
* provides signed template maps API * provides a [template maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Template-maps.md)
(http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps)
Requirements Requirements
------------ ------------
- Core
- Node.js >=0.8
- npm >=1.2.1
- PostgreSQL >8.3.x, PostGIS >1.5.x
- Redis >2.4.0 (http://www.redis.io)
- Mapnik 2.0.1, 2.0.2, 2.1.0, 2.2.0, 2.3.0. See Installing Mapnik.
- Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies)
- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support
[core] - For cache control (optional)
- node-0.8.x+ - CartoDB-SQL-API 1.0.0+
- PostgreSQL-8.3+ - CartoDB 0.9.5+ (for `CDB_QueryTables`)
- PostGIS-1.5.0+ - Varnish (http://www.varnish-cache.org)
- Redis 2.4.0+ (http://www.redis.io)
- Mapnik 2.0 or 2.1
[for cache control] - For running the testsuite
- CartoDB-SQL-API 1.0.0+ - ImageMagick (http://www.imagemagick.org)
- CartoDB 0.9.5+ (for ``CDB_QueryTables``)
- Varnish (http://www.varnish-cache.org)
[for running the testsuite]
- Imagemagick (http://www.imagemagick.org)
Configure Configure
--------- ---------
@ -74,59 +74,16 @@ there may be out-of-sync records in there.
Take a look: http://redis.io/commands Take a look: http://redis.io/commands
URLs Documentation
---- -------------
**TILES** The [docs directory](https://github.com/CartoDB/Windshaft-cartodb/tree/master/docs) contains different documentation
resources, from higher level to more detailed ones:
[GET] subdomain.cartodb.com/tiles/:table_name/:z/:x/:y.[png|png8|grid.json] The [Maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md) defined the endpoints and their
expected parameters and outputs.
Args:
* sql - plain SQL arguments
* interactivity - specify the column to use in UTFGrid
* cache_buster - Specify an identifier for the internal tile cache.
Requesting tiles with the same cache_buster value may
result in being served a cached version of the tile
(even when requesting a tile for the first time, as tiles
can be prepared in advance)
* cache_policy - Set to "persist" to have the server send an Cache-Control
header requesting caching devices to keep the response
cached as much as possible. This is best used with a
timestamp value in cache_buster for manual control of
updates.
* geom_type - override the cartodb default
* style - override the default map style with Carto
**STYLE** Examples
--------
[GET/POST] subdomain.cartodb.com/tiles/:table_name/style [CartoDB's Map Gallery](http://cartodb.com/gallery/) showcases several examples of visualisations built on top of this.
Args:
* style - the style in CartoCSS you want to set
* style_version - the version of the style for POST
* style_convert - request conversion to target version (both POST and GET)
**INFOWINDOW**
[GET] subdomain.cartodb.com/tiles/:table_name/infowindow
Args:
* infowindow - returns contents of infowindow from CartoDB.
**MAP METADATA**
[GET] subdomain.cartodb.com/tiles/:table_name/map_metadata
Args:
* infowindow - returns contents of infowindow from CartoDB.
All GET requests are wrappable with JSONP using callback argument,
including the UTFGrid map tile call.

View File

@ -14,11 +14,11 @@ var config = {
// Base url for the Templated Maps API // Base url for the Templated Maps API
// "/api/v1/map/named" is the new API, // "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x // "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)' ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)'
// Base url for the Detached Maps API // Base url for the Detached Maps API
// "maps" is the the new API, // "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x // "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)' ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API // Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table' ,base_url_legacy: '/tiles/:table'
@ -174,6 +174,12 @@ var config = {
x: 0, x: 0,
y: 0 y: 0
} }
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
}; };
module.exports = config; module.exports = config;

View File

@ -14,11 +14,11 @@ var config = {
// Base url for the Templated Maps API // Base url for the Templated Maps API
// "/api/v1/map/named" is the new API, // "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x // "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)' ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)'
// Base url for the Detached Maps API // Base url for the Detached Maps API
// "maps" is the the new API, // "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x // "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)' ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API // Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table' ,base_url_legacy: '/tiles/:table'
@ -183,6 +183,12 @@ var config = {
x: 0, x: 0,
y: 0 y: 0
} }
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
}; };
module.exports = config; module.exports = config;

View File

@ -14,11 +14,11 @@ var config = {
// Base url for the Templated Maps API // Base url for the Templated Maps API
// "/api/v1/maps/named" is the new API, // "/api/v1/maps/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x // "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/maps/named|/tiles/template)' ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)'
// Base url for the Detached Maps API // Base url for the Detached Maps API
// "/api/v1/maps" is the the new API, // "/api/v1/maps" is the the new API,
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x // "/tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/maps|/tiles/layergroup)' ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API // Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table' ,base_url_legacy: '/tiles/:table'
@ -183,6 +183,12 @@ var config = {
x: 0, x: 0,
y: 0 y: 0
} }
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
}; };
module.exports = config; module.exports = config;

View File

@ -14,11 +14,11 @@ var config = {
// Base url for the Templated Maps API // Base url for the Templated Maps API
// "/api/v1/map/named" is the new API, // "/api/v1/map/named" is the new API,
// "/tiles/template" is for compatibility with versions up to 1.6.x // "/tiles/template" is for compatibility with versions up to 1.6.x
,base_url_templated: '(?:/api/v1/map/named|/tiles/template)' ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)'
// Base url for the Detached Maps API // Base url for the Detached Maps API
// "maps" is the the new API, // "maps" is the the new API,
// "tiles/layergroup" is for compatibility with versions up to 1.6.x // "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)' ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)'
// Base url for the Inline Maps and Table Maps API // Base url for the Inline Maps and Table Maps API
,base_url_legacy: '/tiles/:table' ,base_url_legacy: '/tiles/:table'
@ -172,6 +172,12 @@ var config = {
x: 0, x: 0,
y: 0 y: 0
} }
// Use this as a feature flags enabling/disabling mechanism
,enabledFeatures: {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true
}
}; };
module.exports = config; module.exports = config;

View File

@ -34,10 +34,10 @@ $.ajax({
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
url: 'http://documentation.cartodb.com/api/v1/map', url: 'https://documentation.cartodb.com/api/v1/map',
data: JSON.stringify(mapconfig), data: JSON.stringify(mapconfig),
success: function(data) { success: function(data) {
var templateUrl = 'http://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png' var templateUrl = 'https://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
console.log(templateUrl); console.log(templateUrl);
} }
}) })
@ -79,7 +79,7 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
<div class="code-title notitle code-request"></div> <div class="code-title notitle code-request"></div>
```bash ```bash
curl -X POST 'http://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json' curl -X POST 'https://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
``` ```
The response will return JSON with properties for the `layergroupid` and the timestamp (`last_updated`) of the last data modification. The response will return JSON with properties for the `layergroupid` and the timestamp (`last_updated`) of the last data modification.
@ -96,7 +96,7 @@ Here is an example response:
You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template: You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template:
```bash ```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
``` ```
## General Concepts ## General Concepts
@ -107,7 +107,7 @@ The following concepts are the same for every endpoint in the API except when it
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map). By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map).
To execute an authorized request, api_key=YOURAPIKEY should be added to the request URL. The param can be also passed as POST param. We **strongly advise** using HTTPS when you are performing requests that include your `api_key`. To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`.
### Errors ### Errors
@ -167,7 +167,7 @@ The response includes:
The ID for that map, used to compose the URL for the tiles. The final URL is: The ID for that map, used to compose the URL for the tiles. The final URL is:
```html ```html
http://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png https://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png
``` ```
- **updated_at** - **updated_at**
@ -183,7 +183,7 @@ The response includes:
<div class="code-title code-request with-result">REQUEST</div> <div class="code-title code-request with-result">REQUEST</div>
```bash ```bash
curl 'http://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
``` ```
<div class="code-title">RESPONSE</div> <div class="code-title">RESPONSE</div>
@ -201,19 +201,19 @@ curl 'http://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application
The tiles can be accessed using: The tiles can be accessed using:
```bash ```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png
``` ```
For UTF grid tiles: For UTF grid tiles:
```bash ```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json
``` ```
For attributes defined in `attributes` section: For attributes defined in `attributes` section:
```bash ```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
``` ```
Which returns JSON with the attributes defined, like: Which returns JSON with the attributes defined, like:

128
docs/Routes.md Normal file
View File

@ -0,0 +1,128 @@
This document list all routes available in Windshaft-cartodb Maps API server.
## Routes list
1. `GET (?:/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:token(f),:z(f),:x(f),:y(f),:scale_factor(t),:format(f)} (1)`
<br/>Notes: Mapnik retina tiles [0]
1. `GET (?:/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:token(f),:z(f),:x(f),:y(f),:format(f)} (1)`
<br/>Notes: Mapnik tiles [0]
1. `GET (?:/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:token(f),:layer(f),:z(f),:x(f),:y(f),:format(f)} (1)`
<br/>Notes: Per :layer rendering based on :format [0]
1. `GET (?:/api/v1/map|/tiles/layergroup) {} (1)`
<br/>Notes: Map instantiation [0]
1. `GET (?:/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:token(f),:layer(f),:fid(f)} (1)`
<br/>Notes: Endpoint for info windows data, alternative for sql api when tables are private [0]
1. `GET (?:/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:token(f),:z(f),:lat(f),:lng(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static Maps API [0]
1. `GET (?:/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:token(f),:west(f),:south(f),:east(f),:north(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static Maps API [0]
1. `GET / {} (1)`
<br/>Notes: Welcome message
1. `GET /version {} (1)`
<br/>Notes: Return relevant module versions: mapnik, grainstore, etc
1. `GET /tiles/:table/:z/:x/:y.* {:table(f),:z(f),:x(f),:y(f)} (1)`
<br/>Notes: **[DEPRECATED]** Per :table tiles rendering
1. `GET /tiles/:table/style {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** Style for :table
1. `GET (?:/api/v1/map/named|/tiles/template)/:template_id/jsonp {:template_id(f)} (1)`
<br/>Notes: Named maps JSONP instantiation [1]
1. `GET (?:/api/v1/map/named|/tiles/template)/:template_id {:template_id(f)} (1)`
<br/>Notes: Named map retrieval (w/ API KEY) [1]
1. `GET (?:/api/v1/map/named|/tiles/template) {} (1)`
<br/>Notes: List named maps (w/ API KEY) [1]
1. `GET /tiles/:table/infowindow {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** retrieve info window template for :table
1. `GET /tiles/:table/map_metadata {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** retrieve map metadata for :table
1. `GET /health {} (1)`
<br/>Notes: Healt check
1. `OPTIONS (?:/api/v1/map|/tiles/layergroup) {} (1)`
<br/>Notes: CORS [0]
1. `OPTIONS /tiles/:table/:z/:x/:y.* {:table(f),:z(f),:x(f),:y(f)} (1)`
<br/>Notes: **[DEPRECATED]** CORS
1. `OPTIONS /tiles/:table/style {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** CORS
1. `OPTIONS (?:/api/v1/map/named|/tiles/template)/:template_id {:template_id(f)} (1)`
<br/>Notes: CORS [1]
1. `POST (?:/api/v1/map|/tiles/layergroup) {} (1)`
<br/>Notes: Map instantiation [0]
1. `POST /tiles/:table/style {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** Create style for :table
1. `POST (?:/api/v1/map/named|/tiles/template) {} (1)`
<br/>Notes: Create named map (w/ API KEY) [1]
1. `POST (?:/api/v1/map/named|/tiles/template)/:template_id {:template_id(f)} (1)`
<br/>Notes: Instantiate named map [1]
1. `DELETE /tiles/:table/style {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** Delete :table style
1. `DELETE (?:/api/v1/map/named|/tiles/template)/:template_id {:template_id(f)} (1)`
<br/>Notes: Delete named map (w/ API KEY) [1]
1. `DELETE /tiles/:table/flush_cache {:table(f)} (1)`
<br/>Notes: **[DEPRECATED]** Flush internal caches for :table
1. `PUT (?:/api/v1/map/named|/tiles/template)/:template_id {:template_id(f)} (1)`
<br/>Notes: Update a named map (w/ API KEY) [1]
## Optional deprecated routes
- [0] `/tiles/layergroup` is deprecated and `/api/v1/map` should be used but we keep it for now.
- [1] `/tiles/template` is deprecated and `/api/v1/map/named` should be used but we keep it for now.
## How to generate the list of routes
Something like the following patch should do the trick
```javascript
diff --git a/lib/cartodb/cartodb_windshaft.js b/lib/cartodb/cartodb_windshaft.js
index 477a4c2..f69eebb 100644
--- a/lib/cartodb/cartodb_windshaft.js
+++ b/lib/cartodb/cartodb_windshaft.js
@@ -242,6 +242,20 @@ var CartodbWindshaft = function(serverOptions) {
}
});
+ var format = require('util').format;
+ var routesNotes = Object.keys(ws.routes.routes)
+ .map(function(method) { return ws.routes.routes[method]; })
+ .reduce(function(previous, current) { current.map(function(r) { previous.push(r) }); return previous;}, [])
+ .map(function(route) {
+ return format("\n1. `%s %s {%s} (%d)`\n<br/>Notes: [DEPRECATED]? ",
+ route.method.toUpperCase(),
+ route.path,
+ route.keys.map(function(k) { return format(':%s(%s)', k.name, k.optional ? 't' : 'f'); } ).join(','),
+ route.callbacks.length
+ );
+ });
+ console.log(routesNotes.join('\n'));
+
return ws;
};
```

View File

@ -1,6 +1,6 @@
var sqlApi = require('../sql/sql_api'); var sqlApi = require('../sql/sql_api');
var PSQL = require('cartodb-psql'); var PSQL = require('cartodb-psql');
var Step = require('step'); var step = require('step');
function QueryTablesApi(pgConnection, metadataBackend) { function QueryTablesApi(pgConnection, metadataBackend) {
this.pgConnection = pgConnection; this.pgConnection = pgConnection;
@ -78,7 +78,7 @@ QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, call
var params = {}; var params = {};
Step( step(
function setAuth() { function setAuth() {
self.pgConnection.setDBAuth(username, params, this); self.pgConnection.setDBAuth(username, params, this);
}, },
@ -109,7 +109,7 @@ QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, call
} else { } else {
Step( step(
function getApiKey() { function getApiKey() {
self.metadataBackend.getUserMapKey(username, this); self.metadataBackend.getUserMapKey(username, this);
}, },
@ -138,7 +138,7 @@ function prepareSql(sql) {
function shouldQueryPostgresDirectly() { function shouldQueryPostgresDirectly() {
return global.environment return global.environment &&
&& global.environment.enabledFeatures global.environment.enabledFeatures &&
&& global.environment.enabledFeatures.cdbQueryTablesFromPostgres; global.environment.enabledFeatures.cdbQueryTablesFromPostgres;
} }

View File

@ -1,4 +1,4 @@
var Step = require('step'); var step = require('step');
var _ = require('underscore'); var _ = require('underscore');
function PgConnection(metadataBackend) { function PgConnection(metadataBackend) {
@ -24,13 +24,13 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) {
var user_params = {}; var user_params = {};
var auth_user = global.environment.postgres_auth_user; var auth_user = global.environment.postgres_auth_user;
var auth_pass = global.environment.postgres_auth_pass; var auth_pass = global.environment.postgres_auth_pass;
Step( step(
function getId() { function getId() {
self.metadataBackend.getUserId(username, this); self.metadataBackend.getUserId(username, this);
}, },
function(err, user_id) { function(err, user_id) {
if (err) throw err; if (err) throw err;
user_params['user_id'] = user_id; user_params.user_id = user_id;
var dbuser = _.template(auth_user, user_params); var dbuser = _.template(auth_user, user_params);
_.extend(params, {dbuser:dbuser}); _.extend(params, {dbuser:dbuser});
@ -42,7 +42,7 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) {
}, },
function(err, user_password) { function(err, user_password) {
if (err) throw err; if (err) throw err;
user_params['user_password'] = user_password; user_params.user_password = user_password;
if ( auth_pass ) { if ( auth_pass ) {
var dbpass = _.template(auth_pass, user_params); var dbpass = _.template(auth_pass, user_params);
_.extend(params, {dbpassword:dbpass}); _.extend(params, {dbpassword:dbpass});
@ -76,7 +76,7 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
dbhost: global.environment.postgres.host, dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port dbport: global.environment.postgres.port
}); });
Step( step(
function getConnectionParams() { function getConnectionParams() {
self.metadataBackend.getUserDBConnectionParams(dbowner, this); self.metadataBackend.getUserDBConnectionParams(dbowner, this);
}, },

View File

@ -1,6 +1,5 @@
var _ = require('underscore'), var Varnish = require('node-varnish');
Varnish = require('node-varnish'), var varnish_queue = null;
varnish_queue = null;
function init(host, port, secret) { function init(host, port, secret) {
varnish_queue = new Varnish.VarnishQueue(host, port, secret); varnish_queue = new Varnish.VarnishQueue(host, port, secret);
@ -23,4 +22,4 @@ function invalidate_db(dbname, table) {
module.exports = { module.exports = {
init: init, init: init,
invalidate_db: invalidate_db invalidate_db: invalidate_db
} };

View File

@ -1,12 +1,11 @@
var _ = require('underscore'); var _ = require('underscore');
var Step = require('step'); var step = require('step');
var Windshaft = require('windshaft'); var Windshaft = require('windshaft');
var Cache = require('./cache_validator');
var os = require('os'); var os = require('os');
var HealthCheck = require('./monitoring/health_check'); var HealthCheck = require('./monitoring/health_check');
if ( ! process.env['PGAPPNAME'] ) if ( ! process.env.PGAPPNAME )
process.env['PGAPPNAME']='cartodb_tiler'; process.env.PGAPPNAME='cartodb_tiler';
var CartodbWindshaft = function(serverOptions) { var CartodbWindshaft = function(serverOptions) {
// Perform keyword substitution in statsd // Perform keyword substitution in statsd
@ -18,8 +17,8 @@ var CartodbWindshaft = function(serverOptions) {
} }
} }
var redisPool = serverOptions.redis.pool var redisPool = serverOptions.redis.pool ||
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'})); require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
var cartoData = require('cartodb-redis')({pool: redisPool}); var cartoData = require('cartodb-redis')({pool: redisPool});
@ -34,18 +33,21 @@ var CartodbWindshaft = function(serverOptions) {
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'), var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'), NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
VarnishHttpCacheBackend = require('./cache/backend/varnish_http'), VarnishHttpCacheBackend = require('./cache/backend/varnish_http'),
varnishHttpCacheBackend = new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port), varnishHttpCacheBackend = new VarnishHttpCacheBackend(
serverOptions.varnish_host,
serverOptions.varnish_http_port
),
surrogateKeysCache = new SurrogateKeysCache(varnishHttpCacheBackend); surrogateKeysCache = new SurrogateKeysCache(varnishHttpCacheBackend);
if (serverOptions.varnish_purge_enabled) { function invalidateNamedMap (owner, templateName) {
function invalidateNamedMap(owner, templateName) { surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) { if (err) {
if (err) { console.warn('Cache: surrogate key invalidation failed');
console.warn('Cache: surrogate key invalidation failed'); }
} });
}); }
}
if (serverOptions.varnish_purge_enabled) {
['update', 'delete'].forEach(function(eventType) { ['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap); templateMaps.on(eventType, invalidateNamedMap);
}); });
@ -85,7 +87,7 @@ var CartodbWindshaft = function(serverOptions) {
} }
} }
var req = res.req; var req = res.req;
Step ( step (
function addCacheChannel() { function addCacheChannel() {
if ( ! req ) { if ( ! req ) {
// having no associated request can happen when // having no associated request can happen when
@ -107,10 +109,11 @@ var CartodbWindshaft = function(serverOptions) {
//console.log("Skipping cache channel in route:\n" + req.route.path); //console.log("Skipping cache channel in route:\n" + req.route.path);
return false; return false;
} }
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" + mapCreateRoutes.join("\n")); //console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" +
// mapCreateRoutes.join("\n"));
serverOptions.addCacheChannel(that, req, this); serverOptions.addCacheChannel(that, req, this);
}, },
function sendResponse(err, added) { function sendResponse(err/*, added*/) {
if ( err ) console.log(err + err.stack); if ( err ) console.log(err + err.stack);
ws_sendResponse.apply(that, thatArgs); ws_sendResponse.apply(that, thatArgs);
return null; return null;

View File

@ -1,4 +1,4 @@
var Step = require('step'); var step = require('step');
var _ = require('underscore'); var _ = require('underscore');
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache, function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
@ -35,7 +35,7 @@ TemplateMapsController.prototype.create = function(req, res) {
var cdbuser = self.serverOptions.userByReq(req); var cdbuser = self.serverOptions.userByReq(req);
Step( step(
function checkPerms(){ function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this); self.serverOptions.authorizedByAPIKey(req, this);
}, },
@ -83,7 +83,7 @@ TemplateMapsController.prototype.update = function(req, res) {
var cdbuser = this.serverOptions.userByReq(req); var cdbuser = this.serverOptions.userByReq(req);
var template; var template;
var tpl_id; var tpl_id;
Step( step(
function checkPerms(){ function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this); self.serverOptions.authorizedByAPIKey(req, this);
}, },
@ -100,8 +100,7 @@ TemplateMapsController.prototype.update = function(req, res) {
tpl_id = req.params.template_id.split('@'); tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) { if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) { if ( tpl_id[0] != cdbuser ) {
err = new Error("Invalid template id '" err = new Error("Invalid template id '" + req.params.template_id + "' for user '" + cdbuser + "'");
+ req.params.template_id + "' for user '" + cdbuser + "'");
err.http_status = 404; err.http_status = 404;
throw err; throw err;
} }
@ -142,9 +141,8 @@ TemplateMapsController.prototype.retrieve = function(req, res) {
this.app.doCORS(res); this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req); var cdbuser = this.serverOptions.userByReq(req);
var template;
var tpl_id; var tpl_id;
Step( step(
function checkPerms(){ function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this); self.serverOptions.authorizedByAPIKey(req, this);
}, },
@ -158,10 +156,10 @@ TemplateMapsController.prototype.retrieve = function(req, res) {
tpl_id = req.params.template_id.split('@'); tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) { if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) { if ( tpl_id[0] != cdbuser ) {
var err = new Error("Cannot get template id '" var templateNotFoundErr = new Error("Cannot get template id '" + req.params.template_id +
+ req.params.template_id + "' for user '" + cdbuser + "'"); "' for user '" + cdbuser + "'");
err.http_status = 404; templateNotFoundErr.http_status = 404;
throw err; throw templateNotFoundErr;
} }
tpl_id = tpl_id[1]; tpl_id = tpl_id[1];
} }
@ -204,9 +202,8 @@ TemplateMapsController.prototype.destroy = function(req, res) {
this.app.doCORS(res); this.app.doCORS(res);
var cdbuser = this.serverOptions.userByReq(req); var cdbuser = this.serverOptions.userByReq(req);
var template;
var tpl_id; var tpl_id;
Step( step(
function checkPerms(){ function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this); self.serverOptions.authorizedByAPIKey(req, this);
}, },
@ -220,16 +217,16 @@ TemplateMapsController.prototype.destroy = function(req, res) {
tpl_id = req.params.template_id.split('@'); tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) { if ( tpl_id.length > 1 ) {
if ( tpl_id[0] != cdbuser ) { if ( tpl_id[0] != cdbuser ) {
var err = new Error("Cannot find template id '" var templateNotFoundErr = new Error("Cannot find template id '" + req.params.template_id +
+ req.params.template_id + "' for user '" + cdbuser + "'"); "' for user '" + cdbuser + "'");
err.http_status = 404; templateNotFoundErr.http_status = 404;
throw err; throw templateNotFoundErr;
} }
tpl_id = tpl_id[1]; tpl_id = tpl_id[1];
} }
self.templateMaps.delTemplate(cdbuser, tpl_id, this); self.templateMaps.delTemplate(cdbuser, tpl_id, this);
}, },
function prepareResponse(err, tpl_val){ function prepareResponse(err/*, tpl_val*/){
if ( err ) throw err; if ( err ) throw err;
return { status: 'ok' }; return { status: 'ok' };
}, },
@ -259,7 +256,7 @@ TemplateMapsController.prototype.list = function(req, res) {
var cdbuser = this.serverOptions.userByReq(req); var cdbuser = this.serverOptions.userByReq(req);
Step( step(
function checkPerms(){ function checkPerms(){
self.serverOptions.authorizedByAPIKey(req, this); self.serverOptions.authorizedByAPIKey(req, this);
}, },
@ -299,7 +296,7 @@ TemplateMapsController.prototype.instantiate = function(req, res) {
if ( req.profiler && req.profiler.statsd_client) { if ( req.profiler && req.profiler.statsd_client) {
req.profiler.start('windshaft-cartodb.instance_template_post'); req.profiler.start('windshaft-cartodb.instance_template_post');
} }
Step( step(
function() { function() {
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') { if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') {
throw new Error('template POST data must be of type application/json, it is instead '); throw new Error('template POST data must be of type application/json, it is instead ');
@ -311,7 +308,7 @@ TemplateMapsController.prototype.instantiate = function(req, res) {
); );
}; };
TemplateMapsController.prototype.options = function(req, res) { TemplateMapsController.prototype.options = function(req, res, next) {
this.app.doCORS(res, "Content-Type"); this.app.doCORS(res, "Content-Type");
return next(); return next();
}; };
@ -326,7 +323,7 @@ TemplateMapsController.prototype.jsonp = function(req, res) {
if ( req.profiler && req.profiler.statsd_client) { if ( req.profiler && req.profiler.statsd_client) {
req.profiler.start('windshaft-cartodb.instance_template_get'); req.profiler.start('windshaft-cartodb.instance_template_get');
} }
Step( step(
function() { function() {
if ( req.query.callback === undefined || req.query.callback.length === 0) { if ( req.query.callback === undefined || req.query.callback.length === 0) {
throw new Error('callback parameter should be present and be a function name'); throw new Error('callback parameter should be present and be a function name');
@ -361,9 +358,8 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
var tpl_id = req.params.template_id.split('@'); var tpl_id = req.params.template_id.split('@');
if ( tpl_id.length > 1 ) { if ( tpl_id.length > 1 ) {
if ( tpl_id[0] && tpl_id[0] != cdbuser ) { if ( tpl_id[0] && tpl_id[0] != cdbuser ) {
var err = new Error('Cannot instanciate map of user "' var err = new Error('Cannot instanciate map of user "' + tpl_id[0] + '" on database of user "' + cdbuser +
+ tpl_id[0] + '" on database of user "' '"');
+ cdbuser + '"');
err.http_status = 403; err.http_status = 403;
callback(err); callback(err);
return; return;
@ -371,7 +367,7 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
tpl_id = tpl_id[1]; tpl_id = tpl_id[1];
} }
var auth_token = req.query.auth_token; var auth_token = req.query.auth_token;
Step( step(
function getTemplate(){ function getTemplate(){
self.templateMaps.getTemplate(cdbuser, tpl_id, this); self.templateMaps.getTemplate(cdbuser, tpl_id, this);
}, },
@ -464,7 +460,7 @@ TemplateMapsController.prototype.finish_instantiation = function(err, response,
TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callback) { TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callback) {
var self = this; var self = this;
Step( step(
function setAuth() { function setAuth() {
self.pgConnection.setDBAuth(cdbuser, params, this); self.pgConnection.setDBAuth(cdbuser, params, this);
}, },

View File

@ -1,8 +1,7 @@
var _ = require('underscore'), var dot = require('dot');
dot = require('dot'), var fs = require('fs');
fs = require('fs'), var path = require('path');
path = require('path'), var step = require('step');
Step = require('step');
function HealthCheck(metadataBackend, tilelive) { function HealthCheck(metadataBackend, tilelive) {
this.metadataBackend = metadataBackend; this.metadataBackend = metadataBackend;
@ -40,9 +39,9 @@ HealthCheck.prototype.check = function(config, callback) {
ok: false ok: false
} }
}; };
mapnikXmlParams = config; var mapnikXmlParams = config;
Step( step(
function getDBParams() { function getDBParams() {
startTime = Date.now(); startTime = Date.now();
self.metadataBackend.getAllUserDBParams(config.username, this); self.metadataBackend.getAllUserDBParams(config.username, this);

View File

@ -1,5 +1,5 @@
var _ = require('underscore'); var _ = require('underscore');
var Step = require('step'); var step = require('step');
var QueryTablesApi = require('./api/query_tables_api'); var QueryTablesApi = require('./api/query_tables_api');
var PgConnection = require('./backends/pg_connection'); var PgConnection = require('./backends/pg_connection');
var crypto = require('crypto'); var crypto = require('crypto');
@ -18,6 +18,7 @@ if ( _.isUndefined(global.environment.sqlapi.domain) ) {
// Whitelist query parameters and attach format // Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [ var REQUEST_QUERY_PARAMS_WHITELIST = [
'user',
'sql', 'sql',
'geom_type', 'geom_type',
'cache_buster', 'cache_buster',
@ -35,8 +36,8 @@ var REQUEST_QUERY_PARAMS_WHITELIST = [
]; ];
module.exports = function(redisPool) { module.exports = function(redisPool) {
redisPool = redisPool redisPool = redisPool ||
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'})); require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
var cartoData = require('cartodb-redis')({ pool: redisPool }), var cartoData = require('cartodb-redis')({ pool: redisPool }),
lzmaWorker = new LZMA(), lzmaWorker = new LZMA(),
@ -147,7 +148,7 @@ module.exports = function(redisPool) {
var that = this; var that = this;
Step ( step (
function checkCached() { function checkCached() {
if ( me.channelCache.hasOwnProperty(cacheKey) ) { if ( me.channelCache.hasOwnProperty(cacheKey) ) {
callback(null, me.channelCache[cacheKey]); callback(null, me.channelCache[cacheKey]);
@ -167,7 +168,7 @@ module.exports = function(redisPool) {
} }
var next = this; var next = this;
var mapStore = app.mapStore; var mapStore = app.mapStore;
Step( step(
function loadFromStore() { function loadFromStore() {
mapStore.load(req.params.token, this); mapStore.load(req.params.token, this);
}, },
@ -196,7 +197,7 @@ module.exports = function(redisPool) {
// strip out windshaft/mapnik inserted sql if present // strip out windshaft/mapnik inserted sql if present
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/); var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
sql = (sql != null) ? sql[1] : req.params.sql; sql = (sql !== null) ? sql[1] : req.params.sql;
return sql; return sql;
}, },
@ -281,14 +282,16 @@ module.exports = function(redisPool) {
}; };
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) { me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection, function(err, layers, datasource) { mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection,
if (err) { function(err, layers, datasource) {
return callback(err); if (err) {
} return callback(err);
}
requestMapConfig.layers = layers; requestMapConfig.layers = layers;
return callback(null, requestMapConfig, datasource) return callback(null, requestMapConfig, datasource);
}); }
);
}; };
me.afterLayergroupCreate = function(req, mapconfig, response, callback) { me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
@ -333,7 +336,7 @@ module.exports = function(redisPool) {
var dbName = req.params.dbname; var dbName = req.params.dbname;
var cacheKey = dbName + ':' + token; var cacheKey = dbName + ':' + token;
Step( step(
function getAffectedTablesAndLastUpdatedTime() { function getAffectedTablesAndLastUpdatedTime() {
queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this); queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
}, },
@ -375,16 +378,18 @@ module.exports = function(redisPool) {
me.userByReq = function(req) { me.userByReq = function(req) {
var host = req.headers.host; var host = req.headers.host;
if (req.params.user) {
return req.params.user;
}
var mat = host.match(this.re_userFromHost); var mat = host.match(this.re_userFromHost);
if ( ! mat ) { if ( ! mat ) {
console.error("ERROR: user pattern '" + this.re_userFromHost console.error("ERROR: user pattern '" + this.re_userFromHost + "' does not match hostname '" + host + "'");
+ "' does not match hostname '" + host + "'");
return; return;
} }
// console.log("Matches: "); console.dir(mat); // console.log("Matches: "); console.dir(mat);
if ( ! mat.length === 2 ) { if ( mat.length !== 2 ) {
console.error("ERROR: pattern '" + this.re_userFromHost console.error("ERROR: pattern '" + this.re_userFromHost + "' gave unexpected matches against '" + host + "': " +
+ "' gave unexpected matches against '" + host + "': " + mat); mat);
return; return;
} }
return mat[1]; return mat[1];
@ -409,7 +414,8 @@ module.exports = function(redisPool) {
var layergroup_id = req.params.token; var layergroup_id = req.params.token;
var auth_token = req.params.auth_token; var auth_token = req.params.auth_token;
//console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + " with auth_token " + auth_token); //console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id +
// " with auth_token " + auth_token);
var mapStore = req.app.mapStore; var mapStore = req.app.mapStore;
if (!mapStore) { if (!mapStore) {
throw new Error('Unable to retrieve map configuration token'); throw new Error('Unable to retrieve map configuration token');
@ -445,7 +451,7 @@ module.exports = function(redisPool) {
} }
//console.log("given ApiKey: " + givenKey); //console.log("given ApiKey: " + givenKey);
var user = me.userByReq(req); var user = me.userByReq(req);
Step( step(
function (){ function (){
cartoData.getUserMapKey(user, this); cartoData.getUserMapKey(user, this);
}, },
@ -469,7 +475,7 @@ module.exports = function(redisPool) {
var that = this; var that = this;
var user = me.userByReq(req); var user = me.userByReq(req);
Step( step(
function (){ function (){
that.authorizedByAPIKey(req, this); that.authorizedByAPIKey(req, this);
}, },
@ -545,6 +551,7 @@ module.exports = function(redisPool) {
); );
}; };
// jshint maxcomplexity:10
/** /**
* Whitelist input and get database name & default geometry type from * Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis * subdomain/user metadata held in CartoDB Redis
@ -559,7 +566,12 @@ module.exports = function(redisPool) {
//console.log("type of req.query.lzma is " + typeof(req.query.lzma)); //console.log("type of req.query.lzma is " + typeof(req.query.lzma));
// Decode (from base64) // Decode (from base64)
var lzma = (new Buffer(req.query.lzma, 'base64').toString('binary')).split('').map(function(c) { return c.charCodeAt(0) - 128 }); var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress // Decompress
lzmaWorker.decompress( lzmaWorker.decompress(
@ -574,7 +586,7 @@ module.exports = function(redisPool) {
callback(new Error('Error parsing lzma as JSON: ' + err)); callback(new Error('Error parsing lzma as JSON: ' + err));
} }
}, },
function(percent) { // progress function(/*percent*/) { // progress
//console.log("LZMA decompression " + percent + "%"); //console.log("LZMA decompression " + percent + "%");
} }
); );
@ -597,14 +609,15 @@ module.exports = function(redisPool) {
if ( tksplit.length > 1 ) { if ( tksplit.length > 1 ) {
req.params.signer = tksplit.shift(); req.params.signer = tksplit.shift();
if ( ! req.params.signer ) req.params.signer = user; if ( ! req.params.signer ) req.params.signer = user;
else if ( req.params.signer != user ) { else if ( req.params.signer !== user ) {
var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' + user + '"'); var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' +
user + '"');
err.http_status = 403; err.http_status = 403;
callback(err); callback(err);
return; return;
} }
if ( tksplit.length > 1 ) { if ( tksplit.length > 1 ) {
var template_hash = tksplit.shift(); // unused /*var template_hash = */tksplit.shift(); // unused
} }
req.params.token = tksplit.shift(); req.params.token = tksplit.shift();
//console.log("Request for token " + req.params.token + " with signature from " + req.params.signer); //console.log("Request for token " + req.params.token + " with signature from " + req.params.signer);
@ -617,11 +630,9 @@ module.exports = function(redisPool) {
// for cartodb, ensure interactivity is cartodb_id or user specified // for cartodb, ensure interactivity is cartodb_id or user specified
req.params.interactivity = req.params.interactivity || 'cartodb_id'; req.params.interactivity = req.params.interactivity || 'cartodb_id';
var that = this;
if (req.profiler) req.profiler.done('req2params.setup'); if (req.profiler) req.profiler.done('req2params.setup');
Step( step(
function getPrivacy(){ function getPrivacy(){
me.authorize(req, this); me.authorize(req, this);
}, },

View File

@ -16,7 +16,7 @@ module.exports.query = function (username, api_key, sql, callback) {
var qs = { q: sql }; var qs = { q: sql };
// add api_key if given // add api_key if given
if (_.isString(api_key) && api_key != '') { qs.api_key = api_key; } if (_.isString(api_key) && api_key !== '') { qs.api_key = api_key; }
// call sql api // call sql api
// //
@ -36,11 +36,11 @@ module.exports.query = function (username, api_key, sql, callback) {
var reqSpec = { var reqSpec = {
url:sqlapi, url:sqlapi,
json:true, json:true,
headers:{host: sqlapihostname} headers:{host: sqlapihostname},
// http://nodejs.org/api/http.html#http_agent_maxsockets // http://nodejs.org/api/http.html#http_agent_maxsockets
,pool:{maxSockets:maxSockets} pool:{maxSockets:maxSockets},
// timeout in milliseconds // timeout in milliseconds
,timeout:maxSQLTime timeout:maxSQLTime
}; };
if ( sql.length > maxGetLen ) { if ( sql.length > maxGetLen ) {
reqSpec.method = 'POST'; reqSpec.method = 'POST';

View File

@ -1,7 +1,7 @@
var crypto = require('crypto'), var crypto = require('crypto');
Step = require('step'), var step = require('step');
_ = require('underscore'), var _ = require('underscore');
dot = require('dot'); var dot = require('dot');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
@ -56,7 +56,7 @@ var o = TemplateMaps.prototype;
//--------------- PRIVATE METHODS -------------------------------- //--------------- PRIVATE METHODS --------------------------------
o._userTemplateLimit = function() { o._userTemplateLimit = function() {
return this.opts['max_user_templates'] || 0; return this.opts.max_user_templates || 0;
}; };
/** /**
@ -71,7 +71,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
var that = this; var that = this;
var db = that.db_signatures; var db = that.db_signatures;
Step( step(
function getRedisClient() { function getRedisClient() {
that.redis_pool.acquire(db, this); that.redis_pool.acquire(db, this);
}, },
@ -89,6 +89,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
}; };
var _reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/; var _reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/;
// jshint maxcomplexity:15
o._checkInvalidTemplate = function(template) { o._checkInvalidTemplate = function(template) {
if ( template.version != '0.0.1' ) { if ( template.version != '0.0.1' ) {
return new Error("Unsupported template version " + template.version); return new Error("Unsupported template version " + template.version);
@ -136,7 +137,6 @@ o._checkInvalidTemplate = function(template) {
break; break;
default: default:
return new Error("Unsupported authentication method: " + auth.method); return new Error("Unsupported authentication method: " + auth.method);
break;
} }
return false; return false;
@ -205,7 +205,7 @@ o.addTemplate = function(owner, template, callback) {
var userTemplatesKey = this.key_usr_tpl({ owner:owner }); var userTemplatesKey = this.key_usr_tpl({ owner:owner });
var limit = this._userTemplateLimit(); var limit = this._userTemplateLimit();
Step( step(
function checkLimit() { function checkLimit() {
if ( ! limit ) { if ( ! limit ) {
return 0; return 0;
@ -253,7 +253,7 @@ o.addTemplate = function(owner, template, callback) {
// //
o.delTemplate = function(owner, tpl_id, callback) { o.delTemplate = function(owner, tpl_id, callback) {
var self = this; var self = this;
Step( step(
function deleteTemplate() { function deleteTemplate() {
self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this); self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
}, },
@ -311,7 +311,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
var userTemplatesKey = this.key_usr_tpl({ owner:owner }); var userTemplatesKey = this.key_usr_tpl({ owner:owner });
Step( step(
function getExistingTemplate() { function getExistingTemplate() {
self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this); self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
}, },
@ -366,7 +366,7 @@ o.listTemplates = function(owner, callback) {
// //
o.getTemplate = function(owner, tpl_id, callback) { o.getTemplate = function(owner, tpl_id, callback) {
var self = this; var self = this;
Step( step(
function getTemplate() { function getTemplate() {
self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this); self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
}, },
@ -424,14 +424,14 @@ var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
_reCSSColorName = /^[a-zA-Z]+$/, _reCSSColorName = /^[a-zA-Z]+$/,
_reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/; _reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
_replaceVars = function(str, params) { function _replaceVars (str, params) {
//return _.template(str, params); // lazy way, possibly dangerous //return _.template(str, params); // lazy way, possibly dangerous
// Construct regular expressions for each param // Construct regular expressions for each param
Object.keys(params).forEach(function(k) { Object.keys(params).forEach(function(k) {
str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]); str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
}); });
return str; return str;
}; }
o.instance = function(template, params) { o.instance = function(template, params) {
var all_params = {}; var all_params = {};
var phold = template.placeholders || {}; var phold = template.placeholders || {};
@ -450,16 +450,14 @@ o.instance = function(template, params) {
else if ( type === 'number' ) { else if ( type === 'number' ) {
// check it's a number // check it's a number
if ( typeof(val) !== 'number' && ! val.match(_reNumber) ) { if ( typeof(val) !== 'number' && ! val.match(_reNumber) ) {
throw new Error("Invalid number value for template parameter '" throw new Error("Invalid number value for template parameter '" + k + "': " + val);
+ k + "': " + val);
} }
} }
else if ( type === 'css_color' ) { else if ( type === 'css_color' ) {
// check it only contains letters or // check it only contains letters or
// starts with # and only contains hexdigits // starts with # and only contains hexdigits
if ( ! val.match(_reCSSColorName) && ! val.match(_reCSSColorVal) ) { if ( ! val.match(_reCSSColorName) && ! val.match(_reCSSColorVal) ) {
throw new Error("Invalid css_color value for template parameter '" throw new Error("Invalid css_color value for template parameter '" + k + "': " + val);
+ k + "': " + val);
} }
} }
else { else {

149
npm-shrinkwrap.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "windshaft-cartodb", "name": "windshaft-cartodb",
"version": "1.28.6", "version": "1.30.1",
"dependencies": { "dependencies": {
"cartodb-psql": { "cartodb-psql": {
"version": "0.4.0", "version": "0.4.0",
"from": "cartodb-psql@~0.4.0", "from": "cartodb-psql@~0.4.0",
"resolved": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0", "resolved": "https://registry.npmjs.org/cartodb-psql/-/cartodb-psql-0.4.0.tgz",
"dependencies": { "dependencies": {
"pg": { "pg": {
"version": "2.6.2-cdb1", "version": "2.6.2-cdb1",
@ -87,7 +87,8 @@
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
"from": "isarray@0.0.1" "from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
@ -95,7 +96,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
"from": "inherits@~2.0.1" "from": "inherits@2"
} }
} }
} }
@ -119,7 +120,7 @@
"redis-mpool": { "redis-mpool": {
"version": "0.3.0", "version": "0.3.0",
"from": "redis-mpool@~0.3.0", "from": "redis-mpool@~0.3.0",
"resolved": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0", "resolved": "https://registry.npmjs.org/redis-mpool/-/redis-mpool-0.3.0.tgz",
"dependencies": { "dependencies": {
"generic-pool": { "generic-pool": {
"version": "2.1.1", "version": "2.1.1",
@ -157,9 +158,9 @@
"resolved": "https://registry.npmjs.org/rollbar/-/rollbar-0.3.13.tgz", "resolved": "https://registry.npmjs.org/rollbar/-/rollbar-0.3.13.tgz",
"dependencies": { "dependencies": {
"node-uuid": { "node-uuid": {
"version": "1.4.2", "version": "1.4.3",
"from": "node-uuid@1.4.x", "from": "node-uuid@1.4.x",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz" "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
}, },
"lru-cache": { "lru-cache": {
"version": "2.2.4", "version": "2.2.4",
@ -181,8 +182,8 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
}, },
"windshaft": { "windshaft": {
"version": "0.37.5", "version": "0.40.0",
"from": "windshaft@~0.37.5", "from": "windshaft@~0.40.0",
"dependencies": { "dependencies": {
"chronograph": { "chronograph": {
"version": "0.1.0", "version": "0.1.0",
@ -193,6 +194,36 @@
"version": "0.23.0", "version": "0.23.0",
"from": "grainstore@~0.23.0", "from": "grainstore@~0.23.0",
"dependencies": { "dependencies": {
"redis-mpool": {
"version": "0.1.0",
"from": "https://github.com/CartoDB/node-redis-mpool/tarball/0.1.0",
"resolved": "https://github.com/CartoDB/node-redis-mpool/tarball/0.1.0",
"dependencies": {
"generic-pool": {
"version": "2.1.1",
"from": "generic-pool@~2.1.1"
},
"redis": {
"version": "0.12.1",
"from": "redis@~0.12.1"
},
"hiredis": {
"version": "0.1.17",
"from": "hiredis@~0.1.17",
"dependencies": {
"bindings": {
"version": "1.2.1",
"from": "bindings@*"
},
"nan": {
"version": "1.1.2",
"from": "nan@~1.1.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.1.2.tgz"
}
}
}
}
},
"carto": { "carto": {
"version": "0.9.5-cdb2", "version": "0.9.5-cdb2",
"from": "https://github.com/CartoDB/carto/tarball/0.9.5-cdb2", "from": "https://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
@ -264,9 +295,9 @@
"from": "forever-agent@~0.5.0" "from": "forever-agent@~0.5.0"
}, },
"node-uuid": { "node-uuid": {
"version": "1.4.2", "version": "1.4.3",
"from": "node-uuid@~1.4.0", "from": "node-uuid@~1.4.0",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz" "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
}, },
"tough-cookie": { "tough-cookie": {
"version": "0.12.1", "version": "0.12.1",
@ -1680,7 +1711,7 @@
} }
}, },
"tilelive-mapnik": { "tilelive-mapnik": {
"version": "0.6.12", "version": "0.6.15",
"from": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb", "from": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb",
"resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb", "resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb",
"dependencies": { "dependencies": {
@ -2080,50 +2111,21 @@
} }
}, },
"canvas": { "canvas": {
"version": "1.1.6", "version": "1.2.1",
"from": "canvas@1.1.6", "from": "canvas@1.2.1",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-1.1.6.tgz", "resolved": "https://registry.npmjs.org/canvas/-/canvas-1.2.1.tgz",
"dependencies": { "dependencies": {
"nan": { "nan": {
"version": "1.2.0", "version": "1.5.3",
"from": "nan@~1.2.0" "from": "nan@~1.5.1",
} "resolved": "https://registry.npmjs.org/nan/-/nan-1.5.3.tgz"
}
},
"redis-mpool": {
"version": "0.1.0",
"from": "redis-mpool@git://github.com/CartoDB/node-redis-mpool.git#0.1.0",
"resolved": "git://github.com/CartoDB/node-redis-mpool.git#47510b8d4525ee24aa2e5328976372274a1d144e",
"dependencies": {
"generic-pool": {
"version": "2.1.1",
"from": "generic-pool@~2.1.1"
},
"redis": {
"version": "0.12.1",
"from": "redis@~0.12.1"
},
"hiredis": {
"version": "0.1.17",
"from": "hiredis@~0.1.17",
"dependencies": {
"bindings": {
"version": "1.2.1",
"from": "bindings@*"
},
"nan": {
"version": "1.1.2",
"from": "nan@~1.1.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.1.2.tgz"
}
}
} }
} }
}, },
"carto": { "carto": {
"version": "0.14.0", "version": "0.15.1-cdb1",
"from": "https://github.com/CartoDB/carto/tarball/update_to_master", "from": "https://github.com/CartoDB/carto/tarball/0.15.1-cdb1",
"resolved": "https://github.com/CartoDB/carto/tarball/update_to_master", "resolved": "https://github.com/CartoDB/carto/tarball/0.15.1-cdb1",
"dependencies": { "dependencies": {
"mapnik-reference": { "mapnik-reference": {
"version": "6.0.5", "version": "6.0.5",
@ -2149,44 +2151,12 @@
} }
}, },
"step-profiler": { "step-profiler": {
"version": "0.1.0", "version": "0.2.1",
"from": "step-profiler@git://github.com/CartoDB/node-step-profiler.git#0.1.0", "from": "step-profiler@~0.2.1"
"resolved": "git://github.com/CartoDB/node-step-profiler.git#9b97881e450445bd8a307a9cc372b5129cb4529a"
}, },
"torque.js": { "torque.js": {
"version": "2.7.1", "version": "2.11.0",
"from": "https://github.com/CartoDB/torque/tarball/2.8", "from": "torque.js@~2.11.0"
"resolved": "https://github.com/CartoDB/torque/tarball/2.8",
"dependencies": {
"carto": {
"version": "0.15.1",
"from": "https://github.com/CartoDB/carto/archive/master.tar.gz",
"resolved": "https://github.com/CartoDB/carto/archive/master.tar.gz",
"dependencies": {
"mapnik-reference": {
"version": "6.0.5",
"from": "mapnik-reference@~6.0.2",
"resolved": "https://registry.npmjs.org/mapnik-reference/-/mapnik-reference-6.0.5.tgz"
},
"optimist": {
"version": "0.6.1",
"from": "optimist@~0.6.0",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"dependencies": {
"wordwrap": {
"version": "0.0.2",
"from": "wordwrap@~0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
},
"minimist": {
"version": "0.0.10",
"from": "minimist@~0.0.1"
}
}
}
}
}
}
}, },
"request": { "request": {
"version": "2.48.0", "version": "2.48.0",
@ -2209,7 +2179,8 @@
}, },
"isarray": { "isarray": {
"version": "0.0.1", "version": "0.0.1",
"from": "isarray@0.0.1" "from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
@ -2258,9 +2229,9 @@
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz" "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz"
}, },
"node-uuid": { "node-uuid": {
"version": "1.4.2", "version": "1.4.3",
"from": "node-uuid@~1.4.0", "from": "node-uuid@~1.4.0",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz" "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
}, },
"qs": { "qs": {
"version": "2.3.3", "version": "2.3.3",

View File

@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"name": "windshaft-cartodb", "name": "windshaft-cartodb",
"version": "1.28.6", "version": "1.30.1",
"description": "A map tile server for CartoDB", "description": "A map tile server for CartoDB",
"keywords": [ "keywords": [
"cartodb" "cartodb"
@ -25,7 +25,7 @@
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0", "node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
"underscore" : "~1.6.0", "underscore" : "~1.6.0",
"dot": "~1.0.2", "dot": "~1.0.2",
"windshaft": "https://github.com/CartoDB/Windshaft/tarball/259-remove-old-api", "windshaft": "https://github.com/CartoDB/Windshaft/tarball/master",
"step": "~0.0.5", "step": "~0.0.5",
"queue-async": "~1.0.7", "queue-async": "~1.0.7",
"request": "~2.9.203", "request": "~2.9.203",
@ -38,12 +38,14 @@
}, },
"devDependencies": { "devDependencies": {
"mocha": "~1.21.4", "mocha": "~1.21.4",
"jshint": "~2.6.0",
"redis": "~0.8.6", "redis": "~0.8.6",
"strftime": "~0.8.2", "strftime": "~0.8.2",
"semver": "~1.1.4" "semver": "~1.1.4"
}, },
"scripts": { "scripts": {
"test": "make check" "preinstall": "make pre-install",
"test": "make test-all"
}, },
"engines": { "engines": {
"node": ">=0.8 <0.11", "node": ">=0.8 <0.11",

View File

@ -0,0 +1,24 @@
#!/bin/bash
if [[ "$OSTYPE" == "darwin"* ]]; then
CAIRO_PKG_CONFIG=`pkg-config cairo --cflags-only-I 2> /dev/null`
RESULT=$?
if [[ ${RESULT} -ne 0 ]]; then
echo "###################################################################################"
echo "# PREINSTALL HOOK ERROR #"
echo "#---------------------------------------------------------------------------------#"
echo "# #"
echo "# node-canvas install error: some packages required by 'cairo' are not found #"
echo "# #"
echo -e "# Use '\033[1mmake all\033[0m', it will take care of common/known issues #"
echo "# #"
echo "# As an alternative try: #"
echo "# Try to 'export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig' #"
echo "# #"
echo "# If problems persist visit: https://github.com/Automattic/node-canvas/wiki #"
echo "# #"
echo "###################################################################################"
exit 1
fi
fi

7
scripts/install.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
if [[ "$OSTYPE" == "darwin"* ]]; then
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig
fi
npm install

18
scripts/lzma2config.js Normal file
View File

@ -0,0 +1,18 @@
if (process.argv.length !== 3) {
console.error('Usage: node %s lzma_string', __filename);
process.exit(1);
}
var LZMA = require('lzma').LZMA;
var lzmaWorker = new LZMA();
var lzmaInput = decodeURIComponent(process.argv[2]);
var lzmaBuffer = new Buffer(lzmaInput, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128
});
lzmaWorker.decompress(lzmaBuffer, function(result) {
console.log(JSON.stringify(JSON.parse(JSON.parse(result).config), null, 4));
});

View File

@ -22,9 +22,11 @@ serverOptions = ServerOptions();
var server = new CartodbWindshaft(serverOptions); var server = new CartodbWindshaft(serverOptions);
server.setMaxListeners(0); server.setMaxListeners(0);
['/tiles/layergroup', '/u/localhost/tiles/layergroup'].forEach(function(layergroup_url) {
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) { [true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { var suiteName = 'multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue + ";layergroup_url=" + layergroup_url;
suite(suiteName, function() {
var redis_client = redis.createClient(global.environment.redis.port); var redis_client = redis.createClient(global.environment.redis.port);
var sqlapi_server; var sqlapi_server;
@ -66,7 +68,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -94,7 +96,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -135,7 +137,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/localhost@' + expected_token + ':cb0/0/0/0.png', url: layergroup_url + '/localhost@' + expected_token + ':cb0/0/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -152,7 +154,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/0/0/0/0.grid.json', + '/0/0/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -170,7 +172,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/1/0/0/0.grid.json', + '/1/0/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -222,7 +224,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)), url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET', method: 'GET',
headers: {host: 'localhost'} headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); }); }, {}, function(res, err) { next(err, res); });
@ -255,7 +257,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)), url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET', method: 'GET',
headers: {host: 'localhost'} headers: {host: 'localhost'}
}, {}, function(res, err) { next(err, res); }); }, {}, function(res, err) { next(err, res); });
@ -299,7 +301,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
] ]
}; };
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)), url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET', method: 'GET',
headers: {host: 'localhost'} headers: {host: 'localhost'}
}, {}, function(res) { }, {}, function(res) {
@ -321,7 +323,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
] ]
}; };
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)), url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
method: 'GET', method: 'GET',
headers: {host: 'localhost'} headers: {host: 'localhost'}
}, {}, function(res) { }, {}, function(res) {
@ -352,7 +354,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -379,7 +381,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb10/1/0/0.png', url: layergroup_url + "/" + expected_token + ':cb10/1/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -418,7 +420,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb11/4/0/0.png', url: layergroup_url + "/" + expected_token + ':cb11/4/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -457,7 +459,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/0/1/0/0.grid.json', + '/0/1/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -475,7 +477,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/0/4/0/0.grid.json', + '/0/4/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -539,7 +541,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -561,7 +563,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val); + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val);
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -620,7 +622,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
] ]
}; };
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -648,7 +650,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
] ]
}; };
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -688,7 +690,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?map_key=1234', url: layergroup_url + '?map_key=1234',
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -716,7 +718,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -737,7 +739,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/0/0/0/0.grid.json?map_key=1234', + '/0/0/0/0.grid.json?map_key=1234',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -751,7 +753,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/1/0/0/0.grid.json?map_key=1234', + '/1/0/0/0.grid.json?map_key=1234',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -766,7 +768,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -782,7 +784,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/0/0/0/0.grid.json', + '/0/0/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -798,7 +800,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token url: layergroup_url + "/" + expected_token
+ '/1/0/0/0.grid.json', + '/1/0/0/0.grid.json',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
method: 'GET' method: 'GET'
@ -849,7 +851,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?map_key=1234', url: layergroup_url + '?map_key=1234',
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -878,7 +880,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -908,7 +910,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?map_key=1234', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?map_key=1234',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -1044,7 +1046,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -1068,7 +1070,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -1120,7 +1122,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?api_key=1234', url: layergroup_url + '?api_key=1234',
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -1146,7 +1148,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
if ( err ) throw err; if ( err ) throw err;
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup/' + expected_token + ':cb0/0/0/0.png?api_key=1234', url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png?api_key=1234',
method: 'GET', method: 'GET',
headers: {host: 'localhost' }, headers: {host: 'localhost' },
encoding: 'binary' encoding: 'binary'
@ -1211,7 +1213,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
assert.ok(data.length > 1024*64); assert.ok(data.length > 1024*64);
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup?api_key=1234', url: layergroup_url + '?api_key=1234',
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: data data: data
@ -1268,7 +1270,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -1310,7 +1312,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
{ {
var next = this; var next = this;
assert.response(server, { assert.response(server, {
url: '/tiles/layergroup', url: layergroup_url,
method: 'POST', method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' }, headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup) data: JSON.stringify(layergroup)
@ -1334,7 +1336,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
} }
var layergroupTtlRequest = { var layergroupTtlRequest = {
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify({ url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify({
version: '1.0.0', version: '1.0.0',
layers: [ layers: [
{ options: { { options: {
@ -1442,3 +1444,4 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
}); });
}); });
});