Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4777d1e93c | ||
|
|
97e8b54b8c | ||
|
|
136f68765a | ||
|
|
0062ec99f1 | ||
|
|
98bc95bc58 | ||
|
|
47cc1de89b | ||
|
|
69c623b5b2 | ||
|
|
ab9ae60958 | ||
|
|
907ed478cf | ||
|
|
2eb7529efb | ||
|
|
1782f240ce | ||
|
|
2ea880ce2c | ||
|
|
775b2b75a6 | ||
|
|
2d050eb43c | ||
|
|
7934d659fb | ||
|
|
21b5ed9c8a | ||
|
|
da70839f78 | ||
|
|
2e1f08d764 | ||
|
|
21072645a4 | ||
|
|
e3c6569302 | ||
|
|
091352e75b | ||
|
|
bf7044d723 | ||
|
|
38e4812b43 | ||
|
|
b8395010a3 | ||
|
|
f0eeb393d6 | ||
|
|
a9ab9f8b5c | ||
|
|
f019f34601 | ||
|
|
400e51f13a | ||
|
|
3234c37d62 | ||
|
|
e0413c3302 | ||
|
|
cd79fc606b | ||
|
|
a2ac1c23f1 | ||
|
|
69f99daa60 | ||
|
|
f1e8c9a709 | ||
|
|
1fc0545b5a | ||
|
|
b599e67c35 | ||
|
|
85804f9854 | ||
|
|
7df3658d41 | ||
|
|
c92e786a5f | ||
|
|
d6ef0b7457 | ||
|
|
434d6d4110 | ||
|
|
55a78899a4 | ||
|
|
124133ceca | ||
|
|
2b9f2ee66c | ||
|
|
70d1a30c64 | ||
|
|
2161bbf8e9 | ||
|
|
41521b6776 | ||
|
|
ec2fcad2e0 | ||
|
|
ecc67b1d0f | ||
|
|
4ea1199014 | ||
|
|
e7544e84c2 | ||
|
|
ada616ee6a | ||
|
|
000a248ab4 | ||
|
|
ff515f8c12 | ||
|
|
848bfacc2d | ||
|
|
da4b1d5a0f | ||
|
|
b2d9e5e822 | ||
|
|
d0313a4228 | ||
|
|
1f53884722 | ||
|
|
a664a1c550 | ||
|
|
d210643d63 | ||
|
|
4be0a70362 | ||
|
|
09e0f86936 | ||
|
|
e3b7027b24 | ||
|
|
0f30b7d7ef | ||
|
|
3012b99e15 | ||
|
|
f683e39aea | ||
|
|
985973dfda | ||
|
|
07bc281e25 | ||
|
|
1d433bf5b2 | ||
|
|
d5e20ef558 | ||
|
|
36ea58e750 | ||
|
|
e1e5f87123 | ||
|
|
ea3d2124dc | ||
|
|
415d0c42d5 | ||
|
|
c19f652ff3 | ||
|
|
f5f7be627f | ||
|
|
09b3f0a862 | ||
|
|
d9ab1e8810 | ||
|
|
23f5be6c33 | ||
|
|
07297f6bda | ||
|
|
02bc7b9fbf | ||
|
|
0e3f72ce0b | ||
|
|
f311f6d4df | ||
|
|
65c6559b1a | ||
|
|
53d92fe70e | ||
|
|
6d32199c53 | ||
|
|
25e4e3bd33 | ||
|
|
0321884795 | ||
|
|
5f6185dd51 | ||
|
|
63ba75f703 | ||
|
|
9b4acf99d5 | ||
|
|
9ba53dc4cf | ||
|
|
2f44dfe1da | ||
|
|
b891ae19f4 | ||
|
|
00cf83dc45 | ||
|
|
72294fbd25 | ||
|
|
5af09fc2bf | ||
|
|
c1c6d493b7 | ||
|
|
a30ed5ce04 | ||
|
|
7c35d5cd32 | ||
|
|
2e67633ca8 | ||
|
|
87782b400d | ||
|
|
b6d3785599 | ||
|
|
645a2cd442 | ||
|
|
8c09dfd230 | ||
|
|
336491b54c | ||
|
|
4365c1dbc2 | ||
|
|
3c56c1fab3 | ||
|
|
0a331cee37 | ||
|
|
d7f5c40645 | ||
|
|
5df24e7f27 | ||
|
|
406a1ffb0b | ||
|
|
438ecd5598 | ||
|
|
bd1c24ee1c | ||
|
|
e561f77d4d | ||
|
|
d03a2c64a6 | ||
|
|
fda8afdaf2 | ||
|
|
e4da13189d | ||
|
|
f35d328dbf | ||
|
|
d09998cce1 | ||
|
|
7a01d75cd8 | ||
|
|
83e5d889a7 | ||
|
|
f1eed600d5 | ||
|
|
75a980ff1d | ||
|
|
9d9aafbcb3 | ||
|
|
8e9d9113d7 | ||
|
|
62661c633c | ||
|
|
38e35a6d61 | ||
|
|
0d5242d12b | ||
|
|
edde869a68 | ||
|
|
6d4eb23696 | ||
|
|
1979697551 | ||
|
|
661df294e1 | ||
|
|
39dc9a316d | ||
|
|
cfe434c8ed | ||
|
|
53d7276136 | ||
|
|
e95167a049 | ||
|
|
e82131f4e8 | ||
|
|
cbd44192c9 | ||
|
|
f006d09f31 | ||
|
|
620160c44e | ||
|
|
6ae2c2630a | ||
|
|
a287c84500 | ||
|
|
62e435fd9e | ||
|
|
65702de64d | ||
|
|
ef2b45621b | ||
|
|
1771313bea | ||
|
|
b0624582d9 | ||
|
|
10acdc4615 | ||
|
|
d96ed3511e | ||
|
|
e0dba85f67 | ||
|
|
989e752959 | ||
|
|
71efe2109c | ||
|
|
5da239a2eb | ||
|
|
5c2e5c0d05 | ||
|
|
27d6d636cf | ||
|
|
5db7f002e6 | ||
|
|
ac1f8f8497 | ||
|
|
ee6bd8c561 | ||
|
|
7f20e296a3 | ||
|
|
2e577343d2 | ||
|
|
dc14248de2 | ||
|
|
9536669053 | ||
|
|
11db363bfb | ||
|
|
d2961430f3 | ||
|
|
2b7bd58fd5 | ||
|
|
055932d38e | ||
|
|
c65a29acf4 | ||
|
|
27cda49fd8 | ||
|
|
2d7b706507 | ||
|
|
3ea3638fe9 | ||
|
|
1f9387bb68 | ||
|
|
00542bbc57 | ||
|
|
5be8afbbeb | ||
|
|
9f7dcc2354 | ||
|
|
21b3f44441 | ||
|
|
f295f847d1 | ||
|
|
0478905689 | ||
|
|
d311dd4245 | ||
|
|
b25bb03cdf | ||
|
|
afa625e3d2 | ||
|
|
e08b1ea1a0 | ||
|
|
c6d328ee07 | ||
|
|
8d10d0f760 | ||
|
|
a2a09979e4 | ||
|
|
597cb5286d | ||
|
|
a0243e0bf7 | ||
|
|
0f668aabf1 | ||
|
|
59dfd11e5b | ||
|
|
b1b57d6f24 | ||
|
|
636591ecbb | ||
|
|
a4eade31a2 | ||
|
|
ba0f394a48 | ||
|
|
75c4153f9b | ||
|
|
8e038b0323 | ||
|
|
0a994c731c | ||
|
|
13ae1b4067 | ||
|
|
742a9744ea | ||
|
|
8ed864ad18 | ||
|
|
87638168ff | ||
|
|
8364da683a | ||
|
|
6eec5822f0 | ||
|
|
d667dbcc2f | ||
|
|
40de1a8f86 | ||
|
|
b53c25e514 | ||
|
|
81919706ea | ||
|
|
d38fc16b57 | ||
|
|
90b22b2718 | ||
|
|
04af57cab9 | ||
|
|
d40b15454b | ||
|
|
e1e925bd9e | ||
|
|
151968ae13 | ||
|
|
547782eea5 | ||
|
|
6bd967e9fb | ||
|
|
13f5fda1b8 | ||
|
|
673bd4f3f2 | ||
|
|
d56affae2d | ||
|
|
09527b6808 | ||
|
|
d065ace036 | ||
|
|
2736b93c69 | ||
|
|
10b7ab307e | ||
|
|
56c24a738a | ||
|
|
fa8b27231c | ||
|
|
c17af23a40 | ||
|
|
fbecc11aa5 | ||
|
|
8cacc3bb9e | ||
|
|
a82af16347 | ||
|
|
5018d32af6 | ||
|
|
2c7bc6adde | ||
|
|
58f9f5f7a8 | ||
|
|
e4e633cf86 | ||
|
|
4ca5c5fa3c | ||
|
|
1bb0d8738e | ||
|
|
4949616c4e | ||
|
|
12c5d835c5 | ||
|
|
87eaeb0074 | ||
|
|
8b07156a2d | ||
|
|
358b296750 | ||
|
|
d0ef87b0cf | ||
|
|
e28fe1fdc0 | ||
|
|
aecb07b008 | ||
|
|
5573dfda84 | ||
|
|
7a22973258 | ||
|
|
f099a69df3 | ||
|
|
938b6579c0 | ||
|
|
e445d0de01 | ||
|
|
6a39893e20 | ||
|
|
2e84d18c3c | ||
|
|
b26fe87430 | ||
|
|
e9f5c60719 | ||
|
|
9b06ac833a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ logs/
|
||||
pids/
|
||||
redis.pid
|
||||
test.log
|
||||
npm-debug.log
|
||||
coverage/
|
||||
|
||||
98
.jshintrc
Normal file
98
.jshintrc
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
// // 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
|
||||
"describe": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"beforeEach": true,
|
||||
"afterEach": true,
|
||||
"it": true,
|
||||
"suite": true,
|
||||
"suiteSetup": true,
|
||||
"test": true,
|
||||
"suiteTeardown": true
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ addons:
|
||||
postgresql: "9.3"
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -y pkg-config libcairo2-dev libjpeg8-dev libgif-dev
|
||||
- sudo apt-get install postgresql-plpython-9.3
|
||||
- createdb template_postgis
|
||||
- psql -c "CREATE EXTENSION postgis" template_postgis
|
||||
|
||||
@@ -11,7 +13,6 @@ env:
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
|
||||
notifications:
|
||||
|
||||
38
Makefile
38
Makefile
@@ -1,7 +1,10 @@
|
||||
srcdir=$(shell pwd)
|
||||
SHELL=/bin/bash
|
||||
|
||||
pre-install:
|
||||
@$(SHELL) ./scripts/check-node-canvas.sh
|
||||
|
||||
all:
|
||||
npm install
|
||||
@$(SHELL) ./scripts/install.sh
|
||||
|
||||
clean:
|
||||
rm -rf node_modules/*
|
||||
@@ -15,21 +18,24 @@ config.status--test:
|
||||
config/environments/test.js: config.status--test
|
||||
./config.status--test
|
||||
|
||||
check-local: config/environments/test.js
|
||||
./run_tests.sh ${RUNTESTFLAGS} \
|
||||
test/unit/cartodb/*.js \
|
||||
test/acceptance/*.js
|
||||
test: config/environments/test.js
|
||||
@echo "***tests***"
|
||||
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \
|
||||
test/unit/cartodb/*.js \
|
||||
test/unit/cartodb/cache/model/*.js \
|
||||
test/integration/*.js \
|
||||
test/acceptance/*.js \
|
||||
test/acceptance/cache/*.js
|
||||
|
||||
check-submodules:
|
||||
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \
|
||||
for sub in windshaft grainstore node-varnish mapnik; do \
|
||||
if test -e node_modules/$${sub}; then \
|
||||
echo "Testing submodule $${sub}"; \
|
||||
make -C node_modules/$${sub} check || exit 1; \
|
||||
fi; \
|
||||
done
|
||||
jshint:
|
||||
@echo "***jshint***"
|
||||
@./node_modules/.bin/jshint lib/ app.js
|
||||
|
||||
check-full: check-local check-submodules
|
||||
test-all: jshint test
|
||||
|
||||
check: check-local
|
||||
coverage:
|
||||
@RUNTESTFLAGS=--with-coverage make test
|
||||
|
||||
check: test
|
||||
|
||||
.PHONY: pre-install test jshint coverage
|
||||
|
||||
484
NEWS.md
484
NEWS.md
@@ -1,26 +1,144 @@
|
||||
1.26.2 -- 2015-01-28
|
||||
--------------------
|
||||
# Changelog
|
||||
|
||||
|
||||
## 2.0.0
|
||||
|
||||
Released 2015-04-08
|
||||
|
||||
Announcements:
|
||||
- Major release with **BREAKING CHANGES**:
|
||||
* Removes `/:table/infowindow`, `/:table/map_metadata` and `/:table/flush_cache` endpoints
|
||||
* Sample configuration removes `/tiles/template` and `/tiles/layergroup`
|
||||
* URLs to use from now on are: `/api/v1/map/named` and `/api/v1/map`
|
||||
* No more state changes for styles
|
||||
* No more dump stats for renderers: SIGUSR1 and SIGUSR2 signals
|
||||
* Removes query params:
|
||||
- sql
|
||||
- geom_type
|
||||
- cache_buster
|
||||
- cache_policy
|
||||
- interactivity
|
||||
- style
|
||||
- style_version
|
||||
- style_convert
|
||||
- scale_factor
|
||||
* Affected tables for x-cache-channel will use direct connection to postgresql
|
||||
* Removes some metrics: authorized times ones
|
||||
* Mapnik renderer configuration not part of the `renderer` root configuration
|
||||
- All configuration must be moved into `renderer.mapnik`, see `config/environments/*.js.example` for reference
|
||||
- Removes rollbar as optional logger
|
||||
|
||||
|
||||
## 1.30.0
|
||||
|
||||
Released 2015-03-11
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.40.0](https://github.com/CartoDB/Windshaft/releases/tag/0.40.0)
|
||||
|
||||
|
||||
## 1.29.0
|
||||
|
||||
Released 2015-03-09
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.39.0](https://github.com/CartoDB/Windshaft/releases/tag/0.39.0)
|
||||
|
||||
|
||||
## 1.28.5
|
||||
|
||||
Released 2015-02-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.5](https://github.com/CartoDB/Windshaft/releases/tag/0.37.5)
|
||||
|
||||
|
||||
## 1.28.4
|
||||
|
||||
Released 2015-02-18
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.4](https://github.com/CartoDB/Windshaft/releases/tag/0.37.4)
|
||||
|
||||
|
||||
## 1.28.3
|
||||
|
||||
Released 2015-02-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.3](https://github.com/CartoDB/Windshaft/releases/tag/0.37.3)
|
||||
|
||||
|
||||
## 1.28.2
|
||||
|
||||
Released 2015-02-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.2](https://github.com/CartoDB/Windshaft/releases/tag/0.37.2)
|
||||
|
||||
|
||||
## 1.28.1
|
||||
|
||||
Released 2015-02-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.1](https://github.com/CartoDB/Windshaft/releases/tag/0.37.1)
|
||||
|
||||
|
||||
## 1.28.0
|
||||
|
||||
Released 2015-02-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.0](https://github.com/CartoDB/Windshaft/releases/tag/0.37.0)
|
||||
|
||||
New features:
|
||||
- QueryTablesApi will always use an authenticated query to retrieve last update, this allows to query affected private
|
||||
tables last update (#253)
|
||||
|
||||
|
||||
## 1.27.0
|
||||
|
||||
Released 2015-02-16
|
||||
|
||||
Announcements:
|
||||
- Adds default image placeholder for http renderer to use as fallback
|
||||
|
||||
New features:
|
||||
- `named` layers type, see [MapConfig-NamedMaps-extension](docs/MapConfig-NamedMaps-extension.md)
|
||||
- Starts using datasource per layer feature from Windshaft ([2c7bc6a](https://github.com/CartoDB/Windshaft-cartodb/commit/2c7bc6adde561b20ed955b905e3c7bcd6795d128))
|
||||
|
||||
Bugfixes:
|
||||
- Fixes tests with beforeEach and afterEach triggers
|
||||
|
||||
|
||||
## 1.26.2
|
||||
|
||||
Released 2015-01-28
|
||||
|
||||
Bugfixes:
|
||||
- Accept 'open' string in templates' `auth` as authorized.
|
||||
|
||||
|
||||
1.26.1 -- 2015-01-28
|
||||
--------------------
|
||||
## 1.26.1
|
||||
|
||||
Released 2015-01-28
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.35.1, see https://github.com/CartoDB/Windshaft/pull/254
|
||||
|
||||
|
||||
1.26.0 -- 2015-01-27
|
||||
--------------------
|
||||
## 1.26.0
|
||||
|
||||
Released 2015-01-27
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.35.0, supports mapconfig version `1.3.0`
|
||||
|
||||
|
||||
1.25.0 -- 2015-01-26
|
||||
--------------------
|
||||
## 1.25.0
|
||||
|
||||
Released 2015-01-26
|
||||
|
||||
Announcements:
|
||||
- No more signed maps (#227 and #238)
|
||||
@@ -56,22 +174,25 @@ New features:
|
||||
- SurrogateKeysCache is subscribed to TemplateMaps events to do the invalidations
|
||||
|
||||
|
||||
1.24.0 -- 2015-01-15
|
||||
--------------------
|
||||
## 1.24.0
|
||||
|
||||
Released 2015-01-15
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.34.0 for retina support
|
||||
|
||||
|
||||
1.23.1 -- 2015-01-14
|
||||
--------------------
|
||||
## 1.23.1
|
||||
|
||||
Released 2015-01-14
|
||||
|
||||
Announcements:
|
||||
- Regenerate npm-shrinkwrap.json
|
||||
|
||||
|
||||
1.23.0 -- 2015-01-14
|
||||
--------------------
|
||||
## 1.23.0
|
||||
|
||||
Released 2015-01-14
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.33.0
|
||||
@@ -80,22 +201,25 @@ New features:
|
||||
- Sets HTTP renderer configuration in server_options
|
||||
|
||||
|
||||
1.22.0 -- 2015-01-13
|
||||
--------------------
|
||||
## 1.22.0
|
||||
|
||||
Released 2015-01-13
|
||||
|
||||
New features:
|
||||
- Health check endpoint
|
||||
|
||||
|
||||
1.21.2 -- 2014-12-15
|
||||
--------------------
|
||||
## 1.21.2
|
||||
|
||||
Released 2014-12-15
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.32.4
|
||||
|
||||
|
||||
1.21.1 -- 2014-12-11
|
||||
--------------------
|
||||
## 1.21.1
|
||||
|
||||
Released 2014-12-11
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.32.2
|
||||
@@ -105,29 +229,33 @@ Bugfixes:
|
||||
|
||||
|
||||
|
||||
1.21.0 -- 2014-10-24
|
||||
--------------------
|
||||
## 1.21.0
|
||||
|
||||
Released 2014-10-24
|
||||
|
||||
New features:
|
||||
- Allow a different cache-control max-age for layergroup responses
|
||||
|
||||
|
||||
1.20.2 -- 2014-10-20
|
||||
--------------------
|
||||
## 1.20.2
|
||||
|
||||
Released 2014-10-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.31.0
|
||||
|
||||
|
||||
1.20.1 -- 2014-10-17
|
||||
--------------------
|
||||
## 1.20.1
|
||||
|
||||
Released 2014-10-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades redis-mpool to 0.3.0
|
||||
|
||||
|
||||
1.20.0 -- 2014-10-15
|
||||
--------------------
|
||||
## 1.20.0
|
||||
|
||||
Released 2014-10-15
|
||||
|
||||
New features:
|
||||
- Report to statsd the status of redis pools
|
||||
@@ -137,8 +265,9 @@ Enhancements:
|
||||
- Share one redis-mpool across the application
|
||||
|
||||
|
||||
1.19.0 -- 2014-10-14
|
||||
--------------------
|
||||
## 1.19.0
|
||||
|
||||
Released 2014-10-14
|
||||
|
||||
Announcements:
|
||||
- Dropping support for npm <1.2.1
|
||||
@@ -147,8 +276,9 @@ Announcements:
|
||||
- Generates npm-shrinkwrap.json with npm >1.2.0
|
||||
|
||||
|
||||
1.18.2 -- 2014-10-13
|
||||
--------------------
|
||||
## 1.18.2
|
||||
|
||||
Released 2014-10-13
|
||||
|
||||
Bug fixes:
|
||||
- Defaults resultSet to object if undefined in QueryTablesApi
|
||||
@@ -157,29 +287,33 @@ Announcements:
|
||||
- Upgrades windshaft to 0.28.1
|
||||
|
||||
|
||||
1.18.1 -- 2014-10-13
|
||||
--------------------
|
||||
## 1.18.1
|
||||
|
||||
Released 2014-10-13
|
||||
|
||||
New features:
|
||||
- Allow to add more node.js' threadpool workers via process.env.UV_THREADPOOL_SIZE
|
||||
|
||||
|
||||
1.18.0 -- 2014-10-03
|
||||
--------------------
|
||||
## 1.18.0
|
||||
|
||||
Released 2014-10-03
|
||||
|
||||
Announcements:
|
||||
- Comes back to use mapnik 2.3.x based on cartodb/node-mapnik@1.4.15-cdb from windshaft@0.28.0
|
||||
|
||||
|
||||
1.17.2 -- 2014-10-01
|
||||
--------------------
|
||||
## 1.17.2
|
||||
|
||||
Released 2014-10-01
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.27.2 which downgrades node-mapnik to 0.7.26-cdb1
|
||||
|
||||
|
||||
1.17.1 -- 2014-09-30
|
||||
--------------------
|
||||
## 1.17.1
|
||||
|
||||
Released 2014-09-30
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to 0.27.1 which downgrades node-mapnik to 1.4.10
|
||||
@@ -189,8 +323,9 @@ Enhancements:
|
||||
- Upgrades mocha
|
||||
|
||||
|
||||
1.17.0 -- 2014-09-25
|
||||
--------------------
|
||||
## 1.17.0
|
||||
|
||||
Released 2014-09-25
|
||||
|
||||
New features:
|
||||
- Starts using mapnik 2.3.x
|
||||
@@ -201,14 +336,16 @@ Enhancements:
|
||||
- Metrics revamp: removes and adds some metrics
|
||||
- Adds poolSize configuration for mapnik
|
||||
|
||||
1.16.1 -- 2014-08-19
|
||||
--------------------
|
||||
## 1.16.1
|
||||
|
||||
Released 2014-08-19
|
||||
|
||||
Enhancements:
|
||||
- Upgrades cartodb-redis
|
||||
|
||||
1.16.0 -- 2014-08-18
|
||||
--------------------
|
||||
## 1.16.0
|
||||
|
||||
Released 2014-08-18
|
||||
|
||||
New features:
|
||||
- Configurable QueryTablesAPI to call directly postgresql using cartodb-psql
|
||||
@@ -224,8 +361,9 @@ Enhancements:
|
||||
- windshaft
|
||||
- request
|
||||
|
||||
1.15.0 -- 2014-08-13
|
||||
--------------------
|
||||
## 1.15.0
|
||||
|
||||
Released 2014-08-13
|
||||
Enhancements:
|
||||
- Upgrades dependencies:
|
||||
- redis-mpool
|
||||
@@ -235,8 +373,9 @@ Enhancements:
|
||||
- Slow pool configuration in example configurations
|
||||
|
||||
|
||||
1.14.0 -- 2014-08-07
|
||||
--------------------
|
||||
## 1.14.0
|
||||
|
||||
Released 2014-08-07
|
||||
|
||||
Enhancements:
|
||||
- SQL API requests moved to its own entity
|
||||
@@ -248,43 +387,49 @@ New features:
|
||||
dependencies for this matter
|
||||
|
||||
|
||||
1.13.1 -- 2014-08-04
|
||||
--------------------
|
||||
## 1.13.1
|
||||
|
||||
Released 2014-08-04
|
||||
|
||||
Enhancements:
|
||||
- Profiler header sent as JSON string
|
||||
|
||||
|
||||
1.13.0 -- 2014-07-30
|
||||
--------------------
|
||||
## 1.13.0
|
||||
|
||||
Released 2014-07-30
|
||||
|
||||
New features:
|
||||
- Support for postgresql schemas
|
||||
- Use public user from redis
|
||||
- Support for several auth tokens
|
||||
|
||||
1.12.1 -- 2014-06-24
|
||||
--------------------
|
||||
## 1.12.1
|
||||
|
||||
Released 2014-06-24
|
||||
|
||||
Enhancements:
|
||||
- Caches layergroup and sets X-Cache-Channel in GET requests also in named maps
|
||||
|
||||
1.12.0 -- 2014-06-24
|
||||
--------------------
|
||||
## 1.12.0
|
||||
|
||||
Released 2014-06-24
|
||||
|
||||
New features:
|
||||
- Caches layergroup and sets X-Cache-Channel in GET requests
|
||||
|
||||
1.11.1 -- 2014-05-07
|
||||
--------------------
|
||||
## 1.11.1
|
||||
|
||||
Released 2014-05-07
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Upgrade Windshaft to 0.21.0, see
|
||||
http://github.com/CartoDB/Windshaft/blob/0.21.0/NEWS
|
||||
|
||||
1.11.0 -- 2014-04-28
|
||||
--------------------
|
||||
## 1.11.0
|
||||
|
||||
Released 2014-04-28
|
||||
|
||||
New features:
|
||||
|
||||
@@ -295,23 +440,26 @@ Enhancements:
|
||||
|
||||
- Set default PostgreSQL application name to "cartodb_tiler"
|
||||
|
||||
1.10.2 -- 2014-04-08
|
||||
--------------------
|
||||
## 1.10.2
|
||||
|
||||
Released 2014-04-08
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Fix show_style tool broken since 1.8.1
|
||||
- Fix X-Cache-Channel of tiles accessed via signed token (#188)
|
||||
|
||||
1.10.1 -- 2014-03-21
|
||||
--------------------
|
||||
## 1.10.1
|
||||
|
||||
Released 2014-03-21
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- Do not cache non-success jsonp responses (#186)
|
||||
|
||||
1.10.0 -- 2014-03-20
|
||||
--------------------
|
||||
## 1.10.0
|
||||
|
||||
Released 2014-03-20
|
||||
|
||||
New features:
|
||||
|
||||
@@ -332,15 +480,17 @@ Other changes:
|
||||
|
||||
- Switch to 3-clause BSD license (#184)
|
||||
|
||||
1.9.0 -- 2014-03-10
|
||||
-------------------
|
||||
## 1.9.0
|
||||
|
||||
Released 2014-03-10
|
||||
|
||||
New features:
|
||||
|
||||
- Allow to set server related configuration in serverMetadata (#182)
|
||||
|
||||
1.8.5 -- 2014-03-10
|
||||
-------------------
|
||||
## 1.8.5
|
||||
|
||||
Released 2014-03-10
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -358,8 +508,9 @@ Bug fixes:
|
||||
|
||||
- Do not cache map creation responses (#176)
|
||||
|
||||
1.8.4 -- 2014-03-03
|
||||
-------------------
|
||||
## 1.8.4
|
||||
|
||||
Released 2014-03-03
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -376,8 +527,9 @@ Bug fixes:
|
||||
|
||||
- Fix database connection settings on template instanciation (#174)
|
||||
|
||||
1.8.3 -- 2014-02-27
|
||||
-------------------
|
||||
## 1.8.3
|
||||
|
||||
Released 2014-02-27
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -390,8 +542,9 @@ Enhancements:
|
||||
[ new sqlapi.timeout directive, defaults to 100 ms ]
|
||||
- Do not query CDB_TableMetadata for queries affected by no tables (#168)
|
||||
|
||||
1.8.2 -- 2014-02-25
|
||||
-------------------
|
||||
## 1.8.2
|
||||
|
||||
Released 2014-02-25
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -405,8 +558,9 @@ Bug fixes:
|
||||
|
||||
* Fix munin plugin after log format changes (#154)
|
||||
|
||||
1.8.1 -- 2014-02-19
|
||||
-------------------
|
||||
## 1.8.1
|
||||
|
||||
Released 2014-02-19
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -416,8 +570,9 @@ Bug fixes:
|
||||
|
||||
* Always generate X-Cache-Channel for token-based tile responses (#152)
|
||||
|
||||
1.8.0 -- 2014-02-18
|
||||
-------------------
|
||||
## 1.8.0
|
||||
|
||||
Released 2014-02-18
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -432,16 +587,18 @@ Enhancements:
|
||||
* Allow limiting number of templates for each user (#136)
|
||||
* Allow configuring TTL of mapConfigs via "mapConfigTTL"
|
||||
|
||||
1.7.1 -- 2014-02-11
|
||||
-------------------
|
||||
## 1.7.1
|
||||
|
||||
Released 2014-02-11
|
||||
|
||||
Enhancements:
|
||||
|
||||
* Disable debug logging unless "debug" config param evaluates to true (#137)
|
||||
* Require windshaft 0.17.2 for further reducing log noise (#137)
|
||||
|
||||
1.7.0 -- 2014-02-11
|
||||
-------------------
|
||||
## 1.7.0
|
||||
|
||||
Released 2014-02-11
|
||||
|
||||
New features:
|
||||
|
||||
@@ -462,8 +619,9 @@ Bug fixes:
|
||||
* Allow passing numbers as values for numeric template variables (#130)
|
||||
|
||||
|
||||
1.6.3 -- 2014-01-30
|
||||
-------------------
|
||||
## 1.6.3
|
||||
|
||||
Released 2014-01-30
|
||||
|
||||
Bug fixes:
|
||||
|
||||
@@ -478,8 +636,9 @@ Enhancements:
|
||||
* Stop processing XML on renderer creation, not needed anymore since 1.6.1
|
||||
introduced on-demand XML generation.
|
||||
|
||||
1.6.2 -- 2014-01-23
|
||||
-------------------
|
||||
## 1.6.2
|
||||
|
||||
Released 2014-01-23
|
||||
|
||||
Bug fixes:
|
||||
|
||||
@@ -491,16 +650,18 @@ Enhancements:
|
||||
print XML style now it is not in redis anymore (#110)
|
||||
* Support CORS in template instanciation endpoint (#113)
|
||||
|
||||
1.6.1 -- 2014-01-15
|
||||
-------------------
|
||||
## 1.6.1
|
||||
|
||||
Released 2014-01-15
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Drop cache headers from error responses (#107)
|
||||
* Localize external CartoCSS resources at renderer creation time (#108)
|
||||
|
||||
1.6.0 -- 2014-01-10
|
||||
-------------------
|
||||
## 1.6.0
|
||||
|
||||
Released 2014-01-10
|
||||
|
||||
New features:
|
||||
|
||||
@@ -513,24 +674,27 @@ Other changes:
|
||||
* Update cartodb-redis dependency to "~0.3.0"
|
||||
* Update redis-server dependency to "2.4.0+"
|
||||
|
||||
1.5.2 -- 2013-12-05
|
||||
-------------------
|
||||
## 1.5.2
|
||||
|
||||
Released 2013-12-05
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fix configuration-level compatibility with versions prior to 1.5 (#96)
|
||||
* Fix use of old layergroups on mapnik upgrade (#97)
|
||||
|
||||
1.5.1 -- 2013-11-28
|
||||
-------------------
|
||||
## 1.5.1
|
||||
|
||||
Released 2013-11-28
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Survive presence of malformed CartoCSS in redis (#94)
|
||||
* Accept useless point-transform:scale directives (#93)
|
||||
|
||||
1.5.0 -- 2013-11-19
|
||||
-------------------
|
||||
## 1.5.0
|
||||
|
||||
Released 2013-11-19
|
||||
|
||||
NOTE: new configuration directives `postgres_auth_pass` and
|
||||
`postgres.password` added; see config/environments/*.example
|
||||
@@ -554,24 +718,28 @@ Other changes:
|
||||
* CartoDB redis interaction delegated to "cartodb-redis" module
|
||||
|
||||
|
||||
1.4.1 -- 2013-11-08
|
||||
-------------------
|
||||
## 1.4.1
|
||||
|
||||
Released 2013-11-08
|
||||
|
||||
* Fix support for exponential notation in CartoCSS filter values (#87)
|
||||
|
||||
1.4.0 -- 2013-10-31
|
||||
-------------------
|
||||
## 1.4.0
|
||||
|
||||
Released 2013-10-31
|
||||
|
||||
* Add Support for Mapnik-2.2.0 (#78)
|
||||
|
||||
1.3.6 -- 2013-10-11
|
||||
-------------------
|
||||
## 1.3.6
|
||||
|
||||
Released 2013-10-11
|
||||
|
||||
* Restore support for node-0.8.9 accidentally dropped by 1.3.5
|
||||
NOTE: needs removing node_modules/windshaft and re-running npm install
|
||||
|
||||
1.3.5 -- 2013-10-03
|
||||
-------------------
|
||||
## 1.3.5
|
||||
|
||||
Released 2013-10-03
|
||||
|
||||
* Fixing apostrophes in CartoCSS
|
||||
* Fix "sql/table must contain zoom variable" error when using
|
||||
@@ -580,8 +748,8 @@ Other changes:
|
||||
* Fix error for invalid text-name in CartoCSS (#81)
|
||||
* Do not let anonymous requests use authorized renderer caches
|
||||
|
||||
1.3.4
|
||||
------
|
||||
## 1.3.4
|
||||
|
||||
|
||||
NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
(support for "sqlapi.host" is retained for backward compatibility)
|
||||
@@ -590,38 +758,38 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Improve invalid mapnik-geometry-type CSS error message
|
||||
* Fix race condition in localization of network resources
|
||||
|
||||
1.3.3
|
||||
------
|
||||
## 1.3.3
|
||||
|
||||
* Set Last-Modified header to allow for 304 responses
|
||||
* Add profiling support (needs useProfiler in env config file)
|
||||
* Fix double-checking for layergroups with no interactivity
|
||||
* Log full layergroup config at creation time (#76)
|
||||
|
||||
1.3.2
|
||||
------
|
||||
## 1.3.2
|
||||
|
||||
* Set default layergroup TTL to 2 hours
|
||||
* Serve multilayer tiles and grid with persistent cache control
|
||||
|
||||
1.3.1
|
||||
------
|
||||
## 1.3.1
|
||||
|
||||
* Fix deadlock on new style creation
|
||||
* Fix database authentication with multi-table layergroups
|
||||
* Add tile and grid fetching checks at layergroup creation time
|
||||
* Fix SQL error reporting to NOT split on newline
|
||||
* Fix support for CartoCSS attachments
|
||||
|
||||
1.3.0
|
||||
------
|
||||
## 1.3.0
|
||||
|
||||
* Change stats format for multilayer map token request, see
|
||||
http://github.com/Vizzuality/Windshaft-cartodb/wiki/Redis-stats-format
|
||||
|
||||
1.2.1
|
||||
------
|
||||
## 1.2.1
|
||||
|
||||
* Fix multilayer post from firefox
|
||||
* Fix multilayer cartocss layer name handling
|
||||
|
||||
1.2.0
|
||||
------
|
||||
## 1.2.0
|
||||
|
||||
* Multilayer API changes
|
||||
* Layers passed by index in grid fetching url
|
||||
* Interactivity only specified in layergroup config
|
||||
@@ -629,14 +797,14 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Use ISO format for last_modified timestamp
|
||||
* Expected LZMA encoding changed to base64
|
||||
|
||||
1.1.10
|
||||
------
|
||||
## 1.1.10
|
||||
|
||||
* Fix regression with default interactivity parameter (#74)
|
||||
* More verbose logging for SQL api connection errors
|
||||
* Write stats for multilayer map token request
|
||||
|
||||
1.1.9
|
||||
-----
|
||||
## 1.1.9
|
||||
|
||||
* Handle SQL API errors by requesting no Varnish cache
|
||||
* Fix X-Cache-Channel for multilayer (by token) responses
|
||||
* Add last_modified field to layergroup creation response (#72)
|
||||
@@ -645,18 +813,22 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Add support for LZMA compressed GET parameters
|
||||
* Add support for creating layergroups via GET
|
||||
|
||||
1.1.8
|
||||
-----
|
||||
## 1.1.8
|
||||
|
||||
* Require Windshaft-0.9.1, to reduce harmfulness of cache_buster param
|
||||
|
||||
1.1.7 (DD//MM//YY)
|
||||
-----
|
||||
## 1.1.7
|
||||
|
||||
Released DD//MM//YY
|
||||
|
||||
* Do not let /etc/services confuse FD checker (munin plugin)
|
||||
* Multilayer support (#72)
|
||||
* Expose renderer settings in the environment config files
|
||||
|
||||
1.1.6 (19//02//13)
|
||||
-----
|
||||
## 1.1.6
|
||||
|
||||
Released 19//02//13
|
||||
|
||||
* Require windshaft 0.8.5, fixing some stability issues
|
||||
and providing cache info on request
|
||||
* Require grainstore 0.10.9, fixing an issue with multi-geom markers
|
||||
@@ -665,20 +837,26 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Survive connection refusals from redis
|
||||
* Add maxConnection environment configuration, default to 128
|
||||
|
||||
1.1.5 (DD//MM//YY)
|
||||
-----
|
||||
## 1.1.5
|
||||
|
||||
Released DD//MM//YY
|
||||
|
||||
* Fix bogus cached return of utf grid for fully contained tiles (#67)
|
||||
|
||||
1.1.4 (DD//MM//YY)
|
||||
-----
|
||||
## 1.1.4
|
||||
|
||||
Released DD//MM//YY
|
||||
|
||||
* Reduce default extent to allow for consistent proj4 round-tripping
|
||||
* Enhance reset_styles script to use full configuration (#62)
|
||||
* Have reset_styles script also drop extended keys (#58)
|
||||
* Fix example postgis parameter for simplifying input geoms (#63)
|
||||
* Add row_limit to example config (#64)
|
||||
|
||||
1.1.3 (30//11//12)
|
||||
-----
|
||||
## 1.1.3
|
||||
|
||||
Released 30//11//12
|
||||
|
||||
* Fix reset_styles script to really skip extended keys
|
||||
* CartoCSS versioning
|
||||
* Mapnik-version dependent default styles
|
||||
@@ -686,16 +864,20 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* styles with conditional markers
|
||||
* scale arrow markers by 50%
|
||||
|
||||
1.1.2 (DD//MM//YY)
|
||||
-----
|
||||
## 1.1.2
|
||||
|
||||
Released DD//MM//YY
|
||||
|
||||
* CartoCSS versioning
|
||||
* Fix use of "style_version" with GET (inline styles)
|
||||
* Enhance 2.0 -> 2.1 transforms:
|
||||
* styles with no semicolon
|
||||
* markers shift due to geometry clipping
|
||||
|
||||
1.1.1 (DD//MM//YY)
|
||||
-----
|
||||
## 1.1.1
|
||||
|
||||
Released DD//MM//YY
|
||||
|
||||
* Add support for persistent client cache headers
|
||||
* Fix crash on unknown user (#55)
|
||||
* Add /version entry point
|
||||
@@ -704,8 +886,10 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Support style_version and style_convert parameters in POST /style request
|
||||
* Support style_version in GET /:z/:x/:y request
|
||||
|
||||
1.1.0 (30/10/12)
|
||||
=======
|
||||
## 1.1.0
|
||||
|
||||
Released (30/10/12)
|
||||
|
||||
* Add /version entry point
|
||||
* CartoCSS versioning
|
||||
* Include version in GET /style response
|
||||
@@ -722,12 +906,16 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain
|
||||
* Replaced environment configs by .example ones
|
||||
* Fixed some issues with cluster2
|
||||
|
||||
1.0.0 (03/10/12)
|
||||
-----
|
||||
## 1.0.0
|
||||
|
||||
Released 03/10/12
|
||||
|
||||
* Migrated to node 0.8.x.
|
||||
|
||||
0.9.0 (25/09/12)
|
||||
-----
|
||||
## 0.9.0
|
||||
|
||||
Released 25/09/12
|
||||
|
||||
* External resources in CartoCSS
|
||||
* Added X-Cache-Channel header in all the tiler GET requests
|
||||
* Small fixes
|
||||
|
||||
100
README.md
100
README.md
@@ -1,39 +1,38 @@
|
||||
Windshaft-CartoDB
|
||||
==================
|
||||
|
||||
[]
|
||||
(http://travis-ci.org/CartoDB/Windshaft-cartodb)
|
||||
[](https://travis-ci.org/CartoDB/Windshaft-cartodb)
|
||||
|
||||
This is the CartoDB map tiler. It extends Windshaft with some extra
|
||||
functionality and custom filters for authentication
|
||||
This is the [CartoDB Maps API](http://docs.cartodb.com/cartodb-platform/maps-api.html) tiler. It extends
|
||||
[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
|
||||
* 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
|
||||
* allows tiles to be styled individually
|
||||
* provides a link to varnish high speed cache
|
||||
* provides a ``infowindow`` endpoint for windshaft (DEPRECATED)
|
||||
* provides a ``map_metadata`` endpoint for windshaft (DEPRECATED)
|
||||
* provides signed template maps API
|
||||
(http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps)
|
||||
* provides a [template maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Template-maps.md)
|
||||
|
||||
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]
|
||||
- node-0.8.x+
|
||||
- PostgreSQL-8.3+
|
||||
- PostGIS-1.5.0+
|
||||
- Redis 2.4.0+ (http://www.redis.io)
|
||||
- Mapnik 2.0 or 2.1
|
||||
- For cache control (optional)
|
||||
- CartoDB-SQL-API 1.0.0+
|
||||
- CartoDB 0.9.5+ (for `CDB_QueryTables`)
|
||||
- Varnish (http://www.varnish-cache.org)
|
||||
|
||||
[for cache control]
|
||||
- CartoDB-SQL-API 1.0.0+
|
||||
- CartoDB 0.9.5+ (for ``CDB_QueryTables``)
|
||||
- Varnish (http://www.varnish-cache.org)
|
||||
|
||||
[for running the testsuite]
|
||||
- Imagemagick (http://www.imagemagick.org)
|
||||
- For running the testsuite
|
||||
- ImageMagick (http://www.imagemagick.org)
|
||||
|
||||
Configure
|
||||
---------
|
||||
@@ -75,59 +74,16 @@ there may be out-of-sync records in there.
|
||||
Take a look: http://redis.io/commands
|
||||
|
||||
|
||||
URLs
|
||||
----
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
**TILES**
|
||||
|
||||
[GET] subdomain.cartodb.com/tiles/:table_name/:z/:x/:y.[png|png8|grid.json]
|
||||
|
||||
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
|
||||
The [docs directory](https://github.com/CartoDB/Windshaft-cartodb/tree/master/docs) contains different documentation
|
||||
resources, from higher level to more detailed ones:
|
||||
The [Maps API](https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md) defined the endpoints and their
|
||||
expected parameters and outputs.
|
||||
|
||||
|
||||
**STYLE**
|
||||
Examples
|
||||
--------
|
||||
|
||||
[GET/POST] subdomain.cartodb.com/tiles/:table_name/style
|
||||
|
||||
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.
|
||||
[CartoDB's Map Gallery](http://cartodb.com/gallery/) showcases several examples of visualisations built on top of this.
|
||||
|
||||
62
app.js
62
app.js
@@ -7,17 +7,21 @@
|
||||
* environments: [development, production]
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
RedisPool = require('redis-mpool')
|
||||
;
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var _ = require('underscore');
|
||||
|
||||
var ENV;
|
||||
if ( process.argv[2] ) {
|
||||
ENV = process.argv[2];
|
||||
} else if ( process.env.NODE_ENV ) {
|
||||
ENV = process.env.NODE_ENV;
|
||||
} else {
|
||||
ENV = 'development';
|
||||
}
|
||||
|
||||
if ( process.argv[2] ) ENV = process.argv[2];
|
||||
else if ( process.env['NODE_ENV'] ) ENV = process.env['NODE_ENV'];
|
||||
else ENV = 'development';
|
||||
|
||||
process.env['NODE_ENV'] = ENV;
|
||||
process.env.NODE_ENV = ENV;
|
||||
|
||||
// sanity check
|
||||
if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
|
||||
@@ -26,14 +30,12 @@ if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
// set environment specific variables
|
||||
global.environment = require(__dirname + '/config/environments/' + ENV);
|
||||
global.environment.api_hostname = require('os').hostname().split('.')[0];
|
||||
|
||||
global.log4js = require('log4js');
|
||||
log4js_config = {
|
||||
var log4js_config = {
|
||||
appenders: [],
|
||||
replaceConsole:true
|
||||
};
|
||||
@@ -60,25 +62,18 @@ if ( global.environment.log_filename ) {
|
||||
);
|
||||
}
|
||||
|
||||
if ( global.environment.rollbar ) {
|
||||
log4js_config.appenders.push({
|
||||
type: __dirname + "/lib/cartodb/log4js_rollbar.js",
|
||||
options: global.environment.rollbar
|
||||
});
|
||||
}
|
||||
|
||||
log4js.configure(log4js_config, { cwd: __dirname });
|
||||
global.logger = log4js.getLogger();
|
||||
global.log4js.configure(log4js_config, { cwd: __dirname });
|
||||
global.logger = global.log4js.getLogger();
|
||||
|
||||
var redisOpts = _.extend(global.environment.redis, { name: 'windshaft' }),
|
||||
redisPool = new RedisPool(redisOpts);
|
||||
|
||||
// Include cartodb_windshaft only _after_ the "global" variable is set
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/28
|
||||
var CartodbWindshaft = require('./lib/cartodb/cartodb_windshaft'),
|
||||
var cartodbWindshaft = require('./lib/cartodb/cartodb_windshaft'),
|
||||
serverOptions = require('./lib/cartodb/server_options')(redisPool);
|
||||
|
||||
ws = CartodbWindshaft(serverOptions);
|
||||
var ws = cartodbWindshaft(serverOptions);
|
||||
|
||||
if (global.statsClient) {
|
||||
redisPool.on('status', function(status) {
|
||||
@@ -100,29 +95,20 @@ ws.listen(global.environment.port, global.environment.host);
|
||||
var version = require("./package").version;
|
||||
|
||||
ws.on('listening', function() {
|
||||
console.log("Windshaft tileserver " + version + " started on "
|
||||
+ global.environment.host + ':' + global.environment.port
|
||||
+ " (" + ENV + ")");
|
||||
});
|
||||
|
||||
// DEPRECATED, use SIGUSR2
|
||||
process.on('SIGUSR1', function() {
|
||||
console.log('WARNING: handling of SIGUSR1 by Windshaft-CartoDB is deprecated, please send SIGUSR2 instead');
|
||||
ws.dumpCacheStats();
|
||||
});
|
||||
|
||||
process.on('SIGUSR2', function() {
|
||||
ws.dumpCacheStats();
|
||||
console.log(
|
||||
"Windshaft tileserver %s started on %s:%s (%s)",
|
||||
version, global.environment.host, global.environment.port, ENV
|
||||
);
|
||||
});
|
||||
|
||||
process.on('SIGHUP', function() {
|
||||
global.log4js.clearAndShutdownAppenders(function() {
|
||||
global.log4js.configure(log4js_config);
|
||||
global.logger = log4js.getLogger();
|
||||
global.logger = global.log4js.getLogger();
|
||||
console.log('Log files reloaded');
|
||||
});
|
||||
});
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
logger.error('Uncaught exception: ' + err.stack);
|
||||
global.logger.error('Uncaught exception: ' + err.stack);
|
||||
});
|
||||
|
||||
BIN
assets/default-placeholder.png
Normal file
BIN
assets/default-placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
assets/default-placeholder@2x.png
Normal file
BIN
assets/default-placeholder@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
assets/render-timeout-fallback.png
Normal file
BIN
assets/render-timeout-fallback.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
assets/render-timeout-fallback@2x.png
Normal file
BIN
assets/render-timeout-fallback@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -14,13 +14,11 @@ var config = {
|
||||
// Base url for the Templated Maps API
|
||||
// "/api/v1/map/named" is the new API,
|
||||
// "/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|/u/:user/api/v1/map/named)'
|
||||
// Base url for the Detached Maps API
|
||||
// "maps" is the the new API,
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
|
||||
// Base url for the Inline Maps and Table Maps API
|
||||
,base_url_legacy: '/tiles/:table'
|
||||
,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -86,15 +84,61 @@ var config = {
|
||||
,renderer: {
|
||||
// Milliseconds since last access before renderer cache item expires
|
||||
cache_ttl: 60000,
|
||||
metatile: 4,
|
||||
bufferSize: 64,
|
||||
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
|
||||
poolSize: 8,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
// wasted time.
|
||||
metatile: 2,
|
||||
|
||||
// Buffer size is the tickness in pixel of a buffer
|
||||
// around the rendered (meta?)tile.
|
||||
//
|
||||
// This is important for labels and other marker that overlap tile boundaries.
|
||||
// Setting to 128 ensures no render artifacts.
|
||||
// 64 may have artifacts but is faster.
|
||||
// Less important if we can turn metatiling on.
|
||||
bufferSize: 64,
|
||||
|
||||
// SQL queries will be wrapped with ST_SnapToGrid
|
||||
// Snapping all points of the geometry to a regular grid
|
||||
snapToGrid: false,
|
||||
|
||||
// SQL queries will be wrapped with ST_ClipByBox2D
|
||||
// Returning the portion of a geometry falling within a rectangle
|
||||
// It will only work if snapToGrid is enabled
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
|
||||
limits: {
|
||||
// Time in milliseconds a render request can take before it fails, some notes:
|
||||
// - 0 means no render limit
|
||||
// - it considers metatiling, naive implementation: (render timeout) * (number of tiles in metatile)
|
||||
render: 0,
|
||||
// As the render request will finish even if timed out, whether it should be placed in the internal
|
||||
// cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
|
||||
// the same tile will result in an immediate response, however that will use a lot of more application
|
||||
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
|
||||
// internal cache.
|
||||
cacheOnTimeout: true
|
||||
}
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
proxy: undefined, // the url for a proxy server
|
||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
]
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
// if provided the http renderer will use it instead of throw an error
|
||||
fallbackImage: {
|
||||
type: 'fs', // 'fs' and 'url' supported
|
||||
src: __dirname + '/../../assets/default-placeholder.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
@@ -127,26 +171,6 @@ var config = {
|
||||
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
|
||||
}
|
||||
}
|
||||
,sqlapi: {
|
||||
protocol: 'http',
|
||||
// If "host" is given, it will be used
|
||||
// to connect to the SQL-API without a
|
||||
// DNS lookup
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
// The "domain" part will be appended to
|
||||
// the cartodb username and passed to
|
||||
// SQL-API requests in the Host HTTP header
|
||||
domain: 'localhost.lan',
|
||||
version: 'v1',
|
||||
// Maximum lenght of SQL query for GET
|
||||
// requests. Longer queries will be sent
|
||||
// using POST. Defaults to 2048
|
||||
max_get_sql_length: 2048,
|
||||
// Maximum time to wait for a response,
|
||||
// in milliseconds. Defaults to 100.
|
||||
timeout: 100
|
||||
}
|
||||
,varnish: {
|
||||
host: 'localhost',
|
||||
port: 6082, // the por for the telnet interface where varnish is listening to
|
||||
@@ -168,6 +192,15 @@ var config = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
,disabled_file: 'pids/disabled'
|
||||
|
||||
// Use this as a feature flags enabling/disabling mechanism
|
||||
,enabledFeatures: {
|
||||
// whether it should intercept tile render errors an act based on them, enabled by default.
|
||||
onTileErrorStrategy: true,
|
||||
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
|
||||
cdbQueryTablesFromPostgres: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -14,13 +14,11 @@ var config = {
|
||||
// Base url for the Templated Maps API
|
||||
// "/api/v1/map/named" is the new API,
|
||||
// "/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|/u/:user/api/v1/map/named)'
|
||||
// Base url for the Detached Maps API
|
||||
// "maps" is the the new API,
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
|
||||
// Base url for the Inline Maps and Table Maps API
|
||||
,base_url_legacy: '/tiles/:table'
|
||||
,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -80,15 +78,61 @@ var config = {
|
||||
,renderer: {
|
||||
// Milliseconds since last access before renderer cache item expires
|
||||
cache_ttl: 60000,
|
||||
metatile: 4,
|
||||
bufferSize: 64,
|
||||
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
|
||||
poolSize: 8,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
// wasted time.
|
||||
metatile: 2,
|
||||
|
||||
// Buffer size is the tickness in pixel of a buffer
|
||||
// around the rendered (meta?)tile.
|
||||
//
|
||||
// This is important for labels and other marker that overlap tile boundaries.
|
||||
// Setting to 128 ensures no render artifacts.
|
||||
// 64 may have artifacts but is faster.
|
||||
// Less important if we can turn metatiling on.
|
||||
bufferSize: 64,
|
||||
|
||||
// SQL queries will be wrapped with ST_SnapToGrid
|
||||
// Snapping all points of the geometry to a regular grid
|
||||
snapToGrid: false,
|
||||
|
||||
// SQL queries will be wrapped with ST_ClipByBox2D
|
||||
// Returning the portion of a geometry falling within a rectangle
|
||||
// It will only work if snapToGrid is enabled
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
|
||||
limits: {
|
||||
// Time in milliseconds a render request can take before it fails, some notes:
|
||||
// - 0 means no render limit
|
||||
// - it considers metatiling, naive implementation: (render timeout) * (number of tiles in metatile)
|
||||
render: 0,
|
||||
// As the render request will finish even if timed out, whether it should be placed in the internal
|
||||
// cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
|
||||
// the same tile will result in an immediate response, however that will use a lot of more application
|
||||
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
|
||||
// internal cache.
|
||||
cacheOnTimeout: true
|
||||
}
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
proxy: undefined, // the url for a proxy server
|
||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
]
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
// if provided the http renderer will use it instead of throw an error
|
||||
fallbackImage: {
|
||||
type: 'fs', // 'fs' and 'url' supported
|
||||
src: __dirname + '/../../assets/default-placeholder.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
@@ -121,26 +165,6 @@ var config = {
|
||||
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
|
||||
}
|
||||
}
|
||||
,sqlapi: {
|
||||
protocol: 'https',
|
||||
// If "host" is given, it will be used
|
||||
// to connect to the SQL-API without a
|
||||
// DNS lookup
|
||||
//host: '127.0.0.1',
|
||||
port: 8080,
|
||||
// The "domain" part will be appended to
|
||||
// the cartodb username and passed to
|
||||
// SQL-API requests in the Host HTTP header
|
||||
domain: 'cartodb.com',
|
||||
version: 'v2',
|
||||
// Maximum lenght of SQL query for GET
|
||||
// requests. Longer queries will be sent
|
||||
// using POST. Defaults to 2048
|
||||
max_get_sql_length: 2048,
|
||||
// Maximum time to wait for a response,
|
||||
// in milliseconds. Defaults to 100.
|
||||
timeout: 100
|
||||
}
|
||||
,varnish: {
|
||||
host: 'localhost',
|
||||
port: 6082, // the por for the telnet interface where varnish is listening to
|
||||
@@ -160,15 +184,6 @@ var config = {
|
||||
https: 'cartocdn.global.ssl.fastly.net'
|
||||
}
|
||||
}
|
||||
// Optional rollbar support
|
||||
,rollbar: {
|
||||
token: 'secret',
|
||||
// See http://github.com/rollbar/node_rollbar#configuration-reference
|
||||
options: {
|
||||
endpoint: 'https://api.rollbar.com/api/1/',
|
||||
handler: 'inline'
|
||||
}
|
||||
}
|
||||
// Settings for the health check available at /health
|
||||
,health: {
|
||||
enabled: true,
|
||||
@@ -177,6 +192,15 @@ var config = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
,disabled_file: 'pids/disabled'
|
||||
|
||||
// Use this as a feature flags enabling/disabling mechanism
|
||||
,enabledFeatures: {
|
||||
// whether it should intercept tile render errors an act based on them, enabled by default.
|
||||
onTileErrorStrategy: true,
|
||||
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
|
||||
cdbQueryTablesFromPostgres: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -14,13 +14,11 @@ var config = {
|
||||
// Base url for the Templated Maps API
|
||||
// "/api/v1/maps/named" is the new API,
|
||||
// "/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|/u/:user/api/v1/map/named)'
|
||||
// Base url for the Detached Maps API
|
||||
// "/api/v1/maps" is the the new API,
|
||||
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/maps|/tiles/layergroup)'
|
||||
// Base url for the Inline Maps and Table Maps API
|
||||
,base_url_legacy: '/tiles/:table'
|
||||
,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -80,15 +78,61 @@ var config = {
|
||||
,renderer: {
|
||||
// Milliseconds since last access before renderer cache item expires
|
||||
cache_ttl: 60000,
|
||||
metatile: 4,
|
||||
bufferSize: 64,
|
||||
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
|
||||
poolSize: 8,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
// wasted time.
|
||||
metatile: 2,
|
||||
|
||||
// Buffer size is the tickness in pixel of a buffer
|
||||
// around the rendered (meta?)tile.
|
||||
//
|
||||
// This is important for labels and other marker that overlap tile boundaries.
|
||||
// Setting to 128 ensures no render artifacts.
|
||||
// 64 may have artifacts but is faster.
|
||||
// Less important if we can turn metatiling on.
|
||||
bufferSize: 64,
|
||||
|
||||
// SQL queries will be wrapped with ST_SnapToGrid
|
||||
// Snapping all points of the geometry to a regular grid
|
||||
snapToGrid: false,
|
||||
|
||||
// SQL queries will be wrapped with ST_ClipByBox2D
|
||||
// Returning the portion of a geometry falling within a rectangle
|
||||
// It will only work if snapToGrid is enabled
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
|
||||
limits: {
|
||||
// Time in milliseconds a render request can take before it fails, some notes:
|
||||
// - 0 means no render limit
|
||||
// - it considers metatiling, naive implementation: (render timeout) * (number of tiles in metatile)
|
||||
render: 0,
|
||||
// As the render request will finish even if timed out, whether it should be placed in the internal
|
||||
// cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
|
||||
// the same tile will result in an immediate response, however that will use a lot of more application
|
||||
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
|
||||
// internal cache.
|
||||
cacheOnTimeout: true
|
||||
}
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
proxy: undefined, // the url for a proxy server
|
||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
]
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
// if provided the http renderer will use it instead of throw an error
|
||||
fallbackImage: {
|
||||
type: 'fs', // 'fs' and 'url' supported
|
||||
src: __dirname + '/../../assets/default-placeholder.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
@@ -121,26 +165,6 @@ var config = {
|
||||
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
|
||||
}
|
||||
}
|
||||
,sqlapi: {
|
||||
protocol: 'https',
|
||||
// If "host" is given, it will be used
|
||||
// to connect to the SQL-API without a
|
||||
// DNS lookup
|
||||
//host: '127.0.0.1',
|
||||
port: 8080,
|
||||
// The "domain" part will be appended to
|
||||
// the cartodb username and passed to
|
||||
// SQL-API requests in the Host HTTP header
|
||||
domain: 'cartodb.com',
|
||||
version: 'v2',
|
||||
// Maximum lenght of SQL query for GET
|
||||
// requests. Longer queries will be sent
|
||||
// using POST. Defaults to 2048
|
||||
max_get_sql_length: 2048,
|
||||
// Maximum time to wait for a response,
|
||||
// in milliseconds. Defaults to 100.
|
||||
timeout: 100
|
||||
}
|
||||
,varnish: {
|
||||
host: 'localhost',
|
||||
port: 6082, // the por for the telnet interface where varnish is listening to
|
||||
@@ -160,15 +184,6 @@ var config = {
|
||||
https: 'cartocdn.global.ssl.fastly.net'
|
||||
}
|
||||
}
|
||||
// Optional rollbar support
|
||||
,rollbar: {
|
||||
token: 'secret',
|
||||
// See http://github.com/rollbar/node_rollbar#configuration-reference
|
||||
options: {
|
||||
endpoint: 'https://api.rollbar.com/api/1/',
|
||||
handler: 'inline'
|
||||
}
|
||||
}
|
||||
// Settings for the health check available at /health
|
||||
,health: {
|
||||
enabled: false,
|
||||
@@ -177,6 +192,15 @@ var config = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
,disabled_file: 'pids/disabled'
|
||||
|
||||
// Use this as a feature flags enabling/disabling mechanism
|
||||
,enabledFeatures: {
|
||||
// whether it should intercept tile render errors an act based on them, enabled by default.
|
||||
onTileErrorStrategy: true,
|
||||
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
|
||||
cdbQueryTablesFromPostgres: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -14,13 +14,11 @@ var config = {
|
||||
// Base url for the Templated Maps API
|
||||
// "/api/v1/map/named" is the new API,
|
||||
// "/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|/u/:user/api/v1/map/named)'
|
||||
// Base url for the Detached Maps API
|
||||
// "maps" is the the new API,
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/tiles/layergroup)'
|
||||
// Base url for the Inline Maps and Table Maps API
|
||||
,base_url_legacy: '/tiles/:table'
|
||||
,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -50,7 +48,7 @@ var config = {
|
||||
,postgres: {
|
||||
// Parameters to pass to datasource plugin of mapnik
|
||||
// See http://github.com/mapnik/mapnik/wiki/PostGIS
|
||||
user: "testpublicuser",
|
||||
user: "test_windshaft_publicuser",
|
||||
password: "public",
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
@@ -80,15 +78,63 @@ var config = {
|
||||
,renderer: {
|
||||
// Milliseconds since last access before renderer cache item expires
|
||||
cache_ttl: 60000,
|
||||
metatile: 4,
|
||||
bufferSize: 64,
|
||||
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
|
||||
poolSize: 8,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
// wasted time.
|
||||
metatile: 2,
|
||||
|
||||
// Buffer size is the tickness in pixel of a buffer
|
||||
// around the rendered (meta?)tile.
|
||||
//
|
||||
// This is important for labels and other marker that overlap tile boundaries.
|
||||
// Setting to 128 ensures no render artifacts.
|
||||
// 64 may have artifacts but is faster.
|
||||
// Less important if we can turn metatiling on.
|
||||
bufferSize: 64,
|
||||
|
||||
// SQL queries will be wrapped with ST_SnapToGrid
|
||||
// Snapping all points of the geometry to a regular grid
|
||||
snapToGrid: false,
|
||||
|
||||
// SQL queries will be wrapped with ST_ClipByBox2D
|
||||
// Returning the portion of a geometry falling within a rectangle
|
||||
// It will only work if snapToGrid is enabled
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
|
||||
limits: {
|
||||
// Time in milliseconds a render request can take before it fails, some notes:
|
||||
// - 0 means no render limit
|
||||
// - it considers metatiling, naive implementation: (render timeout) * (number of tiles in metatile)
|
||||
render: 0,
|
||||
// As the render request will finish even if timed out, whether it should be placed in the internal
|
||||
// cache or it should be fully discarded. When placed in the internal cache another attempt to retrieve
|
||||
// the same tile will result in an immediate response, however that will use a lot of more application
|
||||
// memory. If we want to enforce this behaviour we have to implement a cache eviction policy for the
|
||||
// internal cache.
|
||||
cacheOnTimeout: true
|
||||
}
|
||||
},
|
||||
http: {
|
||||
timeout: 2000, // the timeout in ms for a http tile request
|
||||
proxy: undefined, // the url for a proxy server
|
||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
]
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png',
|
||||
// for testing purposes
|
||||
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
// if provided the http renderer will use it instead of throw an error
|
||||
fallbackImage: {
|
||||
type: 'fs', // 'fs' and 'url' supported
|
||||
src: __dirname + '/../../assets/default-placeholder.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
@@ -121,28 +167,6 @@ var config = {
|
||||
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
|
||||
}
|
||||
}
|
||||
,sqlapi: {
|
||||
protocol: 'http',
|
||||
// If "host" is given, it will be used
|
||||
// to connect to the SQL-API without a
|
||||
// DNS lookup
|
||||
host: '127.0.0.1',
|
||||
port: 1080,
|
||||
// The "domain" part will be appended to
|
||||
// the cartodb username and passed to
|
||||
// SQL-API requests in the Host HTTP header
|
||||
domain: 'donot_look_this_up',
|
||||
// This port will be used by "make check" for testing purposes
|
||||
// It must be available
|
||||
version: 'v1',
|
||||
// Maximum lenght of SQL query for GET
|
||||
// requests. Longer queries will be sent
|
||||
// using POST. Defaults to 2048
|
||||
max_get_sql_length: 2048,
|
||||
// Maximum time to wait for a response,
|
||||
// in milliseconds. Defaults to 100.
|
||||
timeout: 100
|
||||
}
|
||||
,varnish: {
|
||||
host: '',
|
||||
port: null, // the por for the telnet interface where varnish is listening to
|
||||
@@ -164,6 +188,15 @@ var config = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
,disabled_file: 'pids/disabled'
|
||||
|
||||
// Use this as a feature flags enabling/disabling mechanism
|
||||
,enabledFeatures: {
|
||||
// whether it should intercept tile render errors an act based on them, enabled by default.
|
||||
onTileErrorStrategy: true,
|
||||
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
|
||||
cdbQueryTablesFromPostgres: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
5
configure
vendored
5
configure
vendored
@@ -56,9 +56,8 @@ while test -n "$1"; do
|
||||
ENVIRONMENT=`echo "$1" | cut -d= -f2`
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option '$1'" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
echo "Unused option '$1'" >&2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
252
docs/Map-API.md
252
docs/Map-API.md
@@ -1,14 +1,14 @@
|
||||
## Maps API
|
||||
|
||||
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account, and you can apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
|
||||
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and you can apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
|
||||
|
||||
You can create two types of maps with the Maps API:
|
||||
|
||||
- **Anonymous maps**
|
||||
Maps that can be created using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example]({{ '/cartodb-platform/cartodb-js.html' | prepend: site.baseurl }}).
|
||||
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example]({{ '/cartodb-platform/cartodb-js.html' | prepend: site.baseurl }}).
|
||||
|
||||
- **Named maps**
|
||||
Maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.
|
||||
There are also maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@@ -34,10 +34,10 @@ $.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
url: 'http://documentation.cartodb.com/api/v1/map',
|
||||
url: 'https://documentation.cartodb.com/api/v1/map',
|
||||
data: JSON.stringify(mapconfig),
|
||||
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);
|
||||
}
|
||||
})
|
||||
@@ -79,19 +79,15 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
|
||||
|
||||
<div class="code-title notitle code-request"></div>
|
||||
```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`, `cdn_url`, 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.
|
||||
|
||||
Here is an example response:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"cdn_url": {
|
||||
"http": "ashbu.cartocdn.com",
|
||||
"https": "cartocdn-ashbu.global.ssl.fastly.net"
|
||||
},
|
||||
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
|
||||
"last_updated": "1970-01-01T00:00:00.000Z"
|
||||
}
|
||||
@@ -100,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:
|
||||
|
||||
```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
|
||||
@@ -109,9 +105,9 @@ The following concepts are the same for every endpoint in the API except when it
|
||||
|
||||
### Auth
|
||||
|
||||
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
|
||||
|
||||
@@ -129,7 +125,7 @@ If you use JSONP, the 200 HTTP code is always returned so the JavaScript client
|
||||
|
||||
### CORS support
|
||||
|
||||
All the endpoints which might be accessed using a web browser add CORS headers and allow OPTIONS method.
|
||||
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.
|
||||
|
||||
## Anonymous Maps
|
||||
|
||||
@@ -171,7 +167,7 @@ The response includes:
|
||||
The ID for that map, used to compose the URL for the tiles. The final URL is:
|
||||
|
||||
```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**
|
||||
@@ -187,7 +183,7 @@ The response includes:
|
||||
|
||||
<div class="code-title code-request with-result">REQUEST</div>
|
||||
```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>
|
||||
@@ -205,19 +201,19 @@ curl 'http://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application
|
||||
The tiles can be accessed using:
|
||||
|
||||
```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:
|
||||
|
||||
```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:
|
||||
|
||||
```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:
|
||||
@@ -226,7 +222,7 @@ Which returns JSON with the attributes defined, like:
|
||||
{ c: 1, d: 2 }
|
||||
```
|
||||
|
||||
Notice UTF Grid and attributes endpoints need an integer parameter, ``layer``. That number is the 0-based index of the layer inside the mapconfig. So in this case 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example mapconfig. If a second layer was available it could be returned with 1, a third layer with 2, etc.
|
||||
Notice UTF Grid and attributes endpoints need an integer parameter, ``layer``. That number is the 0-based index of the layer inside the mapconfig. In this case, 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example mapconfig. If a second layer was available it could be returned with 1, a third layer with 2, etc.
|
||||
|
||||
### Create JSONP
|
||||
|
||||
@@ -271,12 +267,12 @@ callback({
|
||||
|
||||
### Remove
|
||||
|
||||
Anonymous maps cannot be removed by an API call. They will expire after about five minutes but sometimes longer. If an anonymous map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time returns to the map an attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.
|
||||
Anonymous maps cannot be removed by an API call. They will expire after about five minutes but sometimes longer. If an anonymous map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.
|
||||
|
||||
|
||||
## Named Maps
|
||||
|
||||
Named maps are essentially the same as anonymous maps except the mapconfig is stored on the server and the map is given a unique name. Two other big differences are that you can create named maps from private data, and that users without an API Key can see them even though they are from that private data.
|
||||
Named maps are essentially the same as anonymous maps except the mapconfig is stored on the server and the map is given a unique name. Two other big differences are: you can create named maps from private data and that users without an API Key can see them even though they are from that private data.
|
||||
|
||||
The main two differences compared to anonymous maps are:
|
||||
|
||||
@@ -284,9 +280,9 @@ The main two differences compared to anonymous maps are:
|
||||
This allows you to control who is able to see the map based on a token auth
|
||||
|
||||
- **templates**
|
||||
Since the mapconfig is static it can contain some variables so the client can modify the map's appearance using those variables
|
||||
Since the mapconfig is static it can contain some variables so the client can modify the map's appearance using those variables.
|
||||
|
||||
Template maps are persistent with no preset expiration. They can only be created or deleted by a CartoDB user with a valid API_KEY (see [auth section](http://docs.cartodb.com/cartodb-platform/maps-api.html#auth)).
|
||||
Template maps are persistent with no preset expiration. They can only be created or deleted by a CartoDB user with a valid API_KEY (see auth section).
|
||||
|
||||
### Create
|
||||
|
||||
@@ -345,7 +341,7 @@ POST /api/v1/map/named
|
||||
- **auth**:
|
||||
- **method** `"token"` or `"open"` (the default if no `"method"` is given).
|
||||
- **valid_tokens** when `"method"` is set to `"token"`, the values listed here allow you to instantiate the named map.
|
||||
- **placeholders**: Variables not listed here are not substituted. Variable not provided at instantiation time trigger an error. A default is required for optional variables. Type specification is used for quoting, to avoid injections see template format section below.
|
||||
- **placeholders**: Variables not listed here are not substituted. Variables not provided at instantiation time trigger an error. A default is required for optional variables. Type specification is used for quoting, to avoid injections see template format section below.
|
||||
- **layergroup**: the layer list definition. This is the MapConfig explained in anonymous maps. See [MapConfig documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.1.0.md) for more info.
|
||||
|
||||
#### Template Format
|
||||
@@ -371,7 +367,7 @@ The placeholder type will determine the kind of escaping for the associated valu
|
||||
- **number** can only contain numerical representation
|
||||
- **css_color** can only contain color names or hex-values
|
||||
|
||||
Placeholder default values will be used whenever new values are not provided as options at the time of creation on the client. They can also be used to test the template by creating a default version with now options provided.
|
||||
Placeholder default values will be used whenever new values are not provided as options at the time of creation on the client. They can also be used to test the template by creating a default version with new options provided.
|
||||
|
||||
When using templates, be very careful about your selections as they can give broad access to your data if they are defined losely.
|
||||
|
||||
@@ -413,7 +409,7 @@ POST /api/v1/map/named/:template_name
|
||||
}
|
||||
```
|
||||
|
||||
The fields you pass as `params.json` depend on the variables allowed by the named map. If there are variables missing it will raise an error (HTTP 400).
|
||||
The fields you pass as `params.json` depend on the variables allowed by the named map. If there are variables missing it will raise an error (HTTP 400)
|
||||
|
||||
- **auth_token** *optional* if the named map needs auth
|
||||
|
||||
@@ -446,7 +442,7 @@ curl -X POST \
|
||||
}
|
||||
```
|
||||
|
||||
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see anonymous map section). However, you'll need to show the `auth_token`, if required by the template.
|
||||
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see anonymous map section). However you'll need to show the `auth_token`, if required by the template.
|
||||
|
||||
### Using JSONP
|
||||
|
||||
@@ -549,7 +545,7 @@ If a template with the same name does NOT exist, a 400 HTTP response is generate
|
||||
|
||||
### Delete
|
||||
|
||||
Delete the specified template map from the server and disables any previously initialized versions of the map.
|
||||
Delete the specified template map from the server and it disables any previously initialized versions of the map.
|
||||
|
||||
#### Definition
|
||||
|
||||
@@ -576,7 +572,7 @@ curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_nam
|
||||
}
|
||||
```
|
||||
|
||||
On success, a 204 (No Content) response would be issued. Otherwise a 4xx response with with an error will be returned.
|
||||
On success, a 204 (No Content) response will be issued. Otherwise a 4xx response with an error will be returned.
|
||||
|
||||
### Listing Available Templates
|
||||
|
||||
@@ -671,4 +667,196 @@ cartodb.createLayer('map_dom_id',layerSource)
|
||||
|
||||
```
|
||||
|
||||
See [CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js.html) methods [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetparamskey-value) and [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetauthtokenauthtoken).
|
||||
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js.html) has methods for accessing your named maps.
|
||||
|
||||
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
|
||||
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
|
||||
|
||||
##Static Maps API
|
||||
|
||||
The Static Maps API can be initiated using both named and anonymous maps using the 'layergroupid' token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats.
|
||||
|
||||
### Maps API endpoints
|
||||
|
||||
Begin by instantiating either a named or anonymous map using the `layergroupid token` as demonstrated in the Maps API documentation above. The `layergroupsid token` calls to the map and allows for parameters in the definition to generate static images.
|
||||
|
||||
##### Definition
|
||||
|
||||
<div class="code-title notitle code-request"></div>
|
||||
```bash
|
||||
GET /api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format
|
||||
```
|
||||
|
||||
##### Params
|
||||
|
||||
* **:token**: the layergroupid token from the map instantiation
|
||||
* **:z**: the zoom level of the map
|
||||
* **:lat**: the latitude for the center of the map
|
||||
* **:lng**: the longitude for the center of the map
|
||||
* **:width**: the width in pixels for the output image
|
||||
* **:height**: the height in pixels for the output image
|
||||
* **:format**: the format for the image, supported types: `png`, `jpg`
|
||||
* **jpg** will have a default quality of 85.
|
||||
|
||||
#### Bounding Box
|
||||
|
||||
##### Definition
|
||||
|
||||
<div class="code-title notitle code-request"></div>
|
||||
```bash
|
||||
GET /api/v1/map/static/bbox/:token/:bbox/:width/:height.:format`
|
||||
```
|
||||
|
||||
##### Params
|
||||
|
||||
* **:token**: the layergroupid token from the map instantiation
|
||||
* **:bbox**: the bounding box in WGS 84 (EPSG:4326), comma separated values for:
|
||||
- LowerCorner longitude, in decimal degrees (aka most western)
|
||||
- LowerCorner latitude, in decimal degrees (aka most southern)
|
||||
- UpperCorner longitude, in decimal degrees (aka most eastern)
|
||||
- UpperCorner latitude, in decimal degrees (aka most northern)
|
||||
* **:width**: the width in pixels for the output image
|
||||
* **:height**: the height in pixels for the output image
|
||||
* **:format**: the format for the image, supported types: `png`, `jpg`
|
||||
* **jpg** will have a default quality of 85.
|
||||
|
||||
Note: you can see this endpoint as:
|
||||
|
||||
```bash
|
||||
GET /api/v1/map/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format`
|
||||
```
|
||||
|
||||
####Layers
|
||||
|
||||
The Static Maps API allows for multiple layers of incorporation into the `MapConfig` to allow for maximum versatility in creating a static map. The examples below were used to generate the static image example in the next section, and appear in the specific order designated.
|
||||
|
||||
**Basemaps**
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "http",
|
||||
"options": {
|
||||
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
|
||||
"subdomains": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
]
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
By manipulating the `"urlTemplate"` custom basemaps can be used in generating static images. Supported map types for the Static Maps API are:
|
||||
|
||||
'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
|
||||
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
|
||||
'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
|
||||
'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
|
||||
|
||||
**Mapnik**
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "mapnik",
|
||||
"options": {
|
||||
"sql": "select null::geometry the_geom_webmercator",
|
||||
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
|
||||
"cartocss_version": "2.2.0"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
**CartoDB**
|
||||
|
||||
```javascript
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from park",
|
||||
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
|
||||
"cartocss_version": "2.1.1"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
Additoinally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
|
||||
|
||||
|
||||
####Caching
|
||||
|
||||
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
|
||||
|
||||
####Limits
|
||||
|
||||
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
|
||||
* Image resolution by default is set to 72 DPI
|
||||
* JPEG quality by default is 85%
|
||||
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
After instantiating a map from a CartoDB account:
|
||||
|
||||
<div class="code-title code-request with-result">REQUEST</div>
|
||||
```bash
|
||||
GET /api/v1/map/static/center/4b615ff367e498e770e7d05e99181873:1420231989550.8699/14/40.71502926732618/-73.96039009094238/600/400.png
|
||||
```
|
||||
|
||||
####Response
|
||||
<div clas="wrap"><p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>,</div>
|
||||
|
||||
####MapConfig
|
||||
|
||||
For this map, the multiple layers, order, and stylings are defined by the MapConfig.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"version": "1.3.0-alpha",
|
||||
"layers": [
|
||||
{
|
||||
"type": "http",
|
||||
"options": {
|
||||
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
|
||||
"subdomains": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "mapnik",
|
||||
"options": {
|
||||
"sql": "select null::geometry the_geom_webmercator",
|
||||
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
|
||||
"cartocss_version": "2.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from park",
|
||||
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
|
||||
"cartocss_version": "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from residential_zoning_2009",
|
||||
"cartocss": "/** simple visualization */\n\n#residential_zoning_2009{\n polygon-fill: #c7eae5;\n polygon-opacity: 1;\n line-color: #FFF;\n line-width: 0.2;\n line-opacity: 0.5;\n}",
|
||||
"cartocss_version": "2.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"sql": "select * from nycha_developments_july2011",
|
||||
"cartocss": "/** simple visualization */\n\n#nycha_developments_july2011{\n polygon-fill: #ef3b2c;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
|
||||
"cartocss_version": "2.1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
56
docs/MapConfig-NamedMaps-extension.md
Normal file
56
docs/MapConfig-NamedMaps-extension.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 1. Purpose
|
||||
|
||||
This specification describes an extension for
|
||||
[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version.
|
||||
|
||||
|
||||
# 2. Changes over specification
|
||||
|
||||
This extension introduces a new layer type so it's possible to use a named map by its name as a layer.
|
||||
|
||||
## 2.1 Named layers definition
|
||||
|
||||
```javascript
|
||||
{
|
||||
// REQUIRED
|
||||
// string, `named` is the only supported value
|
||||
type: "named",
|
||||
|
||||
// REQUIRED
|
||||
// object, set `named` map layers configuration
|
||||
options: {
|
||||
|
||||
// REQUIRED
|
||||
// string, the name for the named map to use
|
||||
name: "world_borders",
|
||||
|
||||
// OPTIONAL
|
||||
// object, the replacement values for the named map's template placeholders
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details
|
||||
config: {
|
||||
"color": "#000"
|
||||
},
|
||||
|
||||
// OPTIONAL
|
||||
// string array, the authorized tokens in case the named map has auth method set to `token`
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details
|
||||
auth_tokens: [
|
||||
"token1",
|
||||
"token2"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2.2 Limitations
|
||||
|
||||
1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition.
|
||||
2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very
|
||||
same user account.
|
||||
|
||||
|
||||
# History
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Initial version
|
||||
128
docs/Routes.md
Normal file
128
docs/Routes.md
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
```
|
||||
@@ -25,9 +25,7 @@ Again, each inner timer may have several inner timers.
|
||||
- **TemplateMaps_instance**: time to retrieve a map template instance, see *getTemplate* and *authorizedByCert*
|
||||
- **affectedTables**: time to check what are the affected tables for adding the cache channel, see *addCacheChannel*
|
||||
- **authorize**: time to authorize a request, see *authorizedByAPIKey*, *authorizedByCert*, *authorizedBySigner*
|
||||
- **authorizedByAPIKey**: time to authorize using an API KEY
|
||||
- **authorizedByCert**: time to authorize a template instantiation
|
||||
- **authorizedBySigner**: time to authorize a request with auth_token
|
||||
- **findLastUpdated**: time to retrieve the last update time for a list of tables, see *affectedTables*
|
||||
- **generateCacheChannel**: time to generate the headers for the cache channel based on the request, see *addCacheChannel*
|
||||
- **getSignerMapKey**: time to retrieve from redis the authorized user for a template map
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
var sqlApi = require('../sql/sql_api'),
|
||||
PSQL = require('cartodb-psql');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var step = require('step');
|
||||
|
||||
function QueryTablesApi() {
|
||||
function QueryTablesApi(pgConnection, metadataBackend) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.metadataBackend = metadataBackend;
|
||||
}
|
||||
|
||||
var affectedTableRegexCache = {
|
||||
@@ -13,33 +15,12 @@ var affectedTableRegexCache = {
|
||||
|
||||
module.exports = QueryTablesApi;
|
||||
|
||||
QueryTablesApi.prototype.getLastUpdatedTime = function (username, api_key, tableNames, callback) {
|
||||
var sql = '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(',') +
|
||||
'])';
|
||||
|
||||
// call sql api
|
||||
sqlApi.query(username, api_key, sql, function(err, rows){
|
||||
if (err){
|
||||
var msg = err.message ? err.message : err;
|
||||
callback(new Error('could not find last updated timestamp: ' + msg));
|
||||
return;
|
||||
}
|
||||
// when the table has not updated_at means it hasn't been changed so a default last_updated is set
|
||||
var last_updated = 0;
|
||||
if(rows.length !== 0) {
|
||||
last_updated = rows[0].max || 0;
|
||||
}
|
||||
|
||||
callback(null, last_updated*1000);
|
||||
});
|
||||
};
|
||||
|
||||
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, options, sql, callback) {
|
||||
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, callback) {
|
||||
|
||||
var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)';
|
||||
|
||||
runQuery(username, options, query, handleAffectedTablesInQueryRows, callback);
|
||||
this.runQuery(username, query, handleAffectedTablesInQueryRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesInQueryRows(err, rows, callback) {
|
||||
@@ -54,7 +35,7 @@ function handleAffectedTablesInQueryRows(err, rows, callback) {
|
||||
callback(null, tableNames);
|
||||
}
|
||||
|
||||
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, options, sql, callback) {
|
||||
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, sql, callback) {
|
||||
|
||||
var query = [
|
||||
'WITH querytables AS (',
|
||||
@@ -65,7 +46,7 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam
|
||||
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
|
||||
].join(' ');
|
||||
|
||||
runQuery(username, options, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
this.runQuery(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
@@ -89,20 +70,40 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
}
|
||||
|
||||
|
||||
function runQuery(username, options, query, queryHandler, callback) {
|
||||
if (shouldQueryPostgresDirectly()) {
|
||||
var psql = new PSQL(options);
|
||||
psql.query(query, function(err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
var rows = resultSet.rows || [];
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
} else {
|
||||
sqlApi.query(username, options.api_key, query, function(err, rows) {
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
|
||||
var self = this;
|
||||
|
||||
var params = {};
|
||||
|
||||
step(
|
||||
function setAuth() {
|
||||
self.pgConnection.setDBAuth(username, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
self.pgConnection.setDBConn(username, params, this);
|
||||
},
|
||||
function executeQuery(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var psql = new PSQL({
|
||||
user: params.dbuser,
|
||||
pass: params.dbpass,
|
||||
host: params.dbhost,
|
||||
port: params.dbport,
|
||||
dbname: params.dbname
|
||||
});
|
||||
psql.query(query, function(err, resultSet) {
|
||||
resultSet = resultSet || {};
|
||||
var rows = resultSet.rows || [];
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
function prepareSql(sql) {
|
||||
@@ -113,10 +114,3 @@ function prepareSql(sql) {
|
||||
.replace(affectedTableRegexCache.pixel_height, '1')
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
function shouldQueryPostgresDirectly() {
|
||||
return global.environment
|
||||
&& global.environment.enabledFeatures
|
||||
&& global.environment.enabledFeatures.cdbQueryTablesFromPostgres;
|
||||
}
|
||||
|
||||
96
lib/cartodb/backends/pg_connection.js
Normal file
96
lib/cartodb/backends/pg_connection.js
Normal file
@@ -0,0 +1,96 @@
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
function PgConnection(metadataBackend) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
}
|
||||
|
||||
module.exports = PgConnection;
|
||||
|
||||
|
||||
// Set db authentication parameters to those of the given username
|
||||
//
|
||||
// @param username the cartodb username, mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set auth options into
|
||||
// added params are: "dbuser" and "dbpassword"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBAuth = function(username, params, callback) {
|
||||
var self = this;
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
step(
|
||||
function getId() {
|
||||
self.metadataBackend.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw 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;
|
||||
|
||||
self.metadataBackend.getUserDBPass(username, this);
|
||||
},
|
||||
function(err, user_password) {
|
||||
if (err) throw err;
|
||||
user_params.user_password = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
// mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set connection options into
|
||||
// added params are: "dbname", "dbhost"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
|
||||
var self = this;
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(params, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
step(
|
||||
function getConnectionParams() {
|
||||
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
function extendParams(err, dbParams){
|
||||
if (err) throw err;
|
||||
// we don't want null values or overwrite a non public user
|
||||
if (params.dbuser != 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
if ( dbParams ) _.extend(params, dbParams);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
var _ = require('underscore'),
|
||||
Varnish = require('node-varnish'),
|
||||
varnish_queue = null;
|
||||
|
||||
function init(host, port, secret) {
|
||||
varnish_queue = new Varnish.VarnishQueue(host, port, secret);
|
||||
varnish_queue.on('error', function(e) {
|
||||
console.log("[CACHE VALIDATOR ERROR] " + e);
|
||||
});
|
||||
}
|
||||
|
||||
function invalidate_db(dbname, table) {
|
||||
var cmd = 'purge obj.http.X-Cache-Channel ~ "^' + dbname +
|
||||
':(.*'+ table +'.*)|(table)$"';
|
||||
try{
|
||||
varnish_queue.run_cmd(cmd, false);
|
||||
} catch (e) {
|
||||
console.log("[CACHE VALIDATOR ERROR] could not queue command " +
|
||||
cmd + " -- " + e);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
invalidate_db: invalidate_db
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, Windshaft = require('windshaft')
|
||||
, TemplateMaps = require('./template_maps.js')
|
||||
, Cache = require('./cache_validator')
|
||||
, os = require('os')
|
||||
, HealthCheck = require('./monitoring/health_check')
|
||||
;
|
||||
var _ = require('underscore');
|
||||
var step = require('step');
|
||||
var Windshaft = require('windshaft');
|
||||
var os = require('os');
|
||||
var HealthCheck = require('./monitoring/health_check');
|
||||
|
||||
if ( ! process.env['PGAPPNAME'] )
|
||||
process.env['PGAPPNAME']='cartodb_tiler';
|
||||
if ( ! process.env.PGAPPNAME )
|
||||
process.env.PGAPPNAME='cartodb_tiler';
|
||||
|
||||
var CartodbWindshaft = function(serverOptions) {
|
||||
// Perform keyword substitution in statsd
|
||||
@@ -20,27 +17,12 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
var redisPool = serverOptions.redis.pool
|
||||
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
|
||||
var redisPool = serverOptions.redis.pool ||
|
||||
require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
|
||||
|
||||
var cartoData = require('cartodb-redis')({pool: redisPool});
|
||||
|
||||
if(serverOptions.cache_enabled) {
|
||||
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
|
||||
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
|
||||
serverOptions.afterStateChange = function(req, data, callback) {
|
||||
Cache.invalidate_db(req.params.dbname, req.params.table);
|
||||
callback(null, data);
|
||||
}
|
||||
}
|
||||
|
||||
serverOptions.beforeStateChange = function(req, callback) {
|
||||
var err = null;
|
||||
if ( ! req.params.hasOwnProperty('_authorizedByApiKey') ) {
|
||||
err = new Error("map state cannot be changed by unauthenticated request!");
|
||||
}
|
||||
callback(err, req);
|
||||
};
|
||||
var templateMaps = serverOptions.templateMaps;
|
||||
|
||||
// This is for Templated maps
|
||||
//
|
||||
@@ -48,27 +30,24 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
//
|
||||
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
|
||||
|
||||
var templateMapsOpts = {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
};
|
||||
var templateMaps = new TemplateMaps(redisPool, templateMapsOpts);
|
||||
serverOptions.templateMaps = templateMaps;
|
||||
|
||||
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
|
||||
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
|
||||
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);
|
||||
|
||||
if (serverOptions.varnish_purge_enabled) {
|
||||
function invalidateNamedMap(owner, templateName) {
|
||||
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
|
||||
if (err) {
|
||||
console.warn('Cache: surrogate key invalidation failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
function invalidateNamedMap (owner, templateName) {
|
||||
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
|
||||
if (err) {
|
||||
console.warn('Cache: surrogate key invalidation failed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (serverOptions.varnish_purge_enabled) {
|
||||
['update', 'delete'].forEach(function(eventType) {
|
||||
templateMaps.on(eventType, invalidateNamedMap);
|
||||
});
|
||||
@@ -108,7 +87,7 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
}
|
||||
}
|
||||
var req = res.req;
|
||||
Step (
|
||||
step (
|
||||
function addCacheChannel() {
|
||||
if ( ! req ) {
|
||||
// having no associated request can happen when
|
||||
@@ -130,10 +109,11 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
//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"));
|
||||
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" +
|
||||
// mapCreateRoutes.join("\n"));
|
||||
serverOptions.addCacheChannel(that, req, this);
|
||||
},
|
||||
function sendResponse(err, added) {
|
||||
function sendResponse(err/*, added*/) {
|
||||
if ( err ) console.log(err + err.stack);
|
||||
ws_sendResponse.apply(that, thatArgs);
|
||||
return null;
|
||||
@@ -158,7 +138,14 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
|
||||
var TemplateMapsController = require('./controllers/template_maps'),
|
||||
templateMapsController = new TemplateMapsController(
|
||||
ws, serverOptions, templateMaps, cartoData, template_baseurl, surrogateKeysCache, NamedMapsCacheEntry
|
||||
ws,
|
||||
serverOptions,
|
||||
templateMaps,
|
||||
cartoData,
|
||||
template_baseurl,
|
||||
surrogateKeysCache,
|
||||
NamedMapsCacheEntry,
|
||||
serverOptions.pgConnection
|
||||
);
|
||||
templateMapsController.register(ws);
|
||||
|
||||
@@ -166,71 +153,6 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
* END Routing
|
||||
******************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Helper to allow access to the layer to be used in the maps infowindow popup.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/infowindow', function(req, res){
|
||||
ws.doCORS(res);
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getInfowindow(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
ws.sendError(res, {error: err.message}, 500, 'GET INFOWINDOW', err);
|
||||
//ws.sendResponse(res, [{error: err.message}, 500]);
|
||||
} else {
|
||||
ws.sendResponse(res, [{infowindow: data}, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Helper to allow access to metadata to be used in embedded maps.
|
||||
*/
|
||||
ws.get(serverOptions.base_url + '/map_metadata', function(req, res){
|
||||
ws.doCORS(res);
|
||||
Step(
|
||||
function(){
|
||||
serverOptions.getMapMetadata(req, this);
|
||||
},
|
||||
function(err, data){
|
||||
if (err){
|
||||
ws.sendError(res, {error: err.message}, 500, 'GET MAP_METADATA', err);
|
||||
//ws.sendResponse(res, [err.message, 500]);
|
||||
} else {
|
||||
ws.sendResponse(res, [{map_metadata: data}, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper API to allow per table tile cache (and sql cache) to be invalidated remotely.
|
||||
* TODO: Move?
|
||||
*/
|
||||
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
|
||||
if ( req.profiler && req.profiler.statsd_client ) {
|
||||
req.profiler.start('windshaft-cartodb.flush_cache');
|
||||
}
|
||||
ws.doCORS(res);
|
||||
Step(
|
||||
function flushCache(){
|
||||
serverOptions.flushCache(req, serverOptions.cache_enabled ? Cache : null, this);
|
||||
},
|
||||
function sendResponse(err, data){
|
||||
if (err){
|
||||
ws.sendError(res, {error: err.message}, 500, 'DELETE CACHE', err);
|
||||
//ws.sendResponse(res, [500]);
|
||||
} else {
|
||||
ws.sendResponse(res, [{status: 'ok'}, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive);
|
||||
ws.get('/health', function(req, res) {
|
||||
var healthConfig = global.environment.health || {};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
var Step = require('step');
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
var CdbRequest = require('../models/cdb_request');
|
||||
|
||||
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
|
||||
NamedMapsCacheEntry) {
|
||||
NamedMapsCacheEntry, pgConnection) {
|
||||
this.app = app;
|
||||
this.serverOptions = serverOptions;
|
||||
this.templateMaps = templateMaps;
|
||||
@@ -10,10 +11,12 @@ function TemplateMapsController(app, serverOptions, templateMaps, metadataBacken
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.NamedMapsCacheEntry = NamedMapsCacheEntry;
|
||||
this.pgConnection = pgConnection;
|
||||
}
|
||||
|
||||
module.exports = TemplateMapsController;
|
||||
|
||||
var cdbRequest = new CdbRequest();
|
||||
|
||||
TemplateMapsController.prototype.register = function(app) {
|
||||
app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this));
|
||||
@@ -32,9 +35,9 @@ TemplateMapsController.prototype.create = function(req, res) {
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = self.serverOptions.userByReq(req);
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
|
||||
Step(
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
@@ -79,10 +82,10 @@ TemplateMapsController.prototype.update = function(req, res) {
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
@@ -99,8 +102,7 @@ TemplateMapsController.prototype.update = function(req, res) {
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
err = new Error("Invalid template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err = new Error("Invalid template id '" + req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
@@ -140,10 +142,9 @@ TemplateMapsController.prototype.retrieve = function(req, res) {
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var template;
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var tpl_id;
|
||||
Step(
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
@@ -157,10 +158,10 @@ TemplateMapsController.prototype.retrieve = function(req, res) {
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot get template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
var templateNotFoundErr = new Error("Cannot get template id '" + req.params.template_id +
|
||||
"' for user '" + cdbuser + "'");
|
||||
templateNotFoundErr.http_status = 404;
|
||||
throw templateNotFoundErr;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
@@ -202,10 +203,9 @@ TemplateMapsController.prototype.destroy = function(req, res) {
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var template;
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var tpl_id;
|
||||
Step(
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
@@ -219,16 +219,16 @@ TemplateMapsController.prototype.destroy = function(req, res) {
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot find template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
var templateNotFoundErr = new Error("Cannot find template id '" + req.params.template_id +
|
||||
"' for user '" + cdbuser + "'");
|
||||
templateNotFoundErr.http_status = 404;
|
||||
throw templateNotFoundErr;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val){
|
||||
function prepareResponse(err/*, tpl_val*/){
|
||||
if ( err ) throw err;
|
||||
return { status: 'ok' };
|
||||
},
|
||||
@@ -256,9 +256,9 @@ TemplateMapsController.prototype.list = function(req, res) {
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
|
||||
Step(
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
@@ -298,7 +298,7 @@ TemplateMapsController.prototype.instantiate = function(req, res) {
|
||||
if ( req.profiler && req.profiler.statsd_client) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
}
|
||||
Step(
|
||||
step(
|
||||
function() {
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json') {
|
||||
throw new Error('template POST data must be of type application/json, it is instead ');
|
||||
@@ -310,7 +310,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");
|
||||
return next();
|
||||
};
|
||||
@@ -325,7 +325,7 @@ TemplateMapsController.prototype.jsonp = function(req, res) {
|
||||
if ( req.profiler && req.profiler.statsd_client) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
}
|
||||
Step(
|
||||
step(
|
||||
function() {
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
throw new Error('callback parameter should be present and be a function name');
|
||||
@@ -355,14 +355,13 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
|
||||
var template;
|
||||
var layergroup;
|
||||
var fakereq; // used for call to createLayergroup
|
||||
var cdbuser = self.serverOptions.userByReq(req);
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
// Format of template_id: [<template_owner>]@<template_id>
|
||||
var tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] && tpl_id[0] != cdbuser ) {
|
||||
var err = new Error('Cannot instanciate map of user "'
|
||||
+ tpl_id[0] + '" on database of user "'
|
||||
+ cdbuser + '"');
|
||||
var err = new Error('Cannot instanciate map of user "' + tpl_id[0] + '" on database of user "' + cdbuser +
|
||||
'"');
|
||||
err.http_status = 403;
|
||||
callback(err);
|
||||
return;
|
||||
@@ -370,7 +369,7 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
var auth_token = req.query.auth_token;
|
||||
Step(
|
||||
step(
|
||||
function getTemplate(){
|
||||
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
@@ -409,7 +408,13 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa
|
||||
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
|
||||
if ( err ) throw err;
|
||||
layergroup = instance;
|
||||
fakereq = { query: {}, params: {}, headers: _.clone(req.headers),
|
||||
fakereq = {
|
||||
query: {},
|
||||
params: {
|
||||
user: req.params.user
|
||||
},
|
||||
headers: _.clone(req.headers),
|
||||
context: _.clone(req.context),
|
||||
method: req.method,
|
||||
res: res,
|
||||
profiler: req.profiler
|
||||
@@ -463,13 +468,13 @@ TemplateMapsController.prototype.finish_instantiation = function(err, response,
|
||||
|
||||
TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callback) {
|
||||
var self = this;
|
||||
Step(
|
||||
step(
|
||||
function setAuth() {
|
||||
self.serverOptions.setDBAuth(cdbuser, params, this);
|
||||
self.pgConnection.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if ( err ) throw err;
|
||||
self.serverOptions.setDBConn(cdbuser, params, this);
|
||||
self.pgConnection.setDBConn(cdbuser, params, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
var rollbar = require("rollbar");
|
||||
|
||||
/**
|
||||
* Rollbar Appender. Sends logging events to Rollbar using node-rollbar
|
||||
*
|
||||
* @param config object with rollbar configuration data
|
||||
* {
|
||||
* token: 'your-secret-token',
|
||||
* options: node-rollbar options
|
||||
* }
|
||||
*/
|
||||
function rollbarAppender(config) {
|
||||
|
||||
var opt = config.options;
|
||||
rollbar.init(opt.token, opt.options);
|
||||
|
||||
return function(loggingEvent) {
|
||||
/*
|
||||
For logger.trace('one','two','three'):
|
||||
{ startTime: Wed Mar 12 2014 16:27:40 GMT+0100 (CET),
|
||||
categoryName: '[default]',
|
||||
data: [ 'one', 'two', 'three' ],
|
||||
level: { level: 5000, levelStr: 'TRACE' },
|
||||
logger: { category: '[default]', _events: { log: [Object] } } }
|
||||
*/
|
||||
|
||||
// Levels:
|
||||
// TRACE 5000
|
||||
// DEBUG 10000
|
||||
// INFO 20000
|
||||
// WARN 30000
|
||||
// ERROR 40000
|
||||
// FATAL 50000
|
||||
//
|
||||
// We only log error and higher errors
|
||||
//
|
||||
if ( loggingEvent.level.level < 40000 ) return;
|
||||
|
||||
rollbar.reportMessage(loggingEvent.data);
|
||||
};
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
return rollbarAppender(config);
|
||||
}
|
||||
|
||||
exports.name = "rollbar";
|
||||
exports.appender = rollbarAppender;
|
||||
exports.configure = configure;
|
||||
26
lib/cartodb/models/cdb_request.js
Normal file
26
lib/cartodb/models/cdb_request.js
Normal file
@@ -0,0 +1,26 @@
|
||||
function CdbRequest() {
|
||||
this.RE_USER_FROM_HOST = new RegExp(global.environment.user_from_host ||
|
||||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = CdbRequest;
|
||||
|
||||
|
||||
CdbRequest.prototype.userByReq = function(req) {
|
||||
var host = req.headers.host;
|
||||
if (req.params.user) {
|
||||
return req.params.user;
|
||||
}
|
||||
var mat = host.match(this.RE_USER_FROM_HOST);
|
||||
if ( ! mat ) {
|
||||
console.error("Pattern '" + this.RE_USER_FROM_HOST + "' does not match hostname '" + host + "'");
|
||||
return;
|
||||
}
|
||||
// console.log("Matches: "); console.dir(mat);
|
||||
if ( mat.length !== 2 ) {
|
||||
console.error("Pattern '" + this.RE_USER_FROM_HOST + "' gave unexpected matches against '" + host + "': ", mat);
|
||||
return;
|
||||
}
|
||||
return mat[1];
|
||||
};
|
||||
120
lib/cartodb/models/mapconfig_named_layers_adapter.js
Normal file
120
lib/cartodb/models/mapconfig_named_layers_adapter.js
Normal file
@@ -0,0 +1,120 @@
|
||||
var queue = require('queue-async');
|
||||
var _ = require('underscore');
|
||||
var Datasource = require('windshaft').Datasource;
|
||||
|
||||
function MapConfigNamedLayersAdapter(templateMaps) {
|
||||
this.templateMaps = templateMaps;
|
||||
}
|
||||
|
||||
module.exports = MapConfigNamedLayersAdapter;
|
||||
|
||||
MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbMetadata, callback) {
|
||||
var self = this;
|
||||
|
||||
var adaptLayersQueue = queue(layers.length);
|
||||
|
||||
function adaptLayer(layer, done) {
|
||||
if (isNamedTypeLayer(layer)) {
|
||||
|
||||
if (!layer.options.name) {
|
||||
return done(new Error('Missing Named Map `name` in layer options'));
|
||||
}
|
||||
|
||||
var templateName = layer.options.name;
|
||||
var templateConfigParams = layer.options.config || {};
|
||||
var templateAuthTokens = layer.options.auth_tokens;
|
||||
|
||||
self.templateMaps.getTemplate(username, templateName, function(err, template) {
|
||||
if (err || !template) {
|
||||
return done(new Error("Template '" + templateName + "' of user '" + username + "' not found"));
|
||||
}
|
||||
|
||||
if (self.templateMaps.isAuthorized(template, templateAuthTokens)) {
|
||||
var nestedNamedLayers = template.layergroup.layers.filter(function(layer) {
|
||||
return layer.type === 'named';
|
||||
});
|
||||
|
||||
if (nestedNamedLayers.length > 0) {
|
||||
var nestedNamedMapsError = new Error('Nested named layers are not allowed');
|
||||
// nestedNamedMapsError.http_status = 400;
|
||||
return done(nestedNamedMapsError);
|
||||
}
|
||||
|
||||
try {
|
||||
var templateLayergroupConfig = self.templateMaps.instance(template, templateConfigParams);
|
||||
return done(null, {
|
||||
datasource: true,
|
||||
layers: templateLayergroupConfig.layers
|
||||
});
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
} else {
|
||||
var unauthorizedError = new Error("Unauthorized '" + templateName + "' template instantiation");
|
||||
unauthorizedError.http_status = 403;
|
||||
return done(unauthorizedError);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
return done(null, {
|
||||
datasource: false,
|
||||
layers: [layer]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var datasourceBuilder = new Datasource.Builder();
|
||||
|
||||
function layersAdaptQueueFinish(err, layersResults) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!layersResults || layersResults.length === 0) {
|
||||
return callback(new Error('Missing layers array from layergroup config'));
|
||||
}
|
||||
|
||||
var layers = [],
|
||||
currentLayerIndex = 0;
|
||||
|
||||
layersResults.forEach(function(layersResult) {
|
||||
|
||||
layersResult.layers.forEach(function(layer) {
|
||||
layers.push(layer);
|
||||
if (layersResult.datasource) {
|
||||
datasourceBuilder.withLayerDatasource(currentLayerIndex, {
|
||||
user: dbAuth.dbuser
|
||||
});
|
||||
}
|
||||
currentLayerIndex++;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return callback(null, layers, datasourceBuilder.build());
|
||||
}
|
||||
|
||||
|
||||
var dbAuth = {};
|
||||
|
||||
if (_.some(layers, isNamedTypeLayer)) {
|
||||
// Lazy load dbAuth
|
||||
dbMetadata.setDBAuth(username, dbAuth, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
layers.forEach(function(layer) {
|
||||
adaptLayersQueue.defer(adaptLayer, layer);
|
||||
});
|
||||
adaptLayersQueue.awaitAll(layersAdaptQueueFinish);
|
||||
});
|
||||
} else {
|
||||
return callback(null, layers, datasourceBuilder.build());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function isNamedTypeLayer(layer) {
|
||||
return layer.type === 'named';
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
var _ = require('underscore'),
|
||||
dot = require('dot'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
Step = require('step');
|
||||
var fs = require('fs');
|
||||
var step = require('step');
|
||||
|
||||
function HealthCheck(metadataBackend, tilelive) {
|
||||
this.metadataBackend = metadataBackend;
|
||||
@@ -12,24 +9,9 @@ function HealthCheck(metadataBackend, tilelive) {
|
||||
module.exports = HealthCheck;
|
||||
|
||||
|
||||
var mapnikOptions = {
|
||||
query: {
|
||||
metatile: 1,
|
||||
poolSize: 4,
|
||||
bufferSize: 64
|
||||
},
|
||||
protocol: 'mapnik:',
|
||||
slashes: true,
|
||||
xml: null
|
||||
};
|
||||
|
||||
var xmlTemplate = dot.template(fs.readFileSync(path.resolve(__dirname, 'map-config.xml'), 'utf-8'));
|
||||
|
||||
HealthCheck.prototype.check = function(config, callback) {
|
||||
|
||||
var self = this,
|
||||
startTime,
|
||||
result = {
|
||||
var result = {
|
||||
redis: {
|
||||
ok: false
|
||||
},
|
||||
@@ -40,50 +22,23 @@ HealthCheck.prototype.check = function(config, callback) {
|
||||
ok: false
|
||||
}
|
||||
};
|
||||
mapnikXmlParams = config;
|
||||
|
||||
Step(
|
||||
function getDBParams() {
|
||||
startTime = Date.now();
|
||||
self.metadataBackend.getAllUserDBParams(config.username, this);
|
||||
step(
|
||||
function getManualDisable() {
|
||||
fs.readFile(global.environment.disabled_file, this);
|
||||
},
|
||||
function loadMapnik(err, dbParams) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
result.redis = {
|
||||
ok: !err,
|
||||
elapsed: Date.now() - startTime,
|
||||
size: Object.keys(dbParams).length
|
||||
};
|
||||
mapnikOptions.xml = xmlTemplate(mapnikXmlParams);
|
||||
|
||||
startTime = Date.now();
|
||||
self.tilelive.load(mapnikOptions, this);
|
||||
function handleDisabledFile(err, data) {
|
||||
var next = this;
|
||||
if (err) {
|
||||
return next();
|
||||
}
|
||||
if (!!data) {
|
||||
err = new Error(data);
|
||||
err.http_status = 503;
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
function getTile(err, source) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
result.mapnik = {
|
||||
ok: !err,
|
||||
elapsed: Date.now() - startTime
|
||||
};
|
||||
|
||||
startTime = Date.now();
|
||||
source.getTile(config.z, config.x, config.y, this);
|
||||
},
|
||||
function handleTile(err, tile) {
|
||||
result.tile = {
|
||||
ok: !err
|
||||
};
|
||||
|
||||
if (tile) {
|
||||
result.tile.elapsed = Date.now() - startTime;
|
||||
result.tile.size = tile.length;
|
||||
}
|
||||
|
||||
function handleResult(err) {
|
||||
callback(err, result);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,49 +1,48 @@
|
||||
var _ = require('underscore')
|
||||
, Step = require('step')
|
||||
, Cache = require('./cache_validator')
|
||||
, QueryTablesApi = require('./api/query_tables_api')
|
||||
, crypto = require('crypto')
|
||||
, LZMA = require('lzma').LZMA
|
||||
;
|
||||
var _ = require('underscore');
|
||||
var step = require('step');
|
||||
var LZMA = require('lzma').LZMA;
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
|
||||
// This is for backward compatibility with 1.3.3
|
||||
if ( _.isUndefined(global.environment.sqlapi.domain) ) {
|
||||
// Only use "host" as "domain" if it contains alphanumeric characters
|
||||
var host = global.environment.sqlapi.host;
|
||||
if ( host && host.match(/[a-zA-Z]/) ) {
|
||||
global.environment.sqlapi.domain = host;
|
||||
}
|
||||
}
|
||||
var QueryTablesApi = require('./api/query_tables_api');
|
||||
var PgConnection = require('./backends/pg_connection');
|
||||
var TemplateMaps = require('./template_maps.js');
|
||||
var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter');
|
||||
var CdbRequest = require('./models/cdb_request');
|
||||
|
||||
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
|
||||
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
|
||||
|
||||
// Whitelist query parameters and attach format
|
||||
var REQUEST_QUERY_PARAMS_WHITELIST = [
|
||||
'sql',
|
||||
'geom_type',
|
||||
'cache_buster',
|
||||
'cache_policy',
|
||||
'callback',
|
||||
'interactivity',
|
||||
'config',
|
||||
'map_key',
|
||||
'api_key',
|
||||
'auth_token',
|
||||
'style',
|
||||
'style_version',
|
||||
'style_convert',
|
||||
'config',
|
||||
'scale_factor'
|
||||
'callback'
|
||||
];
|
||||
|
||||
module.exports = function(redisPool) {
|
||||
var redisOpts = redisPool ? {pool: redisPool} : global.environment.redis;
|
||||
var cartoData = require('cartodb-redis')(redisOpts),
|
||||
redisPool = redisPool || new RedisPool(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
|
||||
|
||||
var cartoData = require('cartodb-redis')({ pool: redisPool }),
|
||||
lzmaWorker = new LZMA(),
|
||||
queryTablesApi = new QueryTablesApi();
|
||||
pgConnection = new PgConnection(cartoData),
|
||||
queryTablesApi = new QueryTablesApi(pgConnection, cartoData),
|
||||
cdbRequest = new CdbRequest();
|
||||
|
||||
var rendererConfig = _.defaults(global.environment.renderer || {}, {
|
||||
cache_ttl: 60000, // milliseconds
|
||||
metatile: 4,
|
||||
bufferSize: 64,
|
||||
statsInterval: 60000
|
||||
statsInterval: 60000,
|
||||
mapnik: {
|
||||
poolSize: 8,
|
||||
metatile: 2,
|
||||
bufferSize: 64,
|
||||
snapToGrid: false,
|
||||
clipByBox2d: false,
|
||||
limits: {}
|
||||
},
|
||||
http: {}
|
||||
});
|
||||
|
||||
var me = {
|
||||
@@ -69,13 +68,7 @@ module.exports = function(redisPool) {
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
mapnik_version: global.environment.mapnik_version,
|
||||
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
|
||||
default_layergroup_ttl: global.environment.mapConfigTTL || 7200,
|
||||
gc_prob: 0.01 // @deprecated since Windshaft-1.8.0
|
||||
},
|
||||
mapnik: {
|
||||
poolSize: rendererConfig.poolSize,
|
||||
metatile: rendererConfig.metatile,
|
||||
bufferSize: rendererConfig.bufferSize
|
||||
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
|
||||
},
|
||||
statsd: global.environment.statsd,
|
||||
renderCache: {
|
||||
@@ -83,6 +76,7 @@ module.exports = function(redisPool) {
|
||||
statsInterval: rendererConfig.statsInterval
|
||||
},
|
||||
renderer: {
|
||||
mapnik: rendererConfig.mapnik,
|
||||
http: rendererConfig.http
|
||||
},
|
||||
redis: global.environment.redis,
|
||||
@@ -104,6 +98,16 @@ module.exports = function(redisPool) {
|
||||
// Re-use redisPool
|
||||
me.redis.pool = redisPool;
|
||||
|
||||
// Re-use pgConnection
|
||||
me.pgConnection = pgConnection;
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
me.templateMaps = templateMaps;
|
||||
|
||||
var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
|
||||
/* This whole block is about generating X-Cache-Channel { */
|
||||
|
||||
// TODO: review lifetime of elements of this cache
|
||||
@@ -116,136 +120,81 @@ module.exports = function(redisPool) {
|
||||
return dbName + ':' + tableNames.join(',');
|
||||
};
|
||||
|
||||
me.generateMD5 = function(data){
|
||||
var hash = crypto.createHash('md5');
|
||||
hash.update(data);
|
||||
return hash.digest('hex');
|
||||
};
|
||||
|
||||
me.generateCacheChannel = function(app, req, callback){
|
||||
// Build channelCache key
|
||||
var dbName = req.params.dbname;
|
||||
var cacheKey = [ dbName, req.params.token ].join(':');
|
||||
|
||||
// Build channelCache key
|
||||
var dbName = req.params.dbname;
|
||||
var cacheKey = [ dbName ];
|
||||
if ( req.params.token ) cacheKey.push(req.params.token);
|
||||
else if ( req.params.sql ) cacheKey.push( me.generateMD5(req.params.sql) );
|
||||
cacheKey = cacheKey.join(':');
|
||||
|
||||
var that = this;
|
||||
|
||||
Step (
|
||||
function checkCached() {
|
||||
if ( me.channelCache.hasOwnProperty(cacheKey) ) {
|
||||
callback(null, me.channelCache[cacheKey]);
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function extractSQL(err) {
|
||||
if ( err ) throw err;
|
||||
|
||||
if ( req.params.token ) {
|
||||
// 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 next = this;
|
||||
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');
|
||||
if ( err ) throw err;
|
||||
var sql = [];
|
||||
_.each(mapConfig.obj().layers, function(lyr) {
|
||||
sql.push(lyr.options.sql);
|
||||
});
|
||||
sql = sql.join(';');
|
||||
return sql;
|
||||
},
|
||||
function finish(err, sql) {
|
||||
next(err, sql);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! req.params.sql ) {
|
||||
return null; // no sql
|
||||
}
|
||||
|
||||
// We have sql, and no token...
|
||||
|
||||
// strip out windshaft/mapnik inserted sql if present
|
||||
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
|
||||
sql = (sql != null) ? sql[1] : req.params.sql;
|
||||
|
||||
return sql;
|
||||
},
|
||||
function findAffectedTables(err, sql) {
|
||||
if ( err ) throw err;
|
||||
if ( ! sql ) {
|
||||
if ( ! req.params.table ) {
|
||||
throw new Error("this request doesn't need an X-Cache-Channel generated");
|
||||
}
|
||||
return [req.params.table];
|
||||
}
|
||||
var user, key;
|
||||
var next = this;
|
||||
Step (
|
||||
function findUserKey() {
|
||||
if ( req.params.hasOwnProperty('_authorizedBySigner') ) {
|
||||
user = req.params._authorizedBySigner;
|
||||
cartoData.getUserMapKey(user, this);
|
||||
} else {
|
||||
user = that.userByReq(req);
|
||||
key = req.params.map_key || req.params.api_key;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
function getAffected(err, data) {
|
||||
if ( err ) throw err;
|
||||
if ( data ) {
|
||||
if ( req.profiler ) req.profiler.done('getSignerMapKey');
|
||||
key = data;
|
||||
}
|
||||
queryTablesApi.getAffectedTablesInQuery(user, {
|
||||
user: req.params.dbuser,
|
||||
pass: req.params.dbpass,
|
||||
host: req.params.dbhost,
|
||||
port: req.params.dbport,
|
||||
dbname: req.params.dbname,
|
||||
api_key: key
|
||||
}, sql, this); // in addCacheChannel
|
||||
},
|
||||
function finish(err, data) {
|
||||
next(err,data);
|
||||
}
|
||||
);
|
||||
},
|
||||
function buildCacheChannel(err, tableNames) {
|
||||
if ( err ) throw err;
|
||||
if (req.profiler && ! req.params.table ) {
|
||||
req.profiler.done('affectedTables');
|
||||
}
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
|
||||
// store for caching from me.generateCacheChannel
|
||||
// (not worth when table was specified in params)
|
||||
if ( ! req.params.table ) {
|
||||
me.channelCache[cacheKey] = cacheChannel;
|
||||
}
|
||||
return cacheChannel;
|
||||
},
|
||||
function finish(err, cacheChannel) {
|
||||
callback(err, cacheChannel);
|
||||
// no token means no tables associated
|
||||
if (!req.params.token) {
|
||||
return callback(null, this.buildCacheChannel(dbName, []));
|
||||
}
|
||||
);
|
||||
|
||||
step(
|
||||
function checkCached() {
|
||||
if ( me.channelCache.hasOwnProperty(cacheKey) ) {
|
||||
return callback(null, me.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(cdbRequest.userByReq(req), sql, this); // in addCacheChannel
|
||||
},
|
||||
function buildCacheChannel(err, tableNames) {
|
||||
assert.ifError(err);
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.done('affectedTables');
|
||||
}
|
||||
|
||||
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
|
||||
me.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
|
||||
@@ -260,11 +209,11 @@ module.exports = function(redisPool) {
|
||||
me.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');
|
||||
if (req.profiler) {
|
||||
req.profiler.start('addCacheChannel');
|
||||
}
|
||||
var res = req.res;
|
||||
var cache_policy = req.query.cache_policy;
|
||||
if ( req.params.token ) cache_policy = 'persist';
|
||||
if ( cache_policy == 'persist' ) {
|
||||
if ( req.params.token ) {
|
||||
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
|
||||
} else {
|
||||
var ttl = global.environment.varnish.ttl || 86400;
|
||||
@@ -283,8 +232,10 @@ module.exports = function(redisPool) {
|
||||
res.header('Last-Modified', lastUpdated.toUTCString());
|
||||
|
||||
me.generateCacheChannel(app, req, function(err, channel){
|
||||
if (req.profiler) req.profiler.done('generateCacheChannel');
|
||||
if (req.profiler) req.profiler.end();
|
||||
if (req.profiler) {
|
||||
req.profiler.done('generateCacheChannel');
|
||||
req.profiler.end();
|
||||
}
|
||||
if ( ! err ) {
|
||||
res.header('X-Cache-Channel', channel);
|
||||
cb(null, channel);
|
||||
@@ -296,10 +247,62 @@ module.exports = function(redisPool) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
|
||||
me.renderer.onTileErrorStrategy = function(err, tile, headers, stats, format, callback) {
|
||||
if (err && err.message === 'Render timed out' && format === 'png') {
|
||||
return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {});
|
||||
} else {
|
||||
return callback(err, tile, headers, stats);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
me.renderCache.beforeRendererCreate = function(req, callback) {
|
||||
var user = cdbRequest.userByReq(req);
|
||||
|
||||
var rendererOptions = {};
|
||||
|
||||
step(
|
||||
function getLimits(err) {
|
||||
assert.ifError(err);
|
||||
cartoData.getTilerRenderLimit(user, this);
|
||||
},
|
||||
function handleTilerLimits(err, renderLimit) {
|
||||
assert.ifError(err);
|
||||
rendererOptions.limits = {
|
||||
cacheOnTimeout: rendererConfig.mapnik.limits.cacheOnTimeout || false,
|
||||
render: renderLimit || rendererConfig.mapnik.limits.render || 0
|
||||
};
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, rendererOptions);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
|
||||
mapConfigNamedLayersAdapter.getLayers(cdbRequest.userByReq(req), requestMapConfig.layers, pgConnection,
|
||||
function(err, layers, datasource) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
return callback(null, requestMapConfig, datasource);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
|
||||
var token = response.layergroupid;
|
||||
|
||||
var username = this.userByReq(req);
|
||||
var username = cdbRequest.userByReq(req);
|
||||
|
||||
var tasksleft = 2; // redis key and affectedTables
|
||||
var errors = [];
|
||||
@@ -326,48 +329,38 @@ module.exports = function(redisPool) {
|
||||
// take place before proceeding. Error will be logged
|
||||
// asyncronously
|
||||
cartoData.incMapviewCount(username, mapconfig.stat_tag, function(err) {
|
||||
if (req.profiler) req.profiler.done('incMapviewCount');
|
||||
if ( err ) console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
||||
if (req.profiler) {
|
||||
req.profiler.done('incMapviewCount');
|
||||
}
|
||||
if ( err ) {
|
||||
console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
var sql = [];
|
||||
_.each(mapconfig.layers, function(lyr) {
|
||||
sql.push(lyr.options.sql);
|
||||
});
|
||||
sql = sql.join(';');
|
||||
var sql = mapconfig.layers.map(function(layer) {
|
||||
return layer.options.sql;
|
||||
}).join(';');
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var usr = this.userByReq(req);
|
||||
var key = req.params.map_key || req.params.api_key;
|
||||
|
||||
var cacheKey = dbName + ':' + token;
|
||||
|
||||
Step(
|
||||
step(
|
||||
function getAffectedTablesAndLastUpdatedTime() {
|
||||
queryTablesApi.getAffectedTablesAndLastUpdatedTime(usr, {
|
||||
user: req.params.dbuser,
|
||||
pass: req.params.dbpass,
|
||||
host: req.params.dbhost,
|
||||
port: req.params.dbport,
|
||||
dbname: req.params.dbname,
|
||||
api_key: key
|
||||
}, sql, this);
|
||||
queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
|
||||
},
|
||||
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
|
||||
if ( err ) throw err;
|
||||
if (req.profiler) {
|
||||
req.profiler.done('queryTablesAndLastUpdated');
|
||||
}
|
||||
assert.ifError(err);
|
||||
var cacheChannel = me.buildCacheChannel(dbName, result.affectedTables);
|
||||
me.channelCache[cacheKey] = cacheChannel;
|
||||
|
||||
if (req.res && req.method == 'GET') {
|
||||
var res = req.res;
|
||||
if ( req.query && req.query.cache_policy == 'persist' ) {
|
||||
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
|
||||
} else {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -385,114 +378,6 @@ module.exports = function(redisPool) {
|
||||
|
||||
/* X-Cache-Channel generation } */
|
||||
|
||||
me.re_userFromHost = new RegExp(
|
||||
global.environment.user_from_host ||
|
||||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
|
||||
);
|
||||
|
||||
me.userByReq = function(req) {
|
||||
var host = req.headers.host;
|
||||
var mat = host.match(this.re_userFromHost);
|
||||
if ( ! mat ) {
|
||||
console.error("ERROR: user pattern '" + this.re_userFromHost
|
||||
+ "' does not match hostname '" + host + "'");
|
||||
return;
|
||||
}
|
||||
// console.log("Matches: "); console.dir(mat);
|
||||
if ( ! mat.length === 2 ) {
|
||||
console.error("ERROR: pattern '" + this.re_userFromHost
|
||||
+ "' gave unexpected matches against '" + host + "': " + mat);
|
||||
return;
|
||||
}
|
||||
return mat[1];
|
||||
};
|
||||
|
||||
// Set db authentication parameters to those of the given username
|
||||
//
|
||||
// @param username the cartodb username, mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set auth options into
|
||||
// added params are: "dbuser" and "dbpassword"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBAuth = function(username, params, callback) {
|
||||
|
||||
var user_params = {};
|
||||
var auth_user = global.environment.postgres_auth_user;
|
||||
var auth_pass = global.environment.postgres_auth_pass;
|
||||
Step(
|
||||
function getId() {
|
||||
cartoData.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw 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;
|
||||
|
||||
cartoData.getUserDBPass(username, this);
|
||||
},
|
||||
function(err, user_password) {
|
||||
if (err) throw err;
|
||||
user_params['user_password'] = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Set db connection parameters to those for the given username
|
||||
//
|
||||
// @param dbowner cartodb username of database owner,
|
||||
// mapped to a database username
|
||||
// via CartodbRedis metadata records
|
||||
//
|
||||
// @param params the parameters to set connection options into
|
||||
// added params are: "dbname", "dbhost"
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
me.setDBConn = function(dbowner, params, callback) {
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(params, {
|
||||
dbuser: global.environment.postgres.user,
|
||||
dbpassword: global.environment.postgres.password,
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
Step(
|
||||
function getConnectionParams() {
|
||||
cartoData.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
function extendParams(err, dbParams){
|
||||
if (err) throw err;
|
||||
// we don't want null values or overwrite a non public user
|
||||
if (params.dbuser != 'publicuser' || !dbParams.dbuser) {
|
||||
delete dbParams.dbuser;
|
||||
}
|
||||
if ( dbParams ) _.extend(params, dbParams);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Check if a request is authorized by a signer
|
||||
//
|
||||
// @param req express request object
|
||||
@@ -500,33 +385,27 @@ module.exports = function(redisPool) {
|
||||
// null if the request is not signed by anyone
|
||||
// or will be a string cartodb username otherwise.
|
||||
//
|
||||
me.authorizedBySigner = function(req, callback)
|
||||
{
|
||||
if ( ! req.params.token || ! req.params.signer ) {
|
||||
//console.log("No signature provided"); // debugging
|
||||
callback(null, null); // no signer requested
|
||||
return;
|
||||
}
|
||||
|
||||
var signer = req.params.signer;
|
||||
var layergroup_id = req.params.token;
|
||||
var auth_token = req.params.auth_token;
|
||||
|
||||
//console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + " with auth_token " + auth_token);
|
||||
var mapStore = req.app.mapStore;
|
||||
if (!mapStore) {
|
||||
throw new Error('Unable to retrieve map configuration token');
|
||||
}
|
||||
|
||||
mapStore.load(layergroup_id, function(err, mapConfig) {
|
||||
if (err) {
|
||||
throw err;
|
||||
me.authorizedBySigner = function(req, callback) {
|
||||
if ( ! req.params.token || ! req.params.signer ) {
|
||||
return callback(null, null); // no signer requested
|
||||
}
|
||||
|
||||
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
|
||||
callback(null, authorized ? signer : null);
|
||||
});
|
||||
var signer = req.params.signer;
|
||||
var layergroup_id = req.params.token;
|
||||
var auth_token = req.params.auth_token;
|
||||
|
||||
var mapStore = req.app.mapStore;
|
||||
if (!mapStore) {
|
||||
throw new Error('Unable to retrieve map configuration token');
|
||||
}
|
||||
|
||||
mapStore.load(layergroup_id, function(err, mapConfig) {
|
||||
assert.ifError(err);
|
||||
|
||||
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
|
||||
|
||||
return callback(null, authorized ? signer : null);
|
||||
});
|
||||
};
|
||||
|
||||
// Check if a request is authorized by api_key
|
||||
@@ -547,13 +426,13 @@ module.exports = function(redisPool) {
|
||||
return;
|
||||
}
|
||||
//console.log("given ApiKey: " + givenKey);
|
||||
var user = me.userByReq(req);
|
||||
Step(
|
||||
var user = cdbRequest.userByReq(req);
|
||||
step(
|
||||
function (){
|
||||
cartoData.getUserMapKey(user, this);
|
||||
},
|
||||
function checkApiKey(err, val){
|
||||
if (err) throw err;
|
||||
assert.ifError(err);
|
||||
return ( val && givenKey == val ) ? 1 : 0;
|
||||
},
|
||||
function finish(err, authorized) {
|
||||
@@ -570,15 +449,17 @@ module.exports = function(redisPool) {
|
||||
*/
|
||||
me.authorize = function(req, callback) {
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
var user = cdbRequest.userByReq(req);
|
||||
|
||||
Step(
|
||||
step(
|
||||
function (){
|
||||
that.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function checkApiKey(err, authorized){
|
||||
if (req.profiler) req.profiler.done('authorizedByAPIKey');
|
||||
if (err) throw err;
|
||||
if (req.profiler) {
|
||||
req.profiler.done('authorizedByAPIKey');
|
||||
}
|
||||
assert.ifError(err);
|
||||
|
||||
// if not authorized by api_key, continue
|
||||
if (authorized !== 1) {
|
||||
@@ -588,28 +469,16 @@ module.exports = function(redisPool) {
|
||||
return;
|
||||
}
|
||||
|
||||
_.extend(req.params, { _authorizedByApiKey: true });
|
||||
|
||||
// authorized by api key, login as the given username and stop
|
||||
that.setDBAuth(user, req.params, function(err) {
|
||||
pgConnection.setDBAuth(user, req.params, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
function checkSignAuthorized(err, signed_by){
|
||||
if (err) throw err;
|
||||
if (req.profiler) {
|
||||
if ( req.params._authorizedByApiKey ) {
|
||||
req.profiler.done('setDBAuth');
|
||||
} else {
|
||||
req.profiler.done('authorizedBySigner');
|
||||
}
|
||||
}
|
||||
assert.ifError(err);
|
||||
|
||||
if ( ! signed_by ) {
|
||||
// request not authorized by signer.
|
||||
|
||||
// if table was given, continue to check table privacy
|
||||
if ( req.params.table ) return null;
|
||||
|
||||
// if no signer name was given, let dbparams and
|
||||
// PostgreSQL do the rest.
|
||||
@@ -624,30 +493,17 @@ module.exports = function(redisPool) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorized by "signed_by" !
|
||||
_.extend(req.params, { _authorizedBySigner: signed_by });
|
||||
that.setDBAuth(signed_by, req.params, function(err) {
|
||||
if (req.profiler) req.profiler.done('setDBAuth');
|
||||
pgConnection.setDBAuth(signed_by, req.params, function(err) {
|
||||
if (req.profiler) {
|
||||
req.profiler.done('setDBAuth');
|
||||
}
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
// NOTE: only used to get to table privacy
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getPrivacy(err, dbname){
|
||||
if (err) throw err;
|
||||
if (req.profiler) req.profiler.done('tablePrivacy_getUserDBName');
|
||||
cartoData.getTablePrivacy(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, privacy){
|
||||
if (req.profiler) req.profiler.done('getTablePrivacy');
|
||||
callback(err, privacy !== "0");
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// jshint maxcomplexity:10
|
||||
/**
|
||||
* Whitelist input and get database name & default geometry type from
|
||||
* subdomain/user metadata held in CartoDB Redis
|
||||
@@ -658,17 +514,21 @@ module.exports = function(redisPool) {
|
||||
|
||||
if ( req.query.lzma ) {
|
||||
|
||||
// TODO: check ?
|
||||
//console.log("type of req.query.lzma is " + typeof(req.query.lzma));
|
||||
|
||||
// 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
|
||||
lzmaWorker.decompress(
|
||||
lzma,
|
||||
function(result) {
|
||||
if (req.profiler) req.profiler.done('LZMA decompress');
|
||||
if (req.profiler) {
|
||||
req.profiler.done('lzma');
|
||||
}
|
||||
try {
|
||||
delete req.query.lzma;
|
||||
_.extend(req.query, JSON.parse(result));
|
||||
@@ -676,38 +536,38 @@ module.exports = function(redisPool) {
|
||||
} catch (err) {
|
||||
callback(new Error('Error parsing lzma as JSON: ' + err));
|
||||
}
|
||||
},
|
||||
function(percent) { // progress
|
||||
//console.log("LZMA decompression " + percent + "%");
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var bad_query = _.difference(_.keys(req.query), REQUEST_QUERY_PARAMS_WHITELIST);
|
||||
req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST);
|
||||
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
|
||||
|
||||
_.each(bad_query, function(key){ delete req.query[key]; });
|
||||
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
|
||||
|
||||
var user = me.userByReq(req);
|
||||
var user = cdbRequest.userByReq(req);
|
||||
|
||||
if ( req.params.token ) {
|
||||
//console.log("Request parameters include token " + req.params.token);
|
||||
var tksplit = req.params.token.split(':');
|
||||
req.params.token = tksplit[0];
|
||||
if ( tksplit.length > 1 ) req.params.cache_buster= tksplit[1];
|
||||
if ( tksplit.length > 1 ) {
|
||||
req.params.cache_buster= tksplit[1];
|
||||
}
|
||||
tksplit = req.params.token.split('@');
|
||||
if ( tksplit.length > 1 ) {
|
||||
req.params.signer = tksplit.shift();
|
||||
if ( ! req.params.signer ) 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 + '"');
|
||||
if ( ! req.params.signer ) {
|
||||
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 + '"');
|
||||
err.http_status = 403;
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
if ( tksplit.length > 1 ) {
|
||||
var template_hash = tksplit.shift(); // unused
|
||||
/*var template_hash = */tksplit.shift(); // unused
|
||||
}
|
||||
req.params.token = tksplit.shift();
|
||||
//console.log("Request for token " + req.params.token + " with signature from " + req.params.signer);
|
||||
@@ -717,20 +577,19 @@ module.exports = function(redisPool) {
|
||||
// bring all query values onto req.params object
|
||||
_.extend(req.params, req.query);
|
||||
|
||||
// for cartodb, ensure interactivity is cartodb_id or user specified
|
||||
req.params.interactivity = req.params.interactivity || 'cartodb_id';
|
||||
if (req.profiler) {
|
||||
req.profiler.done('req2params.setup');
|
||||
}
|
||||
|
||||
var that = this;
|
||||
|
||||
if (req.profiler) req.profiler.done('req2params.setup');
|
||||
|
||||
Step(
|
||||
step(
|
||||
function getPrivacy(){
|
||||
me.authorize(req, this);
|
||||
},
|
||||
function gatekeep(err, authorized){
|
||||
if (req.profiler) req.profiler.done('authorize');
|
||||
if(err) throw err;
|
||||
if (req.profiler) {
|
||||
req.profiler.done('authorize');
|
||||
}
|
||||
assert.ifError(err);
|
||||
if(!authorized) {
|
||||
err = new Error("Sorry, you are unauthorized (permission denied)");
|
||||
err.http_status = 403;
|
||||
@@ -739,21 +598,12 @@ module.exports = function(redisPool) {
|
||||
return null;
|
||||
},
|
||||
function getDatabase(err){
|
||||
if(err) throw err;
|
||||
that.setDBConn(user, req.params, this);
|
||||
assert.ifError(err);
|
||||
pgConnection.setDBConn(user, req.params, this);
|
||||
},
|
||||
function getGeometryType(err){
|
||||
if (req.profiler) req.profiler.done('setDBConn');
|
||||
if (err) throw err;
|
||||
if ( ! req.params.table ) return null;
|
||||
cartoData.getTableGeometryType(req.params.dbname, req.params.table, this);
|
||||
},
|
||||
function finishSetup(err, data){
|
||||
function finishSetup(err) {
|
||||
if ( err ) { callback(err, req); return; }
|
||||
|
||||
if (!_.isNull(data))
|
||||
_.extend(req.params, {geom_type: data});
|
||||
|
||||
// Add default database connection parameters
|
||||
// if none given
|
||||
_.defaults(req.params, {
|
||||
@@ -768,89 +618,5 @@ module.exports = function(redisPool) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Little helper method to get the current list of infowindow variables and return to client
|
||||
* @param req
|
||||
* @param callback
|
||||
*/
|
||||
me.getInfowindow = function(req, callback){
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function(){
|
||||
// TODO: if this step really needed ?
|
||||
that.req2params(req, this);
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getInfowindow(err, dbname){
|
||||
if (err) throw err;
|
||||
cartoData.getTableInfowindow(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Little helper method to get map metadata and return to client
|
||||
* @param req
|
||||
* @param callback
|
||||
*/
|
||||
me.getMapMetadata = function(req, callback){
|
||||
var that = this;
|
||||
var user = me.userByReq(req);
|
||||
|
||||
Step(
|
||||
function(){
|
||||
// TODO: if this step really needed ?
|
||||
that.req2params(req, this);
|
||||
},
|
||||
function getDatabase(err){
|
||||
if (err) throw err;
|
||||
cartoData.getUserDBName(user, this);
|
||||
},
|
||||
function getMapMetadata(err, dbname){
|
||||
if (err) throw err;
|
||||
cartoData.getTableMapMetadata(dbname, req.params.table, this);
|
||||
},
|
||||
function(err, data){
|
||||
callback(err, data);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to clear out tile cache on request
|
||||
* @param req
|
||||
* @param callback
|
||||
*/
|
||||
me.flushCache = function(req, Cache, callback){
|
||||
var that = this;
|
||||
|
||||
Step(
|
||||
function getParams(){
|
||||
// this is mostly to compute req.params.dbname
|
||||
that.req2params(req, this);
|
||||
},
|
||||
function flushInternalCache(err){
|
||||
// TODO: implement this, see
|
||||
// http://github.com/Vizzuality/Windshaft-cartodb/issues/73
|
||||
return true;
|
||||
},
|
||||
function flushVarnishCache(err){
|
||||
if (err) { callback(err); return; }
|
||||
if(Cache) {
|
||||
Cache.invalidate_db(req.params.dbname, req.params.table);
|
||||
}
|
||||
callback(null, true);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return me;
|
||||
};
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
var _ = require('underscore'),
|
||||
request = require('request');
|
||||
|
||||
module.exports.query = function (username, api_key, sql, callback) {
|
||||
var api = global.environment.sqlapi;
|
||||
|
||||
// build up api string
|
||||
var sqlapihostname = username;
|
||||
if ( api.domain ) sqlapihostname += '.' + api.domain;
|
||||
|
||||
var sqlapi = api.protocol + '://';
|
||||
if ( api.host && api.host != api.domain ) sqlapi += api.host;
|
||||
else sqlapi += sqlapihostname;
|
||||
sqlapi += ':' + api.port + '/api/' + api.version + '/sql';
|
||||
|
||||
var qs = { q: sql };
|
||||
|
||||
// add api_key if given
|
||||
if (_.isString(api_key) && api_key != '') { qs.api_key = api_key; }
|
||||
|
||||
// call sql api
|
||||
//
|
||||
// NOTE: using POST to avoid size limits:
|
||||
// See http://github.com/CartoDB/Windshaft-cartodb/issues/111
|
||||
//
|
||||
// NOTE: uses "host" header to allow IP based specification
|
||||
// of sqlapi address (and avoid a DNS lookup)
|
||||
//
|
||||
// NOTE: allows for keeping up to "maxConnections" concurrent
|
||||
// sockets opened per SQL-API host.
|
||||
// See http://nodejs.org/api/http.html#http_agent_maxsockets
|
||||
//
|
||||
var maxSockets = global.environment.maxConnections || 128;
|
||||
var maxGetLen = api.max_get_sql_length || 2048;
|
||||
var maxSQLTime = api.timeout || 100; // 1/10 of a second by default
|
||||
var reqSpec = {
|
||||
url:sqlapi,
|
||||
json:true,
|
||||
headers:{host: sqlapihostname}
|
||||
// http://nodejs.org/api/http.html#http_agent_maxsockets
|
||||
,pool:{maxSockets:maxSockets}
|
||||
// timeout in milliseconds
|
||||
,timeout:maxSQLTime
|
||||
};
|
||||
if ( sql.length > maxGetLen ) {
|
||||
reqSpec.method = 'POST';
|
||||
reqSpec.body = qs;
|
||||
} else {
|
||||
reqSpec.method = 'GET';
|
||||
reqSpec.qs = qs;
|
||||
}
|
||||
request(reqSpec, function(err, res, body) {
|
||||
if (err){
|
||||
console.log('ERROR connecting to SQL API on ' + sqlapi + ': ' + err);
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
if (res.statusCode != 200) {
|
||||
var msg = res.body.error ? res.body.error : res.body;
|
||||
callback(new Error(msg));
|
||||
console.log('unexpected response status (' + res.statusCode + ') for sql query: ' + sql + ': ' + msg);
|
||||
return;
|
||||
}
|
||||
callback(null, body.rows);
|
||||
});
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
var crypto = require('crypto'),
|
||||
Step = require('step'),
|
||||
_ = require('underscore'),
|
||||
dot = require('dot');
|
||||
var crypto = require('crypto');
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
var dot = require('dot');
|
||||
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
@@ -44,11 +44,6 @@ function TemplateMaps(redis_pool, opts) {
|
||||
|
||||
// User templates (HASH:tpl_id->tpl_val)
|
||||
this.key_usr_tpl = dot.template("map_tpl|{{=it.owner}}");
|
||||
|
||||
// User template locks (HASH:tpl_id->ctime)
|
||||
this.key_usr_tpl_lck = dot.template("map_tpl|{{=it.owner}}|locks");
|
||||
|
||||
this.lock_ttl = this.opts['lock_ttl'] || 5000;
|
||||
}
|
||||
|
||||
util.inherits(TemplateMaps, EventEmitter);
|
||||
@@ -61,7 +56,7 @@ var o = TemplateMaps.prototype;
|
||||
//--------------- PRIVATE METHODS --------------------------------
|
||||
|
||||
o._userTemplateLimit = function() {
|
||||
return this.opts['max_user_templates'] || 0;
|
||||
return this.opts.max_user_templates || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -76,7 +71,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var that = this;
|
||||
var db = that.db_signatures;
|
||||
|
||||
Step(
|
||||
step(
|
||||
function getRedisClient() {
|
||||
that.redis_pool.acquire(db, this);
|
||||
},
|
||||
@@ -94,6 +89,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
};
|
||||
|
||||
var _reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/;
|
||||
// jshint maxcomplexity:15
|
||||
o._checkInvalidTemplate = function(template) {
|
||||
if ( template.version != '0.0.1' ) {
|
||||
return new Error("Unsupported template version " + template.version);
|
||||
@@ -141,7 +137,6 @@ o._checkInvalidTemplate = function(template) {
|
||||
break;
|
||||
default:
|
||||
return new Error("Unsupported authentication method: " + auth.method);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -210,7 +205,7 @@ o.addTemplate = function(owner, template, callback) {
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
|
||||
var limit = this._userTemplateLimit();
|
||||
|
||||
Step(
|
||||
step(
|
||||
function checkLimit() {
|
||||
if ( ! limit ) {
|
||||
return 0;
|
||||
@@ -258,7 +253,7 @@ o.addTemplate = function(owner, template, callback) {
|
||||
//
|
||||
o.delTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
Step(
|
||||
step(
|
||||
function deleteTemplate() {
|
||||
self._redisCmd('HDEL', [ self.key_usr_tpl({ owner:owner }), tpl_id ], this);
|
||||
},
|
||||
@@ -316,7 +311,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
|
||||
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
|
||||
|
||||
Step(
|
||||
step(
|
||||
function getExistingTemplate() {
|
||||
self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
|
||||
},
|
||||
@@ -371,7 +366,7 @@ o.listTemplates = function(owner, callback) {
|
||||
//
|
||||
o.getTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
Step(
|
||||
step(
|
||||
function getTemplate() {
|
||||
self._redisCmd('HGET', [ self.key_usr_tpl({owner:owner}), tpl_id ], this);
|
||||
},
|
||||
@@ -429,14 +424,14 @@ var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
|
||||
_reCSSColorName = /^[a-zA-Z]+$/,
|
||||
_reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
|
||||
|
||||
_replaceVars = function(str, params) {
|
||||
function _replaceVars (str, params) {
|
||||
//return _.template(str, params); // lazy way, possibly dangerous
|
||||
// Construct regular expressions for each param
|
||||
Object.keys(params).forEach(function(k) {
|
||||
str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
|
||||
});
|
||||
return str;
|
||||
};
|
||||
}
|
||||
o.instance = function(template, params) {
|
||||
var all_params = {};
|
||||
var phold = template.placeholders || {};
|
||||
@@ -455,16 +450,14 @@ o.instance = function(template, params) {
|
||||
else if ( type === 'number' ) {
|
||||
// check it's a number
|
||||
if ( typeof(val) !== 'number' && ! val.match(_reNumber) ) {
|
||||
throw new Error("Invalid number value for template parameter '"
|
||||
+ k + "': " + val);
|
||||
throw new Error("Invalid number value for template parameter '" + k + "': " + val);
|
||||
}
|
||||
}
|
||||
else if ( type === 'css_color' ) {
|
||||
// check it only contains letters or
|
||||
// starts with # and only contains hexdigits
|
||||
if ( ! val.match(_reCSSColorName) && ! val.match(_reCSSColorVal) ) {
|
||||
throw new Error("Invalid css_color value for template parameter '"
|
||||
+ k + "': " + val);
|
||||
throw new Error("Invalid css_color value for template parameter '" + k + "': " + val);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
319
npm-shrinkwrap.json
generated
319
npm-shrinkwrap.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "1.26.2",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"cartodb-psql": {
|
||||
"version": "0.4.0",
|
||||
"from": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
|
||||
"resolved": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
|
||||
"from": "cartodb-psql@~0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cartodb-psql/-/cartodb-psql-0.4.0.tgz",
|
||||
"dependencies": {
|
||||
"pg": {
|
||||
"version": "2.6.2-cdb1",
|
||||
@@ -14,7 +14,8 @@
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.0.3",
|
||||
"from": "generic-pool@2.0.3"
|
||||
"from": "generic-pool@2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.0.3.tgz"
|
||||
},
|
||||
"buffer-writer": {
|
||||
"version": "1.0.0",
|
||||
@@ -26,9 +27,8 @@
|
||||
}
|
||||
},
|
||||
"cartodb-redis": {
|
||||
"version": "0.11.0",
|
||||
"from": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
|
||||
"resolved": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
|
||||
"version": "0.12.1",
|
||||
"from": "cartodb-redis@~0.12.1",
|
||||
"dependencies": {
|
||||
"redis-mpool": {
|
||||
"version": "0.1.0",
|
||||
@@ -37,19 +37,23 @@
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.1.1",
|
||||
"from": "generic-pool@~2.1.1"
|
||||
"from": "generic-pool@~2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz"
|
||||
},
|
||||
"redis": {
|
||||
"version": "0.12.1",
|
||||
"from": "redis@~0.12.1"
|
||||
"from": "redis@~0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
|
||||
},
|
||||
"hiredis": {
|
||||
"version": "0.1.17",
|
||||
"from": "hiredis@~0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.2.1",
|
||||
"from": "bindings@*"
|
||||
"from": "bindings@*",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.1.2",
|
||||
@@ -84,7 +88,8 @@
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1",
|
||||
"from": "core-util-is@~1.0.0"
|
||||
"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",
|
||||
@@ -93,11 +98,13 @@
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@~0.10.x"
|
||||
"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"
|
||||
"from": "inherits@2",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,31 +115,35 @@
|
||||
"from": "lzma@~1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/lzma/-/lzma-1.3.7.tgz"
|
||||
},
|
||||
"node-varnish": {
|
||||
"version": "0.3.0",
|
||||
"from": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
||||
"resolved": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0"
|
||||
"queue-async": {
|
||||
"version": "1.0.7",
|
||||
"from": "queue-async@~1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/queue-async/-/queue-async-1.0.7.tgz"
|
||||
},
|
||||
"redis-mpool": {
|
||||
"version": "0.3.0",
|
||||
"from": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
|
||||
"resolved": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
|
||||
"from": "redis-mpool@~0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-mpool/-/redis-mpool-0.3.0.tgz",
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.1.1",
|
||||
"from": "generic-pool@~2.1.1"
|
||||
"from": "generic-pool@~2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz"
|
||||
},
|
||||
"redis": {
|
||||
"version": "0.12.1",
|
||||
"from": "redis@~0.12.1"
|
||||
"from": "redis@~0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
|
||||
},
|
||||
"hiredis": {
|
||||
"version": "0.1.17",
|
||||
"from": "hiredis@~0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.2.1",
|
||||
"from": "bindings@*"
|
||||
"from": "bindings@*",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.1.2",
|
||||
@@ -148,29 +159,10 @@
|
||||
"from": "request@~2.9.203",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz"
|
||||
},
|
||||
"rollbar": {
|
||||
"version": "0.3.13",
|
||||
"from": "rollbar@~0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/rollbar/-/rollbar-0.3.13.tgz",
|
||||
"dependencies": {
|
||||
"node-uuid": {
|
||||
"version": "1.4.2",
|
||||
"from": "node-uuid@1.4.x",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.2.tgz"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.2.4",
|
||||
"from": "lru-cache@~2.2.1"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.0",
|
||||
"from": "json-stringify-safe@~5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"step": {
|
||||
"version": "0.0.5",
|
||||
"from": "step@~0.0.5"
|
||||
"from": "step@~0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/step/-/step-0.0.5.tgz"
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.6.0",
|
||||
@@ -178,25 +170,54 @@
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
|
||||
},
|
||||
"windshaft": {
|
||||
"version": "0.35.1",
|
||||
"from": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
||||
"resolved": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
||||
"version": "0.41.0",
|
||||
"from": "windshaft@0.41.0",
|
||||
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-0.41.0.tgz",
|
||||
"dependencies": {
|
||||
"chronograph": {
|
||||
"version": "0.1.0",
|
||||
"from": "chronograph@git://github.com/CartoDB/chronographjs.git#0.1.0",
|
||||
"resolved": "git://github.com/CartoDB/chronographjs.git#0b8c35eee510cfa14a16be24d70533b38ecc1d2d"
|
||||
},
|
||||
"queue-async": {
|
||||
"version": "1.0.7",
|
||||
"from": "queue-async@~1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/queue-async/-/queue-async-1.0.7.tgz"
|
||||
},
|
||||
"grainstore": {
|
||||
"version": "0.22.1",
|
||||
"from": "https://github.com/CartoDB/grainstore/tarball/0.22.1",
|
||||
"resolved": "https://github.com/CartoDB/grainstore/tarball/0.22.1",
|
||||
"version": "0.23.0",
|
||||
"from": "grainstore@~0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/grainstore/-/grainstore-0.23.0.tgz",
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz"
|
||||
},
|
||||
"redis": {
|
||||
"version": "0.12.1",
|
||||
"from": "redis@~0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
|
||||
},
|
||||
"hiredis": {
|
||||
"version": "0.1.17",
|
||||
"from": "hiredis@~0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.2.1",
|
||||
"from": "bindings@*",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.1.2",
|
||||
"from": "nan@~1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-1.1.2.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"carto": {
|
||||
"version": "0.9.5-cdb2",
|
||||
"from": "https://github.com/CartoDB/carto/tarball/0.9.5-cdb2",
|
||||
@@ -219,7 +240,8 @@
|
||||
"dependencies": {
|
||||
"sax": {
|
||||
"version": "0.5.8",
|
||||
"from": "sax@0.5.x"
|
||||
"from": "sax@0.5.x",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -235,7 +257,8 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"from": "minimist@~0.0.1"
|
||||
"from": "minimist@~0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +271,8 @@
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.0.4",
|
||||
"from": "generic-pool@~2.0.3"
|
||||
"from": "generic-pool@~2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.0.4.tgz"
|
||||
},
|
||||
"request": {
|
||||
"version": "2.34.0",
|
||||
@@ -257,24 +281,28 @@
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "0.6.6",
|
||||
"from": "qs@~0.6.0"
|
||||
"from": "qs@~0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.0",
|
||||
"from": "json-stringify-safe@~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"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.5.2",
|
||||
"from": "forever-agent@~0.5.0"
|
||||
"from": "forever-agent@~0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"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": {
|
||||
"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.2",
|
||||
@@ -286,6 +314,7 @@
|
||||
"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.7",
|
||||
@@ -308,7 +337,8 @@
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.3.0",
|
||||
"from": "tunnel-agent@~0.3.0"
|
||||
"from": "tunnel-agent@~0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.10.1",
|
||||
@@ -334,7 +364,8 @@
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.3.0",
|
||||
"from": "oauth-sign@~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.0.0",
|
||||
@@ -353,17 +384,20 @@
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "0.2.2",
|
||||
"from": "cryptiles@0.2.x"
|
||||
"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"
|
||||
"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"
|
||||
"from": "aws-sign2@~0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1604,7 +1638,8 @@
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.3.5",
|
||||
"from": "mkdirp@~0.3.3"
|
||||
"from": "mkdirp@~0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz"
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.6.1",
|
||||
@@ -1618,7 +1653,8 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"from": "minimist@~0.0.1"
|
||||
"from": "minimist@~0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1637,9 +1673,9 @@
|
||||
"resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz",
|
||||
"dependencies": {
|
||||
"formidable": {
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.17",
|
||||
"from": "formidable@1.0.x",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz"
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1677,24 +1713,26 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"from": "minimist@~0.0.1"
|
||||
"from": "minimist@~0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tilelive-mapnik": {
|
||||
"version": "0.6.12",
|
||||
"version": "0.6.15",
|
||||
"from": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb",
|
||||
"resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/cdb",
|
||||
"dependencies": {
|
||||
"generic-pool": {
|
||||
"version": "2.1.1",
|
||||
"from": "generic-pool@~2.1.1"
|
||||
"from": "generic-pool@~2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"from": "mime@~1.2.11",
|
||||
"from": "mime@~1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
|
||||
}
|
||||
}
|
||||
@@ -1706,7 +1744,8 @@
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
"version": "1.2.0",
|
||||
"from": "nan@~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",
|
||||
@@ -2084,50 +2123,21 @@
|
||||
}
|
||||
},
|
||||
"canvas": {
|
||||
"version": "1.1.6",
|
||||
"from": "canvas@1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-1.1.6.tgz",
|
||||
"version": "1.2.1",
|
||||
"from": "canvas@1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-1.2.1.tgz",
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
"version": "1.2.0",
|
||||
"from": "nan@~1.2.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"version": "1.5.3",
|
||||
"from": "nan@~1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-1.5.3.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"carto": {
|
||||
"version": "0.14.0",
|
||||
"from": "https://github.com/CartoDB/carto/tarball/update_to_master",
|
||||
"resolved": "https://github.com/CartoDB/carto/tarball/update_to_master",
|
||||
"version": "0.15.1-cdb1",
|
||||
"from": "https://github.com/CartoDB/carto/tarball/0.15.1-cdb1",
|
||||
"resolved": "https://github.com/CartoDB/carto/tarball/0.15.1-cdb1",
|
||||
"dependencies": {
|
||||
"mapnik-reference": {
|
||||
"version": "6.0.5",
|
||||
@@ -2146,51 +2156,22 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"from": "minimist@~0.0.1"
|
||||
"from": "minimist@~0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"step-profiler": {
|
||||
"version": "0.1.0",
|
||||
"from": "step-profiler@git://github.com/CartoDB/node-step-profiler.git#0.1.0",
|
||||
"resolved": "git://github.com/CartoDB/node-step-profiler.git#9b97881e450445bd8a307a9cc372b5129cb4529a"
|
||||
"version": "0.2.1",
|
||||
"from": "step-profiler@~0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/step-profiler/-/step-profiler-0.2.1.tgz"
|
||||
},
|
||||
"torque.js": {
|
||||
"version": "2.7.1",
|
||||
"from": "https://github.com/CartoDB/torque/tarball/2.8",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"version": "2.11.0",
|
||||
"from": "torque.js@~2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/torque.js/-/torque.js-2.11.0.tgz"
|
||||
},
|
||||
"request": {
|
||||
"version": "2.48.0",
|
||||
@@ -2209,7 +2190,8 @@
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1",
|
||||
"from": "core-util-is@~1.0.0"
|
||||
"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",
|
||||
@@ -2218,11 +2200,13 @@
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@~0.10.x"
|
||||
"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"
|
||||
"from": "inherits@~2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2235,11 +2219,13 @@
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.5.2",
|
||||
"from": "forever-agent@~0.5.0"
|
||||
"from": "forever-agent@~0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.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": {
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
@@ -2255,7 +2241,8 @@
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.0",
|
||||
"from": "json-stringify-safe@~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",
|
||||
@@ -2263,9 +2250,9 @@
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"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": {
|
||||
"version": "2.3.3",
|
||||
@@ -2274,11 +2261,13 @@
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.4.0",
|
||||
"from": "tunnel-agent@~0.4.0"
|
||||
"from": "tunnel-agent@~0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.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.2",
|
||||
@@ -2331,21 +2320,25 @@
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "0.2.2",
|
||||
"from": "cryptiles@0.2.x"
|
||||
"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"
|
||||
"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"
|
||||
"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"
|
||||
"from": "stringstream@~0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz"
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "0.0.7",
|
||||
|
||||
21
package.json
21
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "1.26.2",
|
||||
"version": "2.0.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -22,27 +22,30 @@
|
||||
"Sandro Santilli <strk@vizzuality.com>"
|
||||
],
|
||||
"dependencies": {
|
||||
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
||||
"underscore" : "~1.6.0",
|
||||
"dot": "~1.0.2",
|
||||
"windshaft": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
||||
"windshaft": "0.41.0",
|
||||
"step": "~0.0.5",
|
||||
"queue-async": "~1.0.7",
|
||||
"request": "~2.9.203",
|
||||
"cartodb-redis": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
|
||||
"cartodb-psql": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
|
||||
"redis-mpool": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
|
||||
"cartodb-redis": "~0.12.1",
|
||||
"cartodb-psql": "~0.4.0",
|
||||
"redis-mpool": "~0.3.0",
|
||||
"lzma": "~1.3.7",
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
|
||||
"rollbar": "~0.3.13"
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.3.6",
|
||||
"mocha": "~1.21.4",
|
||||
"nock": "~1.3.0",
|
||||
"jshint": "~2.6.0",
|
||||
"redis": "~0.8.6",
|
||||
"strftime": "~0.8.2",
|
||||
"semver": "~1.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "make check"
|
||||
"preinstall": "make pre-install",
|
||||
"test": "make test-all"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8 <0.11",
|
||||
|
||||
15
run_tests.sh
15
run_tests.sh
@@ -4,6 +4,7 @@ OPT_CREATE_REDIS=yes # create the redis test environment
|
||||
OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
|
||||
OPT_DROP_REDIS=yes # drop the redis test environment
|
||||
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
|
||||
OPT_COVERAGE=no # run tests with coverage
|
||||
|
||||
export PGAPPNAME=cartodb_tiler_tester
|
||||
|
||||
@@ -69,6 +70,10 @@ while [ -n "$1" ]; do
|
||||
OPT_CREATE_REDIS=no
|
||||
shift
|
||||
continue
|
||||
elif test "$1" = "--with-coverage"; then
|
||||
OPT_COVERAGE=yes
|
||||
shift
|
||||
continue
|
||||
# This is kept for backward compatibility
|
||||
elif test "$1" = "--nocreate"; then
|
||||
OPT_CREATE_REDIS=no
|
||||
@@ -85,6 +90,7 @@ if [ -z "$1" ]; then
|
||||
echo "Options:" >&2
|
||||
echo " --nocreate do not create the test environment on start" >&2
|
||||
echo " --nodrop do not drop the test environment on exit" >&2
|
||||
echo " --with-coverage use istanbul to determine code coverage" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -112,8 +118,13 @@ cd -
|
||||
|
||||
PATH=node_modules/.bin/:$PATH
|
||||
|
||||
echo "Running tests"
|
||||
mocha -t 10000 -u tdd ${MOCHA_OPTS} ${TESTS}
|
||||
if test x"$OPT_COVERAGE" = xyes; then
|
||||
echo "Running tests with coverage"
|
||||
./node_modules/.bin/istanbul cover node_modules/.bin/_mocha -- -u tdd -t 5000 ${TESTS}
|
||||
else
|
||||
echo "Running tests"
|
||||
mocha -u tdd -t 5000 ${TESTS}
|
||||
fi
|
||||
ret=$?
|
||||
|
||||
cleanup
|
||||
|
||||
24
scripts/check-node-canvas.sh
Normal file
24
scripts/check-node-canvas.sh
Normal 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
7
scripts/install.sh
Normal 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
18
scripts/lzma2config.js
Normal 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));
|
||||
});
|
||||
179
test/acceptance/cache/surrogate_keys_invalidation.js
vendored
179
test/acceptance/cache/surrogate_keys_invalidation.js
vendored
@@ -1,24 +1,25 @@
|
||||
require(__dirname + '/../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var redis = require('redis');
|
||||
var Step = require('step');
|
||||
|
||||
var helper = require(__dirname + '/../../support/test_helper');
|
||||
|
||||
var SqlApiEmulator = require(__dirname + '/../../support/SQLAPIEmu.js');
|
||||
var step = require('step');
|
||||
|
||||
var NamedMapsCacheEntry = require(__dirname + '/../../../lib/cartodb/cache/model/named_maps_entry');
|
||||
var SurrogateKeysCache = require(__dirname + '/../../../lib/cartodb/cache/surrogate_keys_cache');
|
||||
|
||||
var CartodbWindshaft = require(__dirname + '/../../../lib/cartodb/cartodb_windshaft');
|
||||
var ServerOptions = require(__dirname + '/../../../lib/cartodb/server_options');
|
||||
var serverOptions = ServerOptions();
|
||||
|
||||
|
||||
suite('templates surrogate keys', function() {
|
||||
describe('templates surrogate keys', function() {
|
||||
|
||||
var redisClient,
|
||||
sqlApiServer,
|
||||
server;
|
||||
var redisClient = redis.createClient(global.environment.redis.port);
|
||||
|
||||
// Enable Varnish purge for tests
|
||||
var varnishHost = global.environment.varnish.host;
|
||||
global.environment.varnish.host = '127.0.0.1';
|
||||
var varnishPurgeEnabled = global.environment.varnish.purge_enabled;
|
||||
global.environment.varnish.purge_enabled = true;
|
||||
|
||||
var serverOptions = require('../../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var templateOwner = 'localhost',
|
||||
templateName = 'acceptance',
|
||||
@@ -44,22 +45,28 @@ suite('templates surrogate keys', function() {
|
||||
},
|
||||
expectedBody = { template_id: expectedTemplateId };
|
||||
|
||||
suiteSetup(function(done) {
|
||||
// Enable Varnish purge for tests
|
||||
serverOptions.varnish_purge_enabled = true;
|
||||
var varnishHttpUrl = [
|
||||
'http://', global.environment.varnish.host, ':', global.environment.varnish.http_port
|
||||
].join('');
|
||||
|
||||
server = new CartodbWindshaft(serverOptions);
|
||||
var cacheEntryKey = new NamedMapsCacheEntry(templateOwner, templateName).key();
|
||||
var invalidationMatchHeader = '\\b' + cacheEntryKey + '\\b';
|
||||
|
||||
sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done);
|
||||
var nock = require('nock');
|
||||
nock.enableNetConnect(/(127.0.0.1:5555|cartocdn.com)/);
|
||||
|
||||
redisClient = redis.createClient(global.environment.redis.port);
|
||||
after(function(done) {
|
||||
serverOptions.varnish_purge_enabled = false;
|
||||
global.environment.varnish.host = varnishHost;
|
||||
global.environment.varnish.purge_enabled = varnishPurgeEnabled;
|
||||
|
||||
nock.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate;
|
||||
|
||||
beforeEach(function(done) {
|
||||
function createTemplate(callback) {
|
||||
var postTemplateRequest = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: templateOwner,
|
||||
@@ -68,7 +75,7 @@ suite('templates surrogate keys', function() {
|
||||
data: JSON.stringify(template)
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function postTemplate() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
@@ -90,23 +97,28 @@ suite('templates surrogate keys', function() {
|
||||
return true;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test("update template calls surrogate keys invalidation", function(done) {
|
||||
var cacheEntryKey;
|
||||
var surrogateKeysCacheInvalidateMethodInvoked = false;
|
||||
SurrogateKeysCache.prototype.invalidate = function(cacheEntry) {
|
||||
cacheEntryKey = cacheEntry.key();
|
||||
surrogateKeysCacheInvalidateMethodInvoked = true;
|
||||
};
|
||||
it("invalidates surrogate keys on template update", function(done) {
|
||||
|
||||
Step(
|
||||
function putValidTemplate() {
|
||||
var scope = nock(varnishHttpUrl)
|
||||
.intercept('/key', 'PURGE')
|
||||
.matchHeader('Invalidation-Match', invalidationMatchHeader)
|
||||
.reply(204, '');
|
||||
|
||||
step(
|
||||
function createTemplateToUpdate() {
|
||||
createTemplate(this);
|
||||
},
|
||||
function putValidTemplate(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var updateTemplateRequest = {
|
||||
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
host: templateOwner,
|
||||
@@ -132,8 +144,7 @@ suite('templates surrogate keys', function() {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, expectedBody);
|
||||
|
||||
assert.ok(surrogateKeysCacheInvalidateMethodInvoked);
|
||||
assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key());
|
||||
assert.equal(scope.pendingMocks().length, 0);
|
||||
|
||||
return null;
|
||||
},
|
||||
@@ -153,19 +164,23 @@ suite('templates surrogate keys', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test("delete template calls surrogate keys invalidation", function(done) {
|
||||
it("invalidates surrogate on template deletion", function(done) {
|
||||
|
||||
var cacheEntryKey;
|
||||
var surrogateKeysCacheInvalidateMethodInvoked = false;
|
||||
SurrogateKeysCache.prototype.invalidate = function(cacheEntry) {
|
||||
cacheEntryKey = cacheEntry.key();
|
||||
surrogateKeysCacheInvalidateMethodInvoked = true;
|
||||
};
|
||||
var scope = nock(varnishHttpUrl)
|
||||
.intercept('/key', 'PURGE')
|
||||
.matchHeader('Invalidation-Match', invalidationMatchHeader)
|
||||
.reply(204, '');
|
||||
|
||||
Step(
|
||||
function putValidTemplate() {
|
||||
step(
|
||||
function createTemplateToDelete() {
|
||||
createTemplate(this);
|
||||
},
|
||||
function deleteValidTemplate(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var deleteTemplateRequest = {
|
||||
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
host: templateOwner,
|
||||
@@ -188,8 +203,7 @@ suite('templates surrogate keys', function() {
|
||||
throw err;
|
||||
}
|
||||
|
||||
assert.ok(surrogateKeysCacheInvalidateMethodInvoked);
|
||||
assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key());
|
||||
assert.equal(scope.pendingMocks().length, 0);
|
||||
|
||||
return null;
|
||||
},
|
||||
@@ -199,13 +213,66 @@ suite('templates surrogate keys', function() {
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn;
|
||||
done();
|
||||
});
|
||||
it("should update template even if surrogate key invalidation fails", function(done) {
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
sqlApiServer.close(done);
|
||||
var scope = nock(varnishHttpUrl)
|
||||
.intercept('/key', 'PURGE')
|
||||
.matchHeader('Invalidation-Match', invalidationMatchHeader)
|
||||
.reply(503, '');
|
||||
|
||||
step(
|
||||
function createTemplateToUpdate() {
|
||||
createTemplate(this);
|
||||
},
|
||||
function putValidTemplate(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var updateTemplateRequest = {
|
||||
url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
host: templateOwner,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(template)
|
||||
};
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
updateTemplateRequest,
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
next(null, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkValidUpdate(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, expectedBody);
|
||||
|
||||
assert.equal(scope.pendingMocks().length, 0);
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) {
|
||||
return done(err);
|
||||
}
|
||||
redisClient.keys("map_*|localhost", function(err, keys) {
|
||||
if ( err ) {
|
||||
return done(err);
|
||||
}
|
||||
redisClient.del(keys, function(err) {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
var assert = require('../support/assert');
|
||||
require(__dirname + '/../support/test_helper');
|
||||
var CacheValidator = require(__dirname + '/../../lib/cartodb/cache_validator');
|
||||
|
||||
var VarnishEmu = require('../support/VarnishEmu');
|
||||
|
||||
suite('cache_validator', function() {
|
||||
|
||||
test('should call purge on varnish when invalidate database', function(done) {
|
||||
var varnish = new VarnishEmu(function(cmds) {
|
||||
assert.ok(cmds.length == 1);
|
||||
assert.equal('purge obj.http.X-Cache-Channel ~ \"^test_db:(.*test_cache.*)|(table)$\"\n', cmds[0].toString('utf8'));
|
||||
done();
|
||||
},
|
||||
function() {
|
||||
CacheValidator.init('localhost', 1337);
|
||||
CacheValidator.invalidate_db('test_db', 'test_cache');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -5,9 +5,14 @@ var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var metadataBackend = {};
|
||||
var tilelive = {};
|
||||
var HealthCheck = require('../../lib/cartodb/monitoring/health_check');
|
||||
var healthCheck = new HealthCheck(metadataBackend, tilelive);
|
||||
|
||||
suite('health checks', function () {
|
||||
|
||||
beforeEach(function (done) {
|
||||
function resetHealthConfig() {
|
||||
global.environment.health = {
|
||||
enabled: true,
|
||||
username: 'localhost',
|
||||
@@ -15,8 +20,7 @@ suite('health checks', function () {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
var healthCheckRequest = {
|
||||
url: '/health',
|
||||
@@ -27,13 +31,14 @@ suite('health checks', function () {
|
||||
};
|
||||
|
||||
test('returns 200 and ok=true with enabled configuration', function (done) {
|
||||
resetHealthConfig();
|
||||
|
||||
assert.response(server,
|
||||
healthCheckRequest,
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
console.log(res.body);
|
||||
assert.ok(!err);
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
@@ -46,27 +51,44 @@ suite('health checks', function () {
|
||||
);
|
||||
});
|
||||
|
||||
test('fails for invalid user because it is not in redis', function (done) {
|
||||
global.environment.health.username = 'invalid';
|
||||
test('error if disabled file exists', function(done) {
|
||||
var fs = require('fs');
|
||||
|
||||
assert.response(server,
|
||||
healthCheckRequest,
|
||||
{
|
||||
status: 503
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ok(!err);
|
||||
var readFileFn = fs.readFile
|
||||
fs.readFile = function(filename, callback) {
|
||||
callback(null, "Maintenance");
|
||||
}
|
||||
|
||||
healthCheck.check(null, function(err, result) {
|
||||
assert.equal(err.message, "Maintenance");
|
||||
assert.equal(err.http_status, 503);
|
||||
done();
|
||||
fs.readFile = readFileFn;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
test('not err if disabled file does not exists', function(done) {
|
||||
resetHealthConfig();
|
||||
|
||||
assert.equal(parsed.enabled, true);
|
||||
assert.equal(parsed.ok, false);
|
||||
global.environment.disabled_file = '/tmp/ftreftrgtrccre';
|
||||
|
||||
assert.equal(parsed.result.redis.ok, false);
|
||||
assert.response(server,
|
||||
healthCheckRequest,
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ok(!err);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
assert.equal(parsed.enabled, true);
|
||||
assert.equal(parsed.ok, true);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
307
test/acceptance/limits.js
Normal file
307
test/acceptance/limits.js
Normal file
@@ -0,0 +1,307 @@
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var _ = require('underscore');
|
||||
var redis = require('redis');
|
||||
|
||||
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require('../../lib/cartodb/server_options');
|
||||
|
||||
describe('render limits', function() {
|
||||
|
||||
var layergroupUrl = '/api/v1/map';
|
||||
|
||||
var redisClient = redis.createClient(global.environment.redis.port);
|
||||
after(function(done) {
|
||||
redisClient.keys("map_style|*", function(err, matches) {
|
||||
redisClient.del(matches, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var server;
|
||||
beforeEach(function() {
|
||||
server = new CartodbWindshaft(serverOptions());
|
||||
server.setMaxListeners(0);
|
||||
});
|
||||
|
||||
var keysToDelete = [];
|
||||
afterEach(function(done) {
|
||||
redisClient.DEL(keysToDelete, function() {
|
||||
keysToDelete = [];
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var user = 'localhost';
|
||||
|
||||
var pointSleepSql = "SELECT pg_sleep(0.5)," +
|
||||
" 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, 1 cartodb_id";
|
||||
var pointCartoCss = '#layer { marker-fill:red; }';
|
||||
var polygonSleepSql = "SELECT pg_sleep(0.5)," +
|
||||
" ST_Buffer('SRID=3857;POINT(0 0)'::geometry, 100000000) the_geom_webmercator, 1 cartodb_id";
|
||||
var polygonCartoCss = '#layer { polygon-fill:red; }';
|
||||
|
||||
function singleLayergroupConfig(sql, cartocss) {
|
||||
return {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss: cartocss,
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function createRequest(layergroup, userHost) {
|
||||
return {
|
||||
url: layergroupUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: userHost,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
};
|
||||
}
|
||||
|
||||
function withRenderLimit(user, renderLimit, callback) {
|
||||
redisClient.SELECT(5, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var userLimitsKey = 'limits:tiler:' + user;
|
||||
redisClient.HSET(userLimitsKey, 'render', renderLimit, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
keysToDelete.push(userLimitsKey);
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
describe('with onTileErrorStrategy DISABLED', function() {
|
||||
var onTileErrorStrategyEnabled;
|
||||
before(function() {
|
||||
onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||
global.environment.enabledFeatures.onTileErrorStrategy = false;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategyEnabled;
|
||||
});
|
||||
|
||||
it("layergroup creation fails if test tile is slow", function(done) {
|
||||
withRenderLimit(user, 50, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, { errors: [ 'Render timed out' ] });
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("layergroup creation does not fail if user limit is high enough even if test tile is slow", function(done) {
|
||||
withRenderLimit(user, 5000, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("layergroup creation works if test tile is fast but tile request fails if they are slow", function(done) {
|
||||
withRenderLimit(user, 50, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
||||
layergroupId: JSON.parse(res.body).layergroupid,
|
||||
z: 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, { error: 'Render timed out' });
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("tile request does not fail if user limit is high enough", function(done) {
|
||||
withRenderLimit(user, 5000, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
||||
layergroupId: JSON.parse(res.body).layergroupid,
|
||||
z: 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with onTileErrorStrategy', function() {
|
||||
|
||||
it("layergroup creation works even if test tile is slow", function(done) {
|
||||
withRenderLimit(user, 50, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("layergroup creation and tile requests works even if they are slow but returns fallback", function(done) {
|
||||
withRenderLimit(user, 50, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
||||
assert.response(server,
|
||||
createRequest(layergroup, user),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
||||
layergroupId: JSON.parse(res.body).layergroupid,
|
||||
z: 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
done(err);
|
||||
}
|
||||
assert.imageEqualsFile(res.body, './test/fixtures/render-timeout-fallback.png', 25,
|
||||
function(imgErr/*, similarity*/) {
|
||||
done(imgErr);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,12 +1,8 @@
|
||||
var assert = require('../support/assert');
|
||||
var tests = module.exports = {};
|
||||
var _ = require('underscore');
|
||||
var redis = require('redis');
|
||||
var querystring = require('querystring');
|
||||
var semver = require('semver');
|
||||
var Step = require('step');
|
||||
var step = require('step');
|
||||
var strftime = require('strftime');
|
||||
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
|
||||
var redis_stats_db = 5;
|
||||
|
||||
var helper = require(__dirname + '/../support/test_helper');
|
||||
@@ -17,43 +13,39 @@ var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20;
|
||||
var IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
|
||||
var ServerOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
serverOptions = ServerOptions();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions());
|
||||
server.setMaxListeners(0);
|
||||
|
||||
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
|
||||
['/api/v1/map', '/u/localhost/api/v1/map'].forEach(function(layergroup_url) {
|
||||
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue};
|
||||
var suiteName = 'multilayer:postgres=layergroup_url=' + layergroup_url;
|
||||
suite(suiteName, function() {
|
||||
|
||||
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
var cdbQueryTablesFromPostgresEnabledValue = true;
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu
|
||||
var expected_last_updated = new Date(expected_last_updated_epoch).toISOString();
|
||||
|
||||
var test_user = _.template(global.environment.postgres_auth_user, {user_id:1});
|
||||
var test_pubuser = global.environment.postgres.user;
|
||||
var test_database = test_user + '_db';
|
||||
|
||||
suiteSetup(function(done){
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
});
|
||||
|
||||
test("layergroup with 2 layers, each with its style", 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',
|
||||
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',
|
||||
interactivity: 'cartodb_id'
|
||||
} },
|
||||
{ options: {
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator from test_table limit 2 offset 2',
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator' +
|
||||
' from test_table limit 2 offset 2',
|
||||
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
@@ -62,26 +54,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
|
||||
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
// check last modified
|
||||
var qTables = JSON.stringify({
|
||||
'q': 'SELECT CDB_QueryTables($windshaft$'
|
||||
+ layergroup.layers[0].options.sql + ';'
|
||||
+ layergroup.layers[1].options.sql
|
||||
+ '$windshaft$)'
|
||||
});
|
||||
assert.equal(parsedBody.last_updated, expected_last_updated);
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
@@ -95,7 +79,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -112,20 +96,26 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = [layergroup.layers[0].options.sql, ';', layergroup.layers[1].options.sql].join('');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = [
|
||||
layergroup.layers[0].options.sql, ';',
|
||||
layergroup.layers[1].options.sql
|
||||
].join('');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
|
||||
expectedQuery +
|
||||
'$windshaft$) as tablenames )' +
|
||||
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
|
||||
' FROM CDB_TableMetadata m' +
|
||||
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
next(err);
|
||||
});
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png',
|
||||
IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
|
||||
next(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/170
|
||||
@@ -134,7 +124,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -151,15 +141,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/0/0/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer0.grid.json', 2,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -169,15 +158,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/1/0/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer1.grid.json', 2,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -203,25 +191,25 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
|
||||
|
||||
test("should include serverMedata in the response", function(done) {
|
||||
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } }
|
||||
global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } };
|
||||
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',
|
||||
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 expected_token;
|
||||
Step(
|
||||
step(
|
||||
function do_create_get()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
@@ -231,7 +219,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
|
||||
done();
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -241,7 +229,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
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',
|
||||
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'
|
||||
} }
|
||||
@@ -249,12 +238,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
|
||||
var expected_token;
|
||||
Step(
|
||||
step(
|
||||
function do_create_get()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
@@ -298,7 +287,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}, {}, function(res) {
|
||||
@@ -313,14 +302,15 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
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',
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
|
||||
' from test_table limit 2',
|
||||
cartocss: '#layer { invalid-rule:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}, {}, function(res) {
|
||||
@@ -336,8 +326,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select 1 as cartodb_id, '
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
sql: 'select 1 as cartodb_id, ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!))' +
|
||||
' as the_geom_webmercator from test_table limit 1',
|
||||
cartocss: '#layer { polygon-fill:red; }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id'
|
||||
@@ -346,25 +336,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
|
||||
var expected_token; // = "6d8e4ad5458e2d25cf0eef38e38717a6";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
// check last modified
|
||||
var qTables = JSON.stringify({
|
||||
'q': 'SELECT CDB_QueryTables($windshaft$'
|
||||
+ layergroup.layers[0].options.sql
|
||||
+ '$windshaft$)'
|
||||
});
|
||||
assert.equal(parsedBody.last_updated, expected_last_updated);
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
@@ -378,7 +361,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -388,24 +371,26 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
|
||||
// Check X-Cache-Channel
|
||||
var cc = res.headers['x-cache-channel'];
|
||||
assert.ok(cc);
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace(/!pixel_width!/g, '1')
|
||||
.replace(/!pixel_height!/g, '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace(/!pixel_width!/g, '1')
|
||||
.replace(/!pixel_height!/g, '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
|
||||
expectedQuery +
|
||||
'$windshaft$) as tablenames )' +
|
||||
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
|
||||
' FROM CDB_TableMetadata m' +
|
||||
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -415,7 +400,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -428,21 +413,23 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(cc);
|
||||
var dbname = test_database;
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
var jsonquery = cc.substring(dbname.length+1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace('!pixel_width!', '1')
|
||||
.replace('!pixel_height!', '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$'
|
||||
+ expectedQuery
|
||||
+ '$windshaft$) as tablenames )'
|
||||
+ ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max'
|
||||
+ ' FROM CDB_TableMetadata m'
|
||||
+ ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var jsonquery = cc.substring(dbname.length + 1);
|
||||
var sentquery = JSON.parse(jsonquery);
|
||||
var expectedQuery = layergroup.layers[0].options.sql
|
||||
.replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)')
|
||||
.replace('!pixel_width!', '1')
|
||||
.replace('!pixel_height!', '1');
|
||||
assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' +
|
||||
expectedQuery +
|
||||
'$windshaft$) as tablenames )' +
|
||||
' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' +
|
||||
' FROM CDB_TableMetadata m' +
|
||||
' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])');
|
||||
}
|
||||
|
||||
assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -452,15 +439,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/0/1/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/0/1/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -470,15 +456,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/0/4/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/0/4/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2,
|
||||
function(err, similarity) {
|
||||
function(err/*, similarity*/) {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
@@ -508,8 +493,8 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select 1 as cartodb_id, !pixel_height! as h, '
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
sql: 'select 1 as cartodb_id, !pixel_height! as h,' +
|
||||
' ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
cartocss: '#layer { polygon-fill:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
@@ -520,7 +505,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
var expected_token; // will be set on first post and checked on second
|
||||
var now = strftime("%Y%m%d", new Date());
|
||||
var errors = [];
|
||||
Step(
|
||||
step(
|
||||
function clean_stats()
|
||||
{
|
||||
var next = this;
|
||||
@@ -534,7 +519,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -546,17 +531,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
},
|
||||
function check_global_stats_1(err, val) {
|
||||
if ( err ) throw err;
|
||||
assert.equal(val, 1, "Expected score of " + now + " in "
|
||||
+ statskey + ":global to be 1, got " + val);
|
||||
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.equal(val, 1, "Expected score of " + now + " in "
|
||||
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val);
|
||||
assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag +
|
||||
" to be 1, got " + val);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -569,15 +553,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
function check_global_stats_2(err, val)
|
||||
{
|
||||
if ( err ) throw err;
|
||||
assert.equal(val, 2, "Expected score of " + now + " in "
|
||||
+ statskey + ":global to be 2, got " + val);
|
||||
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.equal(val, 2, "Expected score of " + now + " in "
|
||||
+ statskey + ":stat_tag:" + layergroup.stat_tag + " to be 2, got " + val);
|
||||
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) {
|
||||
@@ -607,15 +590,15 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select 1 as cartodb_id, !pixel_height! as h'
|
||||
+ 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
sql: 'select 1 as cartodb_id, !pixel_height! as h' +
|
||||
'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator',
|
||||
cartocss: '#layer { polygon-fit:red; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -643,7 +626,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -678,26 +661,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
|
||||
var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?map_key=1234',
|
||||
url: layergroup_url + '?map_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
// check last modified
|
||||
var qTables = JSON.stringify({
|
||||
'q': 'SELECT CDB_QueryTables($windshaft$'
|
||||
+ layergroup.layers[0].options.sql + ';'
|
||||
+ layergroup.layers[1].options.sql
|
||||
+ '$windshaft$)'
|
||||
});
|
||||
assert.equal(parsedBody.last_updated, expected_last_updated);
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
@@ -711,7 +686,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -732,8 +707,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/0/0/0/0.grid.json?map_key=1234',
|
||||
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json?map_key=1234',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
@@ -746,8 +720,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/1/0/0/0.grid.json?map_key=1234',
|
||||
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json?map_key=1234',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
@@ -761,13 +734,13 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 403);
|
||||
var re = RegExp('permission denied');
|
||||
var re = new RegExp('permission denied');
|
||||
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
|
||||
next(err);
|
||||
});
|
||||
@@ -777,13 +750,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/0/0/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 403);
|
||||
var re = RegExp('permission denied');
|
||||
var re = new RegExp('permission denied');
|
||||
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
|
||||
next(err);
|
||||
});
|
||||
@@ -793,13 +765,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup/' + expected_token
|
||||
+ '/1/0/0/0.grid.json',
|
||||
url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json',
|
||||
headers: {host: 'localhost' },
|
||||
method: 'GET'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 403);
|
||||
var re = RegExp('permission denied');
|
||||
var re = new RegExp('permission denied');
|
||||
assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body);
|
||||
next(err);
|
||||
});
|
||||
@@ -839,12 +810,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
|
||||
var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?map_key=1234',
|
||||
url: layergroup_url + '?map_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -854,13 +825,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
// check last modified
|
||||
var qTables = JSON.stringify({
|
||||
'q': 'SELECT CDB_QueryTables($windshaft$'
|
||||
+ layergroup.layers[0].options.sql
|
||||
+ '$windshaft$)'
|
||||
});
|
||||
assert.equal(parsedBody.last_updated, expected_last_updated);
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
@@ -873,7 +837,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -891,11 +855,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.equal(cc.substring(0, dbname.length), dbname);
|
||||
return null;
|
||||
},
|
||||
function do_restart_server(err, res) {
|
||||
function do_restart_server(err/*, res*/) {
|
||||
if ( err ) throw err;
|
||||
// hack simulating restart...
|
||||
serverOptions = ServerOptions();
|
||||
server = new CartodbWindshaft(serverOptions);
|
||||
server = new CartodbWindshaft(serverOptions());
|
||||
return null;
|
||||
},
|
||||
function do_get1(err)
|
||||
@@ -903,7 +866,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -949,13 +912,13 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
{ options: {
|
||||
sql: "select 1 as cartodb_id, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#sample { text-name: cartodb_id; text-face-name: "Dejagnu"; }',
|
||||
cartocss_version: '2.1.0',
|
||||
cartocss_version: '2.1.0'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -977,18 +940,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
{ options: {
|
||||
sql: "select 'single''quote' as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#s [n="single\'quote" ] { marker-fill:red; }',
|
||||
cartocss_version: '2.1.0',
|
||||
cartocss_version: '2.1.0'
|
||||
} },
|
||||
{ options: {
|
||||
sql: "select 'double\"quote' as n, 'SRID=3857;POINT(2 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#s [n="double\\"quote" ] { marker-fill:red; }',
|
||||
cartocss_version: '2.1.0',
|
||||
cartocss_version: '2.1.0'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -1006,12 +969,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
{ options: {
|
||||
sql: "select .4 as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
|
||||
cartocss: '#s [n<=.2e-2] { marker-fill:red; }',
|
||||
cartocss_version: '2.1.0',
|
||||
cartocss_version: '2.1.0'
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -1034,19 +997,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
]
|
||||
};
|
||||
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expectedBody = { layergroupid: expected_token };
|
||||
if ( expected_token ) {
|
||||
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
|
||||
}
|
||||
@@ -1063,17 +1025,18 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "image/png");
|
||||
assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, similarity) {
|
||||
next(err);
|
||||
});
|
||||
assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png',
|
||||
IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
|
||||
next(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
@@ -1110,12 +1073,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
]
|
||||
};
|
||||
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?api_key=1234',
|
||||
url: layergroup_url + '?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -1141,7 +1104,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
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',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1149,7 +1112,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
},
|
||||
function check_get_tile(err, res) {
|
||||
if ( err ) throw err;
|
||||
var next = this;
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
return null;
|
||||
},
|
||||
@@ -1181,10 +1143,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, 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 (var 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 = {
|
||||
@@ -1199,14 +1161,14 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
};
|
||||
var errors = [];
|
||||
var expected_token;
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var data = JSON.stringify(layergroup);
|
||||
assert.ok(data.length > 1024*64);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup?api_key=1234',
|
||||
url: layergroup_url + '?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: data
|
||||
@@ -1218,8 +1180,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
var last_request = sqlapi_server.getLastRequest();
|
||||
assert.equal(last_request.method, 'POST');
|
||||
return null;
|
||||
},
|
||||
function cleanup(err) {
|
||||
@@ -1250,18 +1210,19 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
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',
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' +
|
||||
' from test_table limit 2',
|
||||
interactivity: 'cartodb_id'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -1283,6 +1244,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
);
|
||||
});
|
||||
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/167
|
||||
test("lack of response from sql-api will result in a timeout", function(done) {
|
||||
|
||||
@@ -1297,12 +1259,12 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/layergroup',
|
||||
url: layergroup_url,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
@@ -1323,9 +1285,10 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
var layergroupTtlRequest = {
|
||||
url: '/tiles/layergroup?config=' + encodeURIComponent(JSON.stringify({
|
||||
url: layergroup_url + '?config=' + encodeURIComponent(JSON.stringify({
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
@@ -1368,16 +1331,60 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
});
|
||||
|
||||
|
||||
test("it's not possible to override authorization with a crafted layergroup", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * from test_table_private_1',
|
||||
cartocss: '#layer { marker-fill:red; }',
|
||||
cartocss_version: '2.3.0',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
}
|
||||
],
|
||||
template: {
|
||||
auth: {
|
||||
method: "open"
|
||||
},
|
||||
name: "open"
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map?signer=localhost',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function(res) {
|
||||
assert.ok(res.body.match(/permission denied for relation test_table_private_1/));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
|
||||
// This test will add map_style records, like
|
||||
// 'map_style|null|publicuser|my_table',
|
||||
redis_client.keys("map_style|*", function(err, matches) {
|
||||
redis_client.del(matches, function(err) {
|
||||
redis_client.select(5, function(err, matches) {
|
||||
redis_client.del(matches, function() {
|
||||
redis_client.select(5, function() {
|
||||
redis_client.keys("user:localhost:mapviews*", function(err, matches) {
|
||||
redis_client.del(matches, function(err) {
|
||||
sqlapi_server.close(done);
|
||||
redis_client.del(matches, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
338
test/acceptance/multilayer_server.js
Normal file
338
test/acceptance/multilayer_server.js
Normal file
@@ -0,0 +1,338 @@
|
||||
var testHelper = require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
|
||||
var redis = require('redis');
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
var QueryTablesApi = require('../../lib/cartodb/api/query_tables_api');
|
||||
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require('../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
describe('tests from old api translated to multilayer', function() {
|
||||
|
||||
var layergroupUrl = '/api/v1/map';
|
||||
|
||||
var redisClient = redis.createClient(global.environment.redis.port);
|
||||
after(function(done) {
|
||||
// This test will add map_style records, like
|
||||
// 'map_style|null|publicuser|my_table',
|
||||
redisClient.keys("map_style|*", function(err, matches) {
|
||||
redisClient.del(matches, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var wadusSql = 'select 1 as cartodb_id, null::geometry as the_geom_webmercator';
|
||||
var pointSql = "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id";
|
||||
|
||||
function singleLayergroupConfig(sql, cartocss) {
|
||||
return {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss: cartocss,
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function createRequest(layergroup, userHost, apiKey) {
|
||||
var url = layergroupUrl;
|
||||
if (apiKey) {
|
||||
url += '?api_key=' + apiKey;
|
||||
}
|
||||
return {
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: userHost || 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
};
|
||||
}
|
||||
|
||||
it("layergroup creation fails if CartoCSS is bogus", function(done) {
|
||||
var layergroup = singleLayergroupConfig(wadusSql, '#my_table3{');
|
||||
assert.response(server,
|
||||
createRequest(layergroup),
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors[0].match(/^style0/));
|
||||
assert.ok(parsed.errors[0].match(/missing closing/));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("multiple bad styles returns 400 with all errors", function(done) {
|
||||
var layergroup = singleLayergroupConfig(wadusSql, '#my_table4{backgxxxxxround-color:#fff;foo:bar}');
|
||||
assert.response(server,
|
||||
createRequest(layergroup),
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
assert.ok(parsed.errors[0].match(/^style0/));
|
||||
assert.ok(parsed.errors[0].match(/Unrecognized rule: backgxxxxxround-color/));
|
||||
assert.ok(parsed.errors[0].match(/Unrecognized rule: foo/));
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Zoom is a special variable
|
||||
it("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done) {
|
||||
var layergroup = singleLayergroupConfig(pointSql, '#gadm4 [ zoom>=3] { marker-fill:red; }');
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/88
|
||||
it("getting a tile from a user-specific database should return an expected tile", function(done) {
|
||||
var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');
|
||||
|
||||
var backupDBHost = global.environment.postgres.host;
|
||||
global.environment.postgres.host = '6.6.6.6';
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'cartodb250user'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
|
||||
global.environment.postgres.host = backupDBHost;
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/89
|
||||
it("getting a tile with a user-specific database password", function(done) {
|
||||
var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }');
|
||||
|
||||
var backupDBPass = global.environment.postgres_auth_pass;
|
||||
global.environment.postgres_auth_pass = '<%= user_password %>';
|
||||
|
||||
assert.response(server,
|
||||
createRequest(layergroup, 'cartodb250user', '4321'),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
|
||||
global.environment.postgres_auth_pass = backupDBPass;
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("creating a layergroup from lzma param", function(done){
|
||||
var params = {
|
||||
config: JSON.stringify(singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'))
|
||||
};
|
||||
|
||||
testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("creating a layergroup from lzma param, invalid json input", function(done) {
|
||||
var params = {
|
||||
config: 'WADUS'
|
||||
};
|
||||
|
||||
testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, { errors: [ 'Unexpected token W' ] });
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("uses queries postgresql to figure affected tables in query", function(done) {
|
||||
var tableName = 'gadm4';
|
||||
var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', {
|
||||
databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db',
|
||||
tableName: tableName
|
||||
});
|
||||
|
||||
var layergroup = singleLayergroupConfig('select * from ' + tableName, '#gadm4 { marker-fill: red; }');
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
|
||||
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
|
||||
assert.equal(res.headers['x-cache-channel'], expectedCacheChannel);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("creates layergroup fails when postgresql queries fail to figure affected tables in query", function(done) {
|
||||
|
||||
var runQueryFn = QueryTablesApi.prototype.runQuery;
|
||||
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
|
||||
return queryHandler(new Error('fake error message'), [], callback);
|
||||
};
|
||||
|
||||
var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
QueryTablesApi.prototype.runQuery = runQueryFn;
|
||||
|
||||
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, {
|
||||
errors: ["Error: could not fetch affected tables and last updated time: fake error message"]
|
||||
});
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("tile requests works when postgresql queries fail to figure affected tables in query", function(done) {
|
||||
var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }');
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
var runQueryFn = QueryTablesApi.prototype.runQuery;
|
||||
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
|
||||
return queryHandler(new Error('failed to query database for affected tables'), [], callback);
|
||||
};
|
||||
|
||||
// reset internal cacheChannel cache
|
||||
serverOptions.channelCache = {};
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
||||
layergroupId: JSON.parse(res.body).layergroupid,
|
||||
z: 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
|
||||
QueryTablesApi.prototype.runQuery = runQueryFn;
|
||||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
655
test/acceptance/named_layers.js
Normal file
655
test/acceptance/named_layers.js
Normal file
@@ -0,0 +1,655 @@
|
||||
var test_helper = require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
suite('named_layers', function() {
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
|
||||
var wadusLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||
cartocss: '#layer { marker-fill: <%= color %>; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
var templateName = 'valid_template';
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
"placeholders": {
|
||||
"color": {
|
||||
"type": "css_color",
|
||||
"default": "#cc3300"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var tokenAuthTemplateName = 'auth_valid_template';
|
||||
var tokenAuthTemplate = {
|
||||
version: '0.0.1',
|
||||
name: tokenAuthTemplateName,
|
||||
auth: {
|
||||
method: 'token',
|
||||
valid_tokens: ['valid1', 'valid2']
|
||||
},
|
||||
placeholders: {
|
||||
color: {
|
||||
"type": "css_color",
|
||||
"default": "#cc3300"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var namedMapLayer = {
|
||||
type: 'named',
|
||||
options: {
|
||||
name: templateName,
|
||||
config: {},
|
||||
auth_tokens: []
|
||||
}
|
||||
};
|
||||
|
||||
var nestedNamedMapTemplateName = 'nested_template';
|
||||
var nestedNamedMapTemplate = {
|
||||
version: '0.0.1',
|
||||
name: nestedNamedMapTemplateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
namedMapLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
suiteSetup(function(done) {
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: true};
|
||||
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.addTemplate(username, template, function(err) {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail for non-existing template name', function(done) {
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: 'nonexistent'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.deepEqual(parsedBody, { errors: ["Template 'nonexistent' of user 'localhost' not found"] });
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('should return 403 if not properly authorized', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: tokenAuthTemplateName,
|
||||
config: {},
|
||||
auth_tokens: ['token1']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.deepEqual(
|
||||
parsedBody,
|
||||
{ errors: [ "Unauthorized 'auth_valid_template' template instantiation" ] }
|
||||
);
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
test('should return 200 and layergroup if properly authorized', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: tokenAuthTemplateName,
|
||||
config: {},
|
||||
auth_tokens: ['valid1']
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
test('should return 400 for nested named map layers', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: nestedNamedMapTemplateName
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.deepEqual(parsedBody, { errors: [ 'Nested named layers are not allowed' ] });
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
test('should return 200 and layergroup with private tables', function(done) {
|
||||
|
||||
var privateTableTemplateName = 'private_table_template';
|
||||
var privateTableTemplate = {
|
||||
version: '0.0.1',
|
||||
name: privateTableTemplateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select * from test_table_private_1',
|
||||
cartocss: '#layer { marker-fill: #cc3300; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: privateTableTemplateName
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createTemplate() {
|
||||
templateMaps.addTemplate(username, privateTableTemplate, this);
|
||||
},
|
||||
function createLayergroup(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
return parsedBody.layergroupid;
|
||||
},
|
||||
function requestTile(err, layergroupId) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function handleTileResponse(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
test_helper.checkCache(res);
|
||||
return true;
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
var next = this;
|
||||
templateMaps.delTemplate(username, privateTableTemplate, function(/*delErr*/) {
|
||||
// ignore deletion error
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
test('should return 200 and layergroup with private tables and interactivity', function(done) {
|
||||
|
||||
var privateTableTemplateNameInteractivity = 'private_table_template_interactivity';
|
||||
var privateTableTemplate = {
|
||||
"version": "0.0.1",
|
||||
"auth": {
|
||||
"method": "open"
|
||||
},
|
||||
"name": privateTableTemplateNameInteractivity,
|
||||
"layergroup": {
|
||||
"layers": [
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"attributes": {
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"id": "cartodb_id"
|
||||
},
|
||||
"cartocss": "#layer { marker-fill: #cc3300; }",
|
||||
"cartocss_version": "2.3.0",
|
||||
"interactivity": "cartodb_id",
|
||||
"sql": "select * from test_table_private_1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: privateTableTemplateNameInteractivity
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createTemplate() {
|
||||
templateMaps.addTemplate(username, privateTableTemplate, this);
|
||||
},
|
||||
function createLayergroup(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.ok(parsedBody.layergroupid);
|
||||
assert.ok(parsedBody.last_updated);
|
||||
|
||||
return parsedBody.layergroupid;
|
||||
},
|
||||
function requestTile(err, layergroupId) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function handleTileResponse(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
test_helper.checkCache(res);
|
||||
return true;
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
var next = this;
|
||||
templateMaps.delTemplate(username, privateTableTemplate, function(/*delErr*/) {
|
||||
// ignore deletion error
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
test('should return 403 when private table is accessed from non named layer', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select * from test_table_private_1',
|
||||
cartocss: '#layer { marker-fill: #cc3300; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'named',
|
||||
options: {
|
||||
name: templateName
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkLayergroup(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(response.body);
|
||||
assert.ok(parsedBody.errors[0].match(/permission denied for relation test_table_private_1/));
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: false};
|
||||
templateMaps.delTemplate(username, nestedNamedMapTemplateName, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.delTemplate(username, tokenAuthTemplateName, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.delTemplate(username, templateName, function(err) {
|
||||
return done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ var semver = require('semver');
|
||||
var Step = require('step');
|
||||
var strftime = require('strftime');
|
||||
var NamedMapsCacheEntry = require(__dirname + '/../../lib/cartodb/cache/model/named_maps_entry');
|
||||
var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js');
|
||||
var redis_stats_db = 5;
|
||||
|
||||
// Pollute the PG environment to make sure
|
||||
@@ -25,22 +24,13 @@ var serverOptions = ServerOptions();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) {
|
||||
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue};
|
||||
|
||||
suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
suite('template_api', function() {
|
||||
serverOptions.channelCache = {};
|
||||
|
||||
var redis_client = redis.createClient(global.environment.redis.port);
|
||||
var sqlapi_server;
|
||||
var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu
|
||||
var expected_last_updated = new Date(expected_last_updated_epoch).toISOString();
|
||||
|
||||
suiteSetup(function(done){
|
||||
sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done);
|
||||
// TODO: check redis is clean ?
|
||||
});
|
||||
|
||||
var template_acceptance1 = {
|
||||
version: '0.0.1',
|
||||
name: 'acceptance1',
|
||||
@@ -87,7 +77,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var expected_failure = false;
|
||||
var expected_tpl_id = "localhost@acceptance1";
|
||||
var post_request_1 = {
|
||||
url: '/tiles/template',
|
||||
url: '/api/v1/map/named',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
@@ -174,7 +164,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
broken_template.auth.method = 'token';
|
||||
delete broken_template.auth.tokens;
|
||||
var post_request_1 = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(broken_template)
|
||||
@@ -204,7 +194,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
broken_template.auth.method = 'token';
|
||||
broken_template.auth.tokens = [];
|
||||
var post_request_1 = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(broken_template)
|
||||
@@ -231,7 +221,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var broken_template = JSON.parse(JSON.stringify(template_acceptance1));
|
||||
broken_template.name = 'broken1';
|
||||
var post_request_1 = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(broken_template)
|
||||
@@ -255,7 +245,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
broken_template.auth.method = 'token';
|
||||
broken_template.auth.tokens = [];
|
||||
var put_request_1 = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(broken_template)
|
||||
@@ -276,7 +266,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
'Error for invalid authentication on PUT does not match ' +
|
||||
re + ': ' + parsed.error);
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -312,7 +302,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
Step(function postTemplate1(err, res) {
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost.localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
@@ -321,7 +311,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
},
|
||||
function testCORS() {
|
||||
assert.response(server, {
|
||||
url: '/tiles/template/acceptance1',
|
||||
url: '/api/v1/map/named/acceptance1',
|
||||
method: 'OPTIONS'
|
||||
},{
|
||||
status: 200,
|
||||
@@ -341,7 +331,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
Step(function postTemplate1(err, res) {
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(tmpl)
|
||||
@@ -353,7 +343,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
function testCORS() {
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/tiles/template/' + tmpl.name,
|
||||
url: '/api/v1/map/named/' + tmpl.name,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
},{
|
||||
@@ -367,7 +357,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
function deleteTemplate(err) {
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tmpl.name + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tmpl.name + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' }
|
||||
}
|
||||
@@ -390,7 +380,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
@@ -411,7 +401,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var backup_name = template_acceptance1.name;
|
||||
template_acceptance1.name += '_new';
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
@@ -430,7 +420,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
tplid2 = parsed.template_id;
|
||||
var next = this;
|
||||
var get_request = {
|
||||
url: '/tiles/template',
|
||||
url: '/api/v1/map/named',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -448,7 +438,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(err.match(/authenticated user/), err);
|
||||
var next = this;
|
||||
var get_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -507,7 +497,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(makeTemplate())
|
||||
@@ -526,7 +516,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var backup_name = template_acceptance1.name;
|
||||
template_acceptance1.name = 'changed_name';
|
||||
var put_request = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance1)
|
||||
@@ -545,7 +535,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsedBody.error.match(/cannot update name/i),
|
||||
'Unexpected error for invalid update: ' + parsedBody.error);
|
||||
var put_request = {
|
||||
url: '/tiles/template/unexistent/?api_key=1234',
|
||||
url: '/api/v1/map/named/unexistent/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(makeTemplate())
|
||||
@@ -563,7 +553,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsedBody.error.match(/cannot update name/i),
|
||||
'Unexpected error for invalid update: ' + parsedBody.error);
|
||||
var put_request = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(makeTemplate())
|
||||
@@ -619,7 +609,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(makeTemplate())
|
||||
@@ -636,7 +626,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -653,7 +643,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsedBody.error.match(/only.*authenticated.*user/i),
|
||||
'Unexpected error for unauthenticated template get: ' + parsedBody.error);
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -708,7 +698,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(makeTemplate())
|
||||
@@ -725,7 +715,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -742,7 +732,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template' from response body: " + res.body);
|
||||
assert.deepEqual(extendDefaultsTemplate(makeTemplate()), parsed.template);
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -760,7 +750,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsed.error.match(/only.*authenticated.*user/i),
|
||||
'Unexpected error for unauthenticated template get: ' + parsed.error);
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -774,7 +764,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body);
|
||||
assert.ok(!res.body, 'Unexpected body in DELETE /template response');
|
||||
var get_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -852,7 +842,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance2)
|
||||
@@ -869,7 +859,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -890,7 +880,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsed.error.match(/unauthorized/i),
|
||||
'Unexpected error for unauthorized instance : ' + parsed.error);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'foreign', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -909,7 +899,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsed.error.match(/cannot instanciate/i),
|
||||
'Unexpected error for forbidden instance : ' + parsed.error);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -933,7 +923,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'last_updated' from response body: " + res.body);
|
||||
// TODO: check value of last_updated ?
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb0/0/0/0.png',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb0/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -953,7 +943,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
'Unexpected error for unauthorized instance '
|
||||
+ '(expected /permission denied/): ' + parsed.error);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
url: '/api/v1/map/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -975,7 +965,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
if ( err ) throw err;
|
||||
var foreignsigned = layergroupid.replace(/[^@]*@/, 'foreign@');
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + foreignsigned + '/0/0/0.png?auth_token=valid1',
|
||||
url: '/api/v1/map/' + foreignsigned + '/0/0/0.png?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1001,7 +991,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -1014,7 +1004,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.equal(res.statusCode, 204,
|
||||
'Deleting template: ' + res.statusCode + ':' + res.body);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
url: '/api/v1/map/' + layergroupid + '/0/0/0.png?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1086,7 +1076,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
@@ -1103,7 +1093,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1124,7 +1114,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsed.error.match(/unauthorized/i),
|
||||
'Unexpected error for unauthorized instance : ' + parsed.error);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1148,7 +1138,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'last_updated' from response body: " + res.body);
|
||||
// TODO: check value of last_updated ?
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb0/0/0/0/0.json.torque',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb0/0/0/0/0.json.torque',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1168,7 +1158,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
'Unexpected error for unauthorized instance '
|
||||
+ '(expected /permission denied): ' + parsed.error);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb1/0/0/0/0.json.torque?auth_token=valid1',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/0.json.torque?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1190,7 +1180,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
serverOptions = ServerOptions(); // need to clean channel cache
|
||||
server = new CartodbWindshaft(serverOptions);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1214,7 +1204,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -1227,7 +1217,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.equal(res.statusCode, 204,
|
||||
'Deleting template: ' + res.statusCode + ':' + res.body);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb2/0/0/0/0.json.torque?auth_token=valid1',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb2/0/0/0/0.json.torque?auth_token=valid1',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1301,7 +1291,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
@@ -1318,7 +1308,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1339,7 +1329,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.ok(parsed.error.match(/unauthorized/i),
|
||||
'Unexpected error for unauthorized instance : ' + parsed.error);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1363,7 +1353,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'last_updated' from response body: " + res.body);
|
||||
// TODO: check value of last_updated ?
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb0/0/attributes/5',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb0/0/attributes/5',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1383,7 +1373,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
'Unexpected error for unauthorized getAttributes '
|
||||
+ '(expected /permission denied/): ' + parsed.error);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb1/0/attributes/5?auth_token=valid2',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb1/0/attributes/5?auth_token=valid2',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1404,7 +1394,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -1417,7 +1407,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.equal(res.statusCode, 204,
|
||||
'Deleting template: ' + res.statusCode + ':' + res.body);
|
||||
var get_request = {
|
||||
url: '/tiles/layergroup/' + layergroupid + ':cb2/0/attributes/5?auth_token=valid2',
|
||||
url: '/api/v1/map/' + layergroupid + ':cb2/0/attributes/5?auth_token=valid2',
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' },
|
||||
encoding: 'binary'
|
||||
@@ -1491,7 +1481,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance_open)
|
||||
@@ -1508,7 +1498,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id,
|
||||
url: '/api/v1/map/named/' + tpl_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1560,7 +1550,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance_open)
|
||||
@@ -1577,7 +1567,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + "/jsonp?callback=test",
|
||||
url: '/api/v1/map/named/' + tpl_id + "/jsonp?callback=test",
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' }
|
||||
}
|
||||
@@ -1634,7 +1624,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance_open)
|
||||
@@ -1651,7 +1641,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + "/jsonp?callback=test%config=" + JSON.stringify('{color:blue}'),
|
||||
url: '/api/v1/map/named/' + tpl_id + "/jsonp?callback=test%config=" + JSON.stringify('{color:blue}'),
|
||||
method: 'GET',
|
||||
headers: {host: 'localhost' }
|
||||
}
|
||||
@@ -1712,7 +1702,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template)
|
||||
@@ -1726,7 +1716,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
template_id = JSON.parse(res.body).template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + template_id,
|
||||
url: '/api/v1/map/named/' + template_id,
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify({})
|
||||
@@ -1761,7 +1751,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
if ( err ) throw err;
|
||||
var del_request = {
|
||||
url: '/tiles/template/' + template_id + '?api_key=1234',
|
||||
url: '/api/v1/map/named/' + template_id + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {host: 'localhost'}
|
||||
}
|
||||
@@ -1814,7 +1804,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
{
|
||||
var next = this;
|
||||
var post_request = {
|
||||
url: '/tiles/template?api_key=1234',
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_acceptance2)
|
||||
@@ -1831,7 +1821,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
tpl_id = parsed.template_id;
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1858,7 +1848,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
var changedTemplate = JSON.parse(JSON.stringify(template_acceptance2));
|
||||
changedTemplate.auth.method = 'open';
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '/?api_key=1234',
|
||||
url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234',
|
||||
method: 'PUT',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(changedTemplate)
|
||||
@@ -1876,7 +1866,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
"Missing 'template_id' from response body: " + res.body);
|
||||
assert.equal(tpl_id, parsed.template_id);
|
||||
var post_request = {
|
||||
url: '/tiles/template/' + tpl_id + '?auth_token=valid2',
|
||||
url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2',
|
||||
method: 'POST',
|
||||
headers: {host: 'localhost', 'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(template_params)
|
||||
@@ -1923,6 +1913,182 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
test("can use an http layer", function(done) {
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
var httpTemplateName = 'acceptance_http';
|
||||
var httpTemplate = {
|
||||
version: '0.0.1',
|
||||
name: httpTemplateName,
|
||||
layergroup: {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: "http",
|
||||
options: {
|
||||
urlTemplate: "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
|
||||
subdomains: [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: "select * from test_table_private_1",
|
||||
cartocss: '#layer { marker-fill:blue; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var template_params = {};
|
||||
|
||||
var errors = [];
|
||||
var expectedTemplateId = username + '@' + httpTemplateName;
|
||||
var layergroupid;
|
||||
Step(
|
||||
function createTemplate()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: username,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(httpTemplate)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function instantiateTemplate(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
assert.deepEqual(JSON.parse(res.body), { template_id: expectedTemplateId });
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named/' + expectedTemplateId,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: username,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(template_params)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
next(null, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function fetchTile(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from response body: " + res.body);
|
||||
layergroupid = parsed.layergroupid;
|
||||
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/' + layergroupid + '/all/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: username
|
||||
},
|
||||
encoding: 'binary'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res) {
|
||||
next(null, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function checkTile(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
assert.equal(res.headers['content-type'], "image/png");
|
||||
return null;
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var next = this;
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named/' + expectedTemplateId + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 204
|
||||
},
|
||||
function(res, err) {
|
||||
next(err, res);
|
||||
}
|
||||
);
|
||||
},
|
||||
function finish(err) {
|
||||
if (err) {
|
||||
errors.push(err);
|
||||
}
|
||||
redis_client.keys("map_*|localhost", function(err, keys) {
|
||||
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 ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
});
|
||||
} else {
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
}
|
||||
else done(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
|
||||
// This test will add map_style records, like
|
||||
@@ -1935,7 +2101,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
redis_client.select(5, function(err) {
|
||||
redis_client.keys("user:localhost:mapviews*", function(err, keys) {
|
||||
redis_client.del(keys, function(err) {
|
||||
sqlapi_server.close(done);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1945,5 +2111,3 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
BIN
test/fixtures/render-timeout-fallback.png
vendored
Normal file
BIN
test/fixtures/render-timeout-fallback.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
298
test/integration/mapconfig_named_layers_datasource.js
Normal file
298
test/integration/mapconfig_named_layers_datasource.js
Normal file
@@ -0,0 +1,298 @@
|
||||
var test_helper = require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
|
||||
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
|
||||
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
|
||||
var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
|
||||
var wadusSql = 'select 1 wadusLayer, null::geometry the_geom_webmercator';
|
||||
var wadusLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: wadusSql,
|
||||
cartocss: '#layer { marker-fill: black; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var wadusTemplateSql = 'select 1 wadusTemplateLayer, null::geometry the_geom_webmercator';
|
||||
var wadusTemplateLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: wadusTemplateSql,
|
||||
cartocss: '#layer { marker-fill: <%= color %>; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var wadusMapnikSql = 'select 1 wadusMapnikLayer, null::geometry the_geom_webmercator';
|
||||
var wadusMapnikLayer = {
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: wadusMapnikSql,
|
||||
cartocss: '#layer { polygon-fill: <%= polygon_color %>; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
var templateName = 'valid_template';
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
"placeholders": {
|
||||
"color": {
|
||||
"type": "css_color",
|
||||
"default": "#cc3300"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusTemplateLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var multipleLayersTemplateName = 'multiple_valid_template';
|
||||
var multipleLayersTemplate = {
|
||||
version: '0.0.1',
|
||||
name: multipleLayersTemplateName,
|
||||
auth: {
|
||||
method: 'token',
|
||||
valid_tokens: ['valid1', 'valid2']
|
||||
},
|
||||
"placeholders": {
|
||||
"polygon_color": {
|
||||
"type": "css_color",
|
||||
"default": "green"
|
||||
},
|
||||
"color": {
|
||||
"type": "css_color",
|
||||
"default": "red"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusMapnikLayer,
|
||||
wadusTemplateLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
suite('named_layers datasources', function() {
|
||||
suiteSetup(function(done) {
|
||||
templateMaps.addTemplate(username, template, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.addTemplate(username, multipleLayersTemplate, done);
|
||||
});
|
||||
});
|
||||
|
||||
function makeNamedMapLayerConfig(layers) {
|
||||
return {
|
||||
version: '1.3.0',
|
||||
layers: layers
|
||||
};
|
||||
}
|
||||
|
||||
var simpleNamedLayer = {
|
||||
type: 'named',
|
||||
options: {
|
||||
name: templateName
|
||||
}
|
||||
};
|
||||
|
||||
var multipleLayersNamedLayer = {
|
||||
type: 'named',
|
||||
options: {
|
||||
name: multipleLayersTemplateName,
|
||||
auth_tokens: ['valid2']
|
||||
}
|
||||
};
|
||||
|
||||
var testScenarios = [
|
||||
{
|
||||
desc: 'without datasource for non-named layers',
|
||||
config: makeNamedMapLayerConfig([wadusLayer]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 1);
|
||||
|
||||
assert.equal(layers[0].type, 'cartodb');
|
||||
assert.equal(layers[0].options.sql, wadusSql);
|
||||
assert.equal(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
done();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'with datasource for the named layer but not for the normal',
|
||||
config: makeNamedMapLayerConfig([wadusLayer, simpleNamedLayer]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
|
||||
assert.equal(layers[0].type, 'cartodb');
|
||||
assert.equal(layers[0].options.sql, wadusSql);
|
||||
assert.equal(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
assert.equal(layers[1].type, 'cartodb');
|
||||
assert.equal(layers[1].options.sql, wadusTemplateSql);
|
||||
var layerDatasource = datasource.getLayerDatasource(1);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
done();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'with datasource for the multiple layers in the named but not for the normal',
|
||||
config: makeNamedMapLayerConfig([wadusLayer, multipleLayersNamedLayer]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 3);
|
||||
|
||||
assert.equal(layers[0].type, 'cartodb');
|
||||
assert.equal(layers[0].options.sql, wadusSql);
|
||||
assert.equal(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
assert.equal(layers[1].type, 'mapnik');
|
||||
assert.equal(layers[1].options.sql, wadusMapnikSql);
|
||||
var layerDatasource = datasource.getLayerDatasource(1);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[2].type, 'cartodb');
|
||||
assert.equal(layers[2].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(2);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
done();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'all with datasource because all are named',
|
||||
config: makeNamedMapLayerConfig([multipleLayersNamedLayer, simpleNamedLayer]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 3);
|
||||
|
||||
assert.equal(layers[0].type, 'mapnik');
|
||||
assert.equal(layers[0].options.sql, wadusMapnikSql);
|
||||
var layerDatasource = datasource.getLayerDatasource(0);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[1].type, 'cartodb');
|
||||
assert.equal(layers[1].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(1);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[2].type, 'cartodb');
|
||||
assert.equal(layers[2].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(2);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
done();
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
desc: 'with a mix of datasource and no datasource depending if layers are named or not',
|
||||
config: makeNamedMapLayerConfig([simpleNamedLayer, multipleLayersNamedLayer, wadusLayer, simpleNamedLayer, wadusLayer, multipleLayersNamedLayer]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 8);
|
||||
|
||||
assert.equal(layers[0].type, 'cartodb');
|
||||
assert.equal(layers[0].options.sql, wadusTemplateSql);
|
||||
var layerDatasource = datasource.getLayerDatasource(0);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[1].type, 'mapnik');
|
||||
assert.equal(layers[1].options.sql, wadusMapnikSql);
|
||||
layerDatasource = datasource.getLayerDatasource(1);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[2].type, 'cartodb');
|
||||
assert.equal(layers[2].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(2);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[3].type, 'cartodb');
|
||||
assert.equal(layers[3].options.sql, wadusSql);
|
||||
assert.equal(datasource.getLayerDatasource(3), undefined);
|
||||
|
||||
assert.equal(layers[4].type, 'cartodb');
|
||||
assert.equal(layers[4].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(4);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[5].type, 'cartodb');
|
||||
assert.equal(layers[5].options.sql, wadusSql);
|
||||
assert.equal(datasource.getLayerDatasource(5), undefined);
|
||||
|
||||
assert.equal(layers[6].type, 'mapnik');
|
||||
assert.equal(layers[6].options.sql, wadusMapnikSql);
|
||||
layerDatasource = datasource.getLayerDatasource(6);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
assert.equal(layers[7].type, 'cartodb');
|
||||
assert.equal(layers[7].options.sql, wadusTemplateSql);
|
||||
layerDatasource = datasource.getLayerDatasource(7);
|
||||
assert.notEqual(layerDatasource, undefined);
|
||||
assert.ok(layerDatasource.user);
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
test('should return a list of layers ' + testScenario.desc, function(done) {
|
||||
mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, pgConnection, function(err, layers, datasource) {
|
||||
testScenario.test(err, layers, datasource, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
templateMaps.delTemplate(username, templateName, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
312
test/integration/mapconfig_named_layers_expanded.js
Normal file
312
test/integration/mapconfig_named_layers_expanded.js
Normal file
@@ -0,0 +1,312 @@
|
||||
var testHelper = require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
var PgConnection = require(__dirname + '/../../lib/cartodb/backends/pg_connection');
|
||||
var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter');
|
||||
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
suite('mapconfig_named_layers_adapter', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
|
||||
var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
|
||||
var wadusLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||
cartocss: '#layer { marker-fill: <%= color %>; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var wadusMapnikLayer = {
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||
cartocss: '#layer { polygon-fill: <%= polygon_color %>; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
var templateName = 'valid_template';
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
"placeholders": {
|
||||
"color": {
|
||||
"type": "css_color",
|
||||
"default": "#cc3300"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var tokenAuthTemplateName = 'auth_valid_template';
|
||||
var tokenAuthTemplate = {
|
||||
version: '0.0.1',
|
||||
name: tokenAuthTemplateName,
|
||||
auth: {
|
||||
method: 'token',
|
||||
valid_tokens: ['valid1', 'valid2']
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var multipleLayersTemplateName = 'multiple_valid_template';
|
||||
var multipleLayersTemplate = {
|
||||
version: '0.0.1',
|
||||
name: multipleLayersTemplateName,
|
||||
auth: {
|
||||
method: 'token',
|
||||
valid_tokens: ['valid1', 'valid2']
|
||||
},
|
||||
"placeholders": {
|
||||
"polygon_color": {
|
||||
"type": "css_color",
|
||||
"default": "green"
|
||||
},
|
||||
"color": {
|
||||
"type": "css_color",
|
||||
"default": "red"
|
||||
}
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusMapnikLayer,
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var namedMapLayer = {
|
||||
type: 'named',
|
||||
options: {
|
||||
name: templateName,
|
||||
config: {},
|
||||
auth_tokens: []
|
||||
}
|
||||
};
|
||||
|
||||
var nestedNamedMapTemplateName = 'nested_template';
|
||||
var nestedNamedMapTemplate = {
|
||||
version: '0.0.1',
|
||||
name: nestedNamedMapTemplateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
layergroup: {
|
||||
layers: [
|
||||
namedMapLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function makeNamedMapLayerConfig(options) {
|
||||
return {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'named',
|
||||
options: options
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
suiteSetup(function(done) {
|
||||
templateMaps.addTemplate(username, template, done);
|
||||
});
|
||||
|
||||
test('should fail for named map layer with missing name', function(done) {
|
||||
var missingNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
config: {}
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
assert.equal(err.message, 'Missing Named Map `name` in layer options');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail for non-existing template name', function(done) {
|
||||
var missingTemplateName = 'wadus';
|
||||
var nonExistentNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: missingTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
assert.equal(err.message, "Template '" + missingTemplateName + "' of user '" + username + "' not found");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail if not properly authorized', function(done) {
|
||||
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var nonAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: tokenAuthTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
assert.equal(err.message, "Unauthorized '" + tokenAuthTemplateName + "' template instantiation");
|
||||
|
||||
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail for nested named map layers', function(done) {
|
||||
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var nestedNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: nestedNamedMapTemplateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(err);
|
||||
assert.ok(!layers);
|
||||
assert.ok(!datasource);
|
||||
assert.equal(err.message, 'Nested named layers are not allowed');
|
||||
|
||||
templateMaps.delTemplate(username, nestedNamedMapTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should return an expanded list of layers for a named map layer', function(done) {
|
||||
var validNamedMapMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: templateName
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.ok(layers.length, 1);
|
||||
assert.ok(layers[0].type, 'cartodb');
|
||||
assert.notEqual(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should return on auth=token with valid tokens provided', function(done) {
|
||||
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var validAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: tokenAuthTemplateName,
|
||||
auth_tokens: ['valid1']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 1);
|
||||
assert.notEqual(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should return an expanded list of layers for a named map layer, multiple layers version', function(done) {
|
||||
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var multipleLayersNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: multipleLayersTemplateName,
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
|
||||
assert.equal(layers[0].type, 'mapnik');
|
||||
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: green; }');
|
||||
assert.notEqual(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
assert.equal(layers[1].type, 'cartodb');
|
||||
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: red; }');
|
||||
assert.notEqual(datasource.getLayerDatasource(1), undefined);
|
||||
|
||||
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should replace template params with the given config', function(done) {
|
||||
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var color = '#cc3300',
|
||||
polygonColor = '#ff9900';
|
||||
|
||||
var multipleLayersNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||
name: multipleLayersTemplateName,
|
||||
config: {
|
||||
polygon_color: polygonColor,
|
||||
color: color
|
||||
},
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
|
||||
assert.equal(layers[0].type, 'mapnik');
|
||||
assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: ' + polygonColor + '; }');
|
||||
assert.notEqual(datasource.getLayerDatasource(0), undefined);
|
||||
|
||||
assert.equal(layers[1].type, 'cartodb');
|
||||
assert.equal(layers[1].options.cartocss, '#layer { marker-fill: ' + color + '; }');
|
||||
assert.notEqual(datasource.getLayerDatasource(1), undefined);
|
||||
|
||||
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
templateMaps.delTemplate(username, templateName, done);
|
||||
});
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
var http = require('http');
|
||||
var url = require('url');
|
||||
var _ = require('underscore');
|
||||
|
||||
var SQLAPIEmulator = function(port, cb) {
|
||||
|
||||
this.queries = [];
|
||||
var that = this;
|
||||
this.requests = [];
|
||||
|
||||
this.sqlapi_server = http.createServer(function(req,res) {
|
||||
//console.log("server got request with method " + req.method);
|
||||
var query;
|
||||
|
||||
that.requests.push(req);
|
||||
|
||||
if ( req.method == 'GET' ) {
|
||||
query = url.parse(req.url, true).query;
|
||||
that.handleQuery(query, res);
|
||||
}
|
||||
else if ( req.method == 'POST') {
|
||||
var data = '';
|
||||
req.on('data', function(chunk) {
|
||||
//console.log("GOT Chunk " + chunk);
|
||||
data += chunk;
|
||||
});
|
||||
req.on('end', function() {
|
||||
//console.log("Data is: "); console.dir(data);
|
||||
query = JSON.parse(data);
|
||||
//console.log("handleQuery is " + that.handleQuery);
|
||||
that.handleQuery(query, res);
|
||||
});
|
||||
}
|
||||
else {
|
||||
that.handleQuery('SQLAPIEmu does not support method' + req.method, res);
|
||||
}
|
||||
}).listen(port, cb);
|
||||
};
|
||||
|
||||
SQLAPIEmulator.prototype.handleQuery = function(query, res) {
|
||||
this.queries.push(query);
|
||||
if ( query.q.match('SQLAPIERROR') ) {
|
||||
res.statusCode = 400;
|
||||
res.write(JSON.stringify({'error':'Some error occurred'}));
|
||||
} else if ( query.q.match('SQLAPINOANSWER') ) {
|
||||
console.log("SQLAPIEmulator will never respond, on request");
|
||||
return;
|
||||
} else if (query.q.match('tablenames')) {
|
||||
var tableNames = JSON.stringify(query);
|
||||
res.write(queryResult({tablenames: '{' + tableNames + '}', max: 1234567890.123}));
|
||||
} else if ( query.q.match('EPOCH.* as max') ) {
|
||||
// This is the structure of the known query sent by tiler
|
||||
res.write(queryResult({max: 1234567890.123}));
|
||||
} else {
|
||||
if ( query.q.match('_private_') && query.api_key === undefined) {
|
||||
res.statusCode = 403;
|
||||
res.write(JSON.stringify({'error':'forbidden: ' + JSON.stringify(query)}));
|
||||
} else {
|
||||
var qs = JSON.stringify(query);
|
||||
res.write(queryResult({cdb_querytables: '{' + qs + '}', max: 1234567890.123}));
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
SQLAPIEmulator.prototype.close = function(cb) {
|
||||
this.sqlapi_server.close(cb);
|
||||
};
|
||||
|
||||
SQLAPIEmulator.prototype.getLastRequest = function() {
|
||||
return this.requests.pop();
|
||||
};
|
||||
|
||||
function queryResult(row) {
|
||||
return JSON.stringify({
|
||||
rows: [row]
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SQLAPIEmulator;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
var net = require('net');
|
||||
|
||||
module.exports = function(on_cmd_recieved, test_callback) {
|
||||
var self = this;
|
||||
var welcome_msg = 'hi, im a varnish emu, right?';
|
||||
|
||||
self.commands_recieved = [];
|
||||
|
||||
var server = net.createServer(function (socket) {
|
||||
var command = '';
|
||||
socket.write("200 " + welcome_msg.length + "\n");
|
||||
socket.write(welcome_msg);
|
||||
socket.on('data', function(data) {
|
||||
self.commands_recieved.push(data);
|
||||
on_cmd_recieved && on_cmd_recieved(self.commands_recieved);
|
||||
socket.write('200 0\n');
|
||||
});
|
||||
});
|
||||
server.listen(1337, "127.0.0.1");
|
||||
|
||||
server.on('listening', function(){
|
||||
test_callback();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -246,9 +246,10 @@ assert.response = function(server, req, res, msg){
|
||||
assert.equal(
|
||||
response.statusCode,
|
||||
status,
|
||||
msg + 'Invalid response status code.\n'
|
||||
msg + colorize('Invalid response status code.\n'
|
||||
+ ' Expected: [green]{' + status + '}\n'
|
||||
+ ' Got: [red]{' + response.statusCode + '}'
|
||||
+ ' Got: [red]{' + response.statusCode + '}\n'
|
||||
+ ' Response body: ' + response.body)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -280,3 +281,16 @@ assert.response = function(server, req, res, msg){
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Colorize the given string using ansi-escape sequences.
|
||||
* Disabled when --boring is set.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
*/
|
||||
function colorize(str) {
|
||||
var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
|
||||
return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str) {
|
||||
return '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
|
||||
});
|
||||
}
|
||||
@@ -78,6 +78,12 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
psql -c "CREATE EXTENSION plpythonu;" ${TEST_DB}
|
||||
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryStatements.sql -o sql/CDB_QueryStatements.sql
|
||||
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryTables.sql -o sql/CDB_QueryTables.sql
|
||||
cat sql/CDB_QueryStatements.sql sql/CDB_QueryTables.sql |
|
||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
fi
|
||||
|
||||
if test x"$PREPARE_REDIS" = xyes; then
|
||||
|
||||
14
test/support/sql/CDB_QueryStatements.sql
Normal file
14
test/support/sql/CDB_QueryStatements.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Return an array of statements found in the given query text
|
||||
--
|
||||
-- Regexp curtesy of Hubert Lubaczewski (depesz)
|
||||
-- Implemented in plpython for performance reasons
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
|
||||
RETURNS SETOF TEXT AS $$
|
||||
import re
|
||||
pat = re.compile( r'''((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)''', re.DOTALL )
|
||||
for match in pat.findall(query):
|
||||
cleaned = match[0].strip()
|
||||
if ( cleaned ):
|
||||
yield cleaned
|
||||
$$ language 'plpythonu' IMMUTABLE STRICT;
|
||||
67
test/support/sql/CDB_QueryTables.sql
Normal file
67
test/support/sql/CDB_QueryTables.sql
Normal file
@@ -0,0 +1,67 @@
|
||||
-- Return an array of table names scanned by a given query
|
||||
--
|
||||
-- Requires PostgreSQL 9.x+
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
|
||||
RETURNS name[]
|
||||
AS $$
|
||||
DECLARE
|
||||
exp XML;
|
||||
tables NAME[];
|
||||
rec RECORD;
|
||||
rec2 RECORD;
|
||||
BEGIN
|
||||
|
||||
tables := '{}';
|
||||
|
||||
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
|
||||
|
||||
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
|
||||
--RAISE WARNING 'Skipping %', rec.q;
|
||||
CONTINUE;
|
||||
END IF;
|
||||
|
||||
BEGIN
|
||||
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
|
||||
EXCEPTION WHEN others THEN
|
||||
-- TODO: if error is 'relation "xxxxxx" does not exist', take xxxxxx as
|
||||
-- the affected table ?
|
||||
RAISE WARNING 'CDB_QueryTables cannot explain query: % (%: %)', rec.q, SQLSTATE, SQLERRM;
|
||||
RAISE EXCEPTION '%', SQLERRM;
|
||||
CONTINUE;
|
||||
END;
|
||||
|
||||
-- Now need to extract all values of <Relation-Name>
|
||||
|
||||
-- RAISE DEBUG 'Explain: %', exp;
|
||||
|
||||
FOR rec2 IN WITH
|
||||
inp AS (
|
||||
SELECT
|
||||
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)::name as p, unnest(s)::name as sc from inp
|
||||
LOOP
|
||||
-- RAISE DEBUG 'tab: %', rec2.p;
|
||||
-- RAISE DEBUG 'sc: %', rec2.sc;
|
||||
tables := array_append(tables, (rec2.sc || '.' || rec2.p)::name);
|
||||
END LOOP;
|
||||
|
||||
-- RAISE DEBUG 'Tables: %', tables;
|
||||
|
||||
END LOOP;
|
||||
|
||||
-- RAISE DEBUG 'Tables: %', tables;
|
||||
|
||||
-- Remove duplicates and sort by name
|
||||
IF array_upper(tables, 1) > 0 THEN
|
||||
WITH dist as ( SELECT DISTINCT unnest(tables)::text as p ORDER BY p )
|
||||
SELECT array_agg(p) from dist into tables;
|
||||
END IF;
|
||||
|
||||
--RAISE DEBUG 'Tables: %', tables;
|
||||
|
||||
return tables;
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
|
||||
@@ -177,3 +177,15 @@ CREATE TABLE test_table_private_1 (
|
||||
INSERT INTO test_table_private_1 SELECT * from test_table;
|
||||
|
||||
GRANT ALL ON TABLE test_table_private_1 TO :TESTUSER;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS
|
||||
CDB_TableMetadata (
|
||||
tabname regclass not null primary key,
|
||||
updated_at timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table'::regclass, '2009-02-13T23:31:30.123Z');
|
||||
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('test_table_private_1'::regclass, '2009-02-13T23:31:30.123Z');
|
||||
|
||||
-- GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
|
||||
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
|
||||
|
||||
60
test/unit/cartodb/cdb_request.test.js
Normal file
60
test/unit/cartodb/cdb_request.test.js
Normal file
@@ -0,0 +1,60 @@
|
||||
require('../../support/test_helper');
|
||||
var assert = require('assert');
|
||||
|
||||
var CdbRequest = require('../../../lib/cartodb/models/cdb_request');
|
||||
|
||||
describe('req2params', function() {
|
||||
|
||||
function createRequest(host, userParam) {
|
||||
var req = {
|
||||
params: {},
|
||||
headers: {
|
||||
host: host
|
||||
}
|
||||
};
|
||||
|
||||
if (userParam) {
|
||||
req.params.user = userParam;
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
it('extracts name from host header', function() {
|
||||
var cdbRequest = new CdbRequest();
|
||||
var user = cdbRequest.userByReq(createRequest('localhost'));
|
||||
|
||||
assert.equal(user, 'localhost');
|
||||
});
|
||||
|
||||
it('extracts name from subdomain host header in case of no config', function() {
|
||||
var userFromHostConfig = global.environment.user_from_host;
|
||||
global.environment.user_from_host = null;
|
||||
|
||||
var cdbRequest = new CdbRequest();
|
||||
var user = cdbRequest.userByReq(createRequest('development.localhost.lan'));
|
||||
|
||||
global.environment.user_from_host = userFromHostConfig;
|
||||
|
||||
assert.equal(user, 'development');
|
||||
});
|
||||
|
||||
it('considers user param before headers', function() {
|
||||
var cdbRequest = new CdbRequest();
|
||||
var user = cdbRequest.userByReq(createRequest('localhost', 'development'));
|
||||
|
||||
assert.equal(user, 'development');
|
||||
});
|
||||
|
||||
it('returns undefined when it cannot extract username', function() {
|
||||
var userFromHostConfig = global.environment.user_from_host;
|
||||
global.environment.user_from_host = null;
|
||||
|
||||
var cdbRequest = new CdbRequest();
|
||||
var user = cdbRequest.userByReq(createRequest('localhost'));
|
||||
|
||||
global.environment.user_from_host = userFromHostConfig;
|
||||
|
||||
assert.equal(user, undefined);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
var assert = require('assert')
|
||||
, _ = require('underscore')
|
||||
, redis = require('redis')
|
||||
, test_helper = require('../../support/test_helper')
|
||||
, tests = module.exports = {};
|
||||
var assert = require('assert');
|
||||
var _ = require('underscore');
|
||||
var test_helper = require('../../support/test_helper');
|
||||
|
||||
suite('req2params', function() {
|
||||
|
||||
@@ -24,7 +22,7 @@ suite('req2params', function() {
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
|
||||
assert.equal(req.params.dbname, test_database, 'could forge dbname: '+ req.params.dbname);
|
||||
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
|
||||
done();
|
||||
@@ -38,7 +36,7 @@ suite('req2params', function() {
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
|
||||
assert.equal(req.params.dbname, test_database);
|
||||
assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')');
|
||||
done();
|
||||
@@ -52,7 +50,7 @@ suite('req2params', function() {
|
||||
assert.ok(_.isObject(req.query), 'request has query');
|
||||
assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query');
|
||||
assert.ok(req.hasOwnProperty('params'), 'request has params');
|
||||
assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity');
|
||||
assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity');
|
||||
assert.equal(req.params.dbname, test_database);
|
||||
assert.equal(req.params.dbuser, test_user);
|
||||
|
||||
@@ -65,23 +63,34 @@ suite('req2params', function() {
|
||||
});
|
||||
|
||||
test('it should extend params with decoded lzma', function(done) {
|
||||
var qo = {
|
||||
style: 'test',
|
||||
style_version: '2.1.0',
|
||||
cache_buster: 5
|
||||
};
|
||||
test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
|
||||
opts.req2params({ headers: { host:'localhost' }, query: { non_included: 'toberemoved', api_key: 'test', style: 'override', lzma: data }}, function(err, req) {
|
||||
if ( err ) { done(err); return; }
|
||||
var query = req.params
|
||||
assert.equal(qo.style, query.style)
|
||||
assert.equal(qo.style_version, query.style_version)
|
||||
assert.equal(qo.cache_buster, query.cache_buster)
|
||||
assert.equal('test', query.api_key)
|
||||
assert.equal(undefined, query.non_included)
|
||||
done();
|
||||
var qo = {
|
||||
config: {
|
||||
version: '1.3.0'
|
||||
}
|
||||
};
|
||||
test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) {
|
||||
var req = {
|
||||
headers: {
|
||||
host:'localhost'
|
||||
},
|
||||
query: {
|
||||
non_included: 'toberemoved',
|
||||
api_key: 'test',
|
||||
style: 'override',
|
||||
lzma: data
|
||||
}
|
||||
};
|
||||
opts.req2params(req, function(err, req) {
|
||||
if ( err ) {
|
||||
return done(err);
|
||||
}
|
||||
var query = req.params;
|
||||
assert.deepEqual(qo.config, query.config);
|
||||
assert.equal('test', query.api_key);
|
||||
assert.equal(undefined, query.non_included);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var path = require('path');
|
||||
var request = require('request');
|
||||
|
||||
function usage(me, exitcode) {
|
||||
console.log("Usage: " + me + " [--env <environment>] <username> <tablename>");
|
||||
process.exit(exitcode);
|
||||
}
|
||||
|
||||
var node_path = process.argv.shift();
|
||||
var script_path = process.argv.shift();
|
||||
var basedir = path.dirname(script_path);
|
||||
var me = path.basename(script_path);
|
||||
|
||||
var ENV = 'development.js';
|
||||
var username, table;
|
||||
var arg;
|
||||
while ( arg = process.argv.shift() ) {
|
||||
if ( arg == '--env' ) {
|
||||
ENV = process.argv.shift();
|
||||
}
|
||||
else if ( ! username ) {
|
||||
username = arg;
|
||||
}
|
||||
else if ( ! table ) {
|
||||
table = arg;
|
||||
}
|
||||
else {
|
||||
console.warn("Unused parameter " + arg);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! table ) {
|
||||
usage(me, 1);
|
||||
}
|
||||
|
||||
global.environment = require('../config/environments/' + ENV);
|
||||
|
||||
// _after_ setting global.environment
|
||||
var serverOptions = require('../lib/cartodb/server_options');
|
||||
|
||||
var host = global.environment.host;
|
||||
var port = global.environment.port;
|
||||
var re = ''+serverOptions.re_userFromHost;
|
||||
var hostname = re.replace(/^\/\^/, '')
|
||||
.replace(/\/$$/, '')
|
||||
.replace(/\\/g,'')
|
||||
.replace(/\([^)]*\)/,username)
|
||||
;
|
||||
//console.log("re: " + re);
|
||||
//console.log("hostname: " + hostname);
|
||||
|
||||
var url = 'http://' + host + ':' + port + '/tiles/' + table + '/flush_cache';
|
||||
request.del({ url: url, headers: { host: hostname } },
|
||||
function(err, res, body) {
|
||||
if ( err ) throw err;
|
||||
console.log(res.body);
|
||||
});
|
||||
Reference in New Issue
Block a user