Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebe38e977f | ||
|
|
40ad143c3e | ||
|
|
875159fa5f | ||
|
|
682db1ca75 | ||
|
|
d56bd8de72 | ||
|
|
1df91aee6f | ||
|
|
b64eaed7ed | ||
|
|
03dae8a93a | ||
|
|
6b93fa0575 | ||
|
|
5a9a2d7449 | ||
|
|
8d200686fd | ||
|
|
1c2f84b0cb | ||
|
|
513fa2af01 | ||
|
|
7580081a64 | ||
|
|
1a66f96379 | ||
|
|
fde680450f | ||
|
|
6843692f01 | ||
|
|
1f3a073f21 | ||
|
|
7b4d41464f | ||
|
|
7ae2910061 | ||
|
|
ed3517e733 | ||
|
|
6ac3b4c005 | ||
|
|
26545af9ae | ||
|
|
1ee96f14ce | ||
|
|
2250e6d608 | ||
|
|
5ad27e4bf5 | ||
|
|
5f765712b4 | ||
|
|
cb2e330e0b | ||
|
|
6de911e5bb | ||
|
|
9edec8ef3f | ||
|
|
8e8ab09bec | ||
|
|
c06cba81f4 | ||
|
|
ad5514dd02 | ||
|
|
a5b9ca706c | ||
|
|
5a476f9354 | ||
|
|
403039b695 | ||
|
|
5ee19cc2ed | ||
|
|
8c3f9c7ba0 | ||
|
|
b95a001e0b | ||
|
|
d180305e8b | ||
|
|
ef8fcf7e93 | ||
|
|
e7bd5dd644 | ||
|
|
8503a5c7c9 | ||
|
|
2de0e5d52b | ||
|
|
b9e4b0a90c | ||
|
|
8fb3dc7529 | ||
|
|
a897e36b91 | ||
|
|
446c432484 | ||
|
|
c49f3aaba5 | ||
|
|
fed29b3b50 | ||
|
|
e7d134d70c | ||
|
|
62dbce4311 | ||
|
|
5b5f7fc700 | ||
|
|
026a0750e3 | ||
|
|
7045f41252 | ||
|
|
eaf6775d9d | ||
|
|
ba2a9b81e9 | ||
|
|
5577600903 | ||
|
|
a0a455b225 | ||
|
|
0019ab495b | ||
|
|
cbebac1cb1 | ||
|
|
e2fd4aca60 | ||
|
|
0c578a193c | ||
|
|
84f579f0ec | ||
|
|
1bf2809355 | ||
|
|
e91bc91057 | ||
|
|
4f9b6be45b | ||
|
|
95aa74ee34 | ||
|
|
e516300825 | ||
|
|
2d84d38b90 | ||
|
|
7cd78be094 | ||
|
|
c7a10b048a | ||
|
|
d143b0235b | ||
|
|
a876c82660 | ||
|
|
a6c1aefecc | ||
|
|
9b122280e1 | ||
|
|
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 | ||
|
|
a2a09979e4 | ||
|
|
597cb5286d | ||
|
|
a0243e0bf7 | ||
|
|
59dfd11e5b | ||
|
|
636591ecbb | ||
|
|
a4eade31a2 | ||
|
|
ba0f394a48 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@ logs/
|
||||
pids/
|
||||
redis.pid
|
||||
test.log
|
||||
npm-debug.log
|
||||
coverage/
|
||||
|
||||
4
.jshintignore
Normal file
4
.jshintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
test/results/
|
||||
test/monkey/
|
||||
test/benchmark.js
|
||||
test/support/
|
||||
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
|
||||
}
|
||||
}
|
||||
41
Makefile
41
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,24 +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/unit/cartodb/cache/model/*.js \
|
||||
test/integration/*.js \
|
||||
test/acceptance/*.js \
|
||||
test/acceptance/cache/*.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/ test/ 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
|
||||
|
||||
521
NEWS.md
521
NEWS.md
@@ -1,19 +1,153 @@
|
||||
1.28.2 -- 2015-02-17
|
||||
--------------------
|
||||
# Changelog
|
||||
|
||||
## 2.3.0
|
||||
|
||||
Released 2015-05-18
|
||||
|
||||
Announcements:
|
||||
- Upgrades cartodb-redis for `global` map stats
|
||||
|
||||
|
||||
## 2.2.0
|
||||
|
||||
Released 2015-04-29
|
||||
|
||||
Enhancements:
|
||||
- jshint is run against tests
|
||||
- tests moved to mocha's `describe`
|
||||
|
||||
New features:
|
||||
- Fastly surrogate keys invalidation for named maps
|
||||
* **New configuration entry**: `fastly`. Check example configurations for more information.
|
||||
- `PgQueryRunner` extracted from `QueryTablesApi` so it can be reused in new `TablesExtentApi`
|
||||
- New top level element, `view`, in templates that holds attributes to identify the map scene.
|
||||
- Named maps static preview in /api/v1/map/static/named/:name/:width/:height.:format endpoint
|
||||
* It will be invalidated if the named map changes
|
||||
* But have a Cache-Control header with a 2 hours max-age, won't be invalidated on data changes
|
||||
|
||||
|
||||
## 2.1.3
|
||||
|
||||
Released 2015-04-16
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.42.2](https://github.com/CartoDB/Windshaft/releases/tag/0.42.2)
|
||||
|
||||
|
||||
## 2.1.2
|
||||
|
||||
Released 2015-04-15
|
||||
|
||||
Bug fixes:
|
||||
- Do not check statsd_client in profiler
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.42.1](https://github.com/CartoDB/Windshaft/releases/tag/0.42.1)
|
||||
|
||||
|
||||
## 2.1.1
|
||||
|
||||
Released 2015-04-10
|
||||
|
||||
Bug fixes:
|
||||
- Do not add x-cache-channel header for GET template routes
|
||||
|
||||
|
||||
## 2.1.0
|
||||
|
||||
Released 2015-04-09
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.42.0](https://github.com/CartoDB/Windshaft/releases/tag/0.42.0)
|
||||
|
||||
|
||||
## 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 -- 2015-02-17
|
||||
--------------------
|
||||
## 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 -- 2015-02-17
|
||||
--------------------
|
||||
## 1.28.0
|
||||
|
||||
Released 2015-02-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [0.37.0](https://github.com/CartoDB/Windshaft/releases/tag/0.37.0)
|
||||
@@ -23,8 +157,9 @@ New features:
|
||||
tables last update (#253)
|
||||
|
||||
|
||||
1.27.0 -- 2015-02-16
|
||||
--------------------
|
||||
## 1.27.0
|
||||
|
||||
Released 2015-02-16
|
||||
|
||||
Announcements:
|
||||
- Adds default image placeholder for http renderer to use as fallback
|
||||
@@ -37,29 +172,33 @@ Bugfixes:
|
||||
- Fixes tests with beforeEach and afterEach triggers
|
||||
|
||||
|
||||
1.26.2 -- 2015-01-28
|
||||
--------------------
|
||||
## 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)
|
||||
@@ -95,22 +234,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
|
||||
@@ -119,22 +261,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
|
||||
@@ -144,29 +289,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
|
||||
@@ -176,8 +325,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
|
||||
@@ -186,8 +336,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
|
||||
@@ -196,29 +347,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
|
||||
@@ -228,8 +383,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
|
||||
@@ -240,14 +396,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
|
||||
@@ -263,8 +421,9 @@ Enhancements:
|
||||
- windshaft
|
||||
- request
|
||||
|
||||
1.15.0 -- 2014-08-13
|
||||
--------------------
|
||||
## 1.15.0
|
||||
|
||||
Released 2014-08-13
|
||||
Enhancements:
|
||||
- Upgrades dependencies:
|
||||
- redis-mpool
|
||||
@@ -274,8 +433,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
|
||||
@@ -287,43 +447,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:
|
||||
|
||||
@@ -334,23 +500,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:
|
||||
|
||||
@@ -371,15 +540,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:
|
||||
|
||||
@@ -397,8 +568,9 @@ Bug fixes:
|
||||
|
||||
- Do not cache map creation responses (#176)
|
||||
|
||||
1.8.4 -- 2014-03-03
|
||||
-------------------
|
||||
## 1.8.4
|
||||
|
||||
Released 2014-03-03
|
||||
|
||||
Enhancements:
|
||||
|
||||
@@ -415,8 +587,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:
|
||||
|
||||
@@ -429,8 +602,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:
|
||||
|
||||
@@ -444,8 +618,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:
|
||||
|
||||
@@ -455,8 +630,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:
|
||||
|
||||
@@ -471,16 +647,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:
|
||||
|
||||
@@ -501,8 +679,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:
|
||||
|
||||
@@ -517,8 +696,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:
|
||||
|
||||
@@ -530,16 +710,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:
|
||||
|
||||
@@ -552,24 +734,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
|
||||
@@ -593,24 +778,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
|
||||
@@ -619,8 +808,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)
|
||||
@@ -629,38 +818,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
|
||||
@@ -668,14 +857,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)
|
||||
@@ -684,18 +873,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
|
||||
@@ -704,20 +897,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
|
||||
@@ -725,16 +924,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
|
||||
@@ -743,8 +946,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
|
||||
@@ -761,12 +966,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/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|/user/:user/api/v1/map/named|/tiles/template)'
|
||||
// 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|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -86,13 +84,54 @@ 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
|
||||
'.*', // will enable any URL
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
@@ -133,26 +172,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
|
||||
@@ -162,6 +181,15 @@ var config = {
|
||||
ttl: 86400,
|
||||
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
|
||||
}
|
||||
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
|
||||
,fastly: {
|
||||
// whether the invalidation is enabled or not
|
||||
enabled: false,
|
||||
// the fastly api key
|
||||
apiKey: 'wadus_api_key',
|
||||
// the service that will get surrogate key invalidation
|
||||
serviceId: 'wadus_service_id'
|
||||
}
|
||||
// If useProfiler is true every response will be served with an
|
||||
// X-Tiler-Profile header containing elapsed timing for various
|
||||
// steps taken for producing the response.
|
||||
@@ -174,6 +202,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|/user/:user/api/v1/map/named|/tiles/template)'
|
||||
// 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|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -80,13 +78,54 @@ 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
|
||||
'.*', // will enable any URL
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
@@ -127,26 +166,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
|
||||
@@ -156,6 +175,15 @@ var config = {
|
||||
ttl: 86400,
|
||||
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
|
||||
}
|
||||
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
|
||||
,fastly: {
|
||||
// whether the invalidation is enabled or not
|
||||
enabled: false,
|
||||
// the fastly api key
|
||||
apiKey: 'wadus_api_key',
|
||||
// the service that will get surrogate key invalidation
|
||||
serviceId: 'wadus_service_id'
|
||||
}
|
||||
// If useProfiler is true every response will be served with an
|
||||
// X-Tiler-Profile header containing elapsed timing for various
|
||||
// steps taken for producing the response.
|
||||
@@ -166,15 +194,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,
|
||||
@@ -183,6 +202,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|/user/:user/api/v1/map/named|/tiles/template)'
|
||||
// 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|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -80,13 +78,54 @@ 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
|
||||
'.*', // will enable any URL
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||
],
|
||||
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||
@@ -127,26 +166,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
|
||||
@@ -156,6 +175,15 @@ var config = {
|
||||
ttl: 86400,
|
||||
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
|
||||
}
|
||||
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
|
||||
,fastly: {
|
||||
// whether the invalidation is enabled or not
|
||||
enabled: false,
|
||||
// the fastly api key
|
||||
apiKey: 'wadus_api_key',
|
||||
// the service that will get surrogate key invalidation
|
||||
serviceId: 'wadus_service_id'
|
||||
}
|
||||
// If useProfiler is true every response will be served with an
|
||||
// X-Tiler-Profile header containing elapsed timing for various
|
||||
// steps taken for producing the response.
|
||||
@@ -166,15 +194,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,
|
||||
@@ -183,6 +202,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|/user/:user/api/v1/map/named|/tiles/template)'
|
||||
// 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|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
@@ -80,13 +78,54 @@ 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
|
||||
'.*', // will enable any URL
|
||||
'http://{s}.example.com/{z}/{x}/{y}.png',
|
||||
// for testing purposes
|
||||
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
|
||||
@@ -129,28 +168,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
|
||||
@@ -160,6 +177,15 @@ var config = {
|
||||
ttl: 86400,
|
||||
layergroupTtl: 86400 // the max-age for cache-control header in layergroup responses
|
||||
}
|
||||
// this [OPTIONAL] configuration enables invalidating by surrogate key in fastly
|
||||
,fastly: {
|
||||
// whether the invalidation is enabled or not
|
||||
enabled: false,
|
||||
// the fastly api key
|
||||
apiKey: 'wadus_api_key',
|
||||
// the service that will get surrogate key invalidation
|
||||
serviceId: 'wadus_service_id'
|
||||
}
|
||||
// If useProfiler is true every response will be served with an
|
||||
// X-Tiler-Profile header containing elapsed timing for various
|
||||
// steps taken for producing the response.
|
||||
@@ -172,6 +198,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;
|
||||
|
||||
@@ -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,7 +79,7 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp
|
||||
|
||||
<div class="code-title notitle code-request"></div>
|
||||
```bash
|
||||
curl -X POST 'http://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
|
||||
curl -X POST 'https://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
The response will return JSON with properties for the `layergroupid` and the timestamp (`last_updated`) of the last data modification.
|
||||
@@ -96,7 +96,7 @@ Here is an example response:
|
||||
You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template:
|
||||
|
||||
```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
|
||||
@@ -107,7 +107,7 @@ The following concepts are the same for every endpoint in the API except when it
|
||||
|
||||
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map).
|
||||
|
||||
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
|
||||
|
||||
@@ -167,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**
|
||||
@@ -183,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>
|
||||
@@ -201,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:
|
||||
@@ -789,7 +789,8 @@ It is important to note that generated images are cached from the live data refe
|
||||
####Limits
|
||||
|
||||
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
|
||||
* Resolution limitations for jpegs are at default a quality of 85. Limitations for quality are restricted to a maximum of XXX dpi.
|
||||
* 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.
|
||||
|
||||
|
||||
@@ -811,7 +812,7 @@ For this map, the multiple layers, order, and stylings are defined by the MapCon
|
||||
|
||||
```javascript
|
||||
{
|
||||
"version": "1.3.0-alpha",
|
||||
"version": "1.3.0",
|
||||
"layers": [
|
||||
{
|
||||
"type": "http",
|
||||
|
||||
101
docs/Routes.md
Normal file
101
docs/Routes.md
Normal file
@@ -0,0 +1,101 @@
|
||||
This document list all routes available in Windshaft-cartodb Maps API server.
|
||||
|
||||
## Routes list
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y@:scale_factor?x.:format {:user(f),: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|/user/:user/api/v1/map|/tiles/layergroup)/:token/:z/:x/:y.:format {:user(f),:token(f),:z(f),:x(f),:y(f),:format(f)} (1)`
|
||||
<br/>Notes: Mapnik tiles [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/:z/:x/:y.(:format) {:user(f),: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|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/attributes/:fid {:user(f),: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|/user/:user/api/v1/map|/tiles/layergroup)/static/center/:token/:z/:lat/:lng/:width/:height.:format {:user(f),: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|/user/:user/api/v1/map|/tiles/layergroup)/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format {:user(f),: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 (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/jsonp {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named maps JSONP instantiation [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Named map retrieval (w/ API KEY) [1]
|
||||
|
||||
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: List named maps (w/ API KEY) [1]
|
||||
|
||||
1. `GET /health {} (1)`
|
||||
<br/>Notes: Healt check
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: CORS [0]
|
||||
|
||||
1. `OPTIONS (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: CORS [1]
|
||||
|
||||
1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
|
||||
<br/>Notes: Map instantiation [0]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template) {:user(f)} (1)`
|
||||
<br/>Notes: Create named map (w/ API KEY) [1]
|
||||
|
||||
1. `POST (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Instantiate named map [1]
|
||||
|
||||
1. `PUT (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Update a named map (w/ API KEY) [1]
|
||||
|
||||
1. `DELETE (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id {:user(f),:template_id(f)} (1)`
|
||||
<br/>Notes: Delete 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,10 +1,5 @@
|
||||
var sqlApi = require('../sql/sql_api');
|
||||
var PSQL = require('cartodb-psql');
|
||||
var Step = require('step');
|
||||
|
||||
function QueryTablesApi(pgConnection, metadataBackend) {
|
||||
this.pgConnection = pgConnection;
|
||||
this.metadataBackend = metadataBackend;
|
||||
function QueryTablesApi(pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
var affectedTableRegexCache = {
|
||||
@@ -21,7 +16,7 @@ QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, sql, cal
|
||||
|
||||
var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)';
|
||||
|
||||
this.runQuery(username, query, handleAffectedTablesInQueryRows, callback);
|
||||
this.pgQueryRunner.run(username, query, handleAffectedTablesInQueryRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesInQueryRows(err, rows, callback) {
|
||||
@@ -47,7 +42,7 @@ QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (usernam
|
||||
'WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'
|
||||
].join(' ');
|
||||
|
||||
this.runQuery(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
this.pgQueryRunner.run(username, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
|
||||
};
|
||||
|
||||
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
@@ -70,63 +65,6 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) {
|
||||
var self = this;
|
||||
|
||||
if (shouldQueryPostgresDirectly()) {
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
Step(
|
||||
function getApiKey() {
|
||||
self.metadataBackend.getUserMapKey(username, this);
|
||||
},
|
||||
function executeQuery(err, apiKey) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
sqlApi.query(username, apiKey, query, function(err, rows) {
|
||||
queryHandler(err, rows, callback);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function prepareSql(sql) {
|
||||
return sql
|
||||
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
|
||||
@@ -135,10 +73,3 @@ function prepareSql(sql) {
|
||||
.replace(affectedTableRegexCache.pixel_height, '1')
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
function shouldQueryPostgresDirectly() {
|
||||
return global.environment
|
||||
&& global.environment.enabledFeatures
|
||||
&& global.environment.enabledFeatures.cdbQueryTablesFromPostgres;
|
||||
}
|
||||
|
||||
54
lib/cartodb/api/tables_extent_api.js
Normal file
54
lib/cartodb/api/tables_extent_api.js
Normal file
@@ -0,0 +1,54 @@
|
||||
function TablesExtentApi(pgQueryRunner) {
|
||||
this.pgQueryRunner = pgQueryRunner;
|
||||
}
|
||||
|
||||
module.exports = TablesExtentApi;
|
||||
|
||||
/**
|
||||
* Given a username and a list of tables it will return the estimated extent in SRID 4326 for all the tables based on
|
||||
* the_geom_webmercator (SRID 3857) column.
|
||||
*
|
||||
* @param {String} username
|
||||
* @param {Array} tableNames The named can be schema qualified, so this accepts both `schema_name.table_name` and
|
||||
* `table_name` format as valid input
|
||||
* @param {Function} callback function(err, result) {Object} result with `west`, `south`, `east`, `north`
|
||||
*/
|
||||
TablesExtentApi.prototype.getBounds = function (username, tableNames, callback) {
|
||||
var estimatedExtentSQLs = tableNames.map(function(tableName) {
|
||||
var schemaTable = tableName.split('.');
|
||||
if (schemaTable.length > 1) {
|
||||
return "ST_EstimatedExtent('" + schemaTable[0] + "', '" + schemaTable[1] + "', 'the_geom_webmercator')";
|
||||
}
|
||||
return "ST_EstimatedExtent('" + schemaTable[0] + "', 'the_geom_webmercator')";
|
||||
});
|
||||
|
||||
var query = [
|
||||
"WITH ext as (" +
|
||||
"SELECT ST_Transform(ST_SetSRID(ST_Extent(ST_Union(ARRAY[",
|
||||
estimatedExtentSQLs.join(','),
|
||||
"])), 3857), 4326) geom)",
|
||||
"SELECT",
|
||||
"ST_XMin(geom) west,",
|
||||
"ST_YMin(geom) south,",
|
||||
"ST_XMax(geom) east,",
|
||||
"ST_YMax(geom) north",
|
||||
"FROM ext"
|
||||
].join(' ');
|
||||
|
||||
this.pgQueryRunner.run(username, query, handleBoundsResult, callback);
|
||||
};
|
||||
|
||||
function handleBoundsResult(err, rows, callback) {
|
||||
if (err){
|
||||
var msg = err.message ? err.message : err;
|
||||
callback(new Error('could not fetch source tables: ' + msg));
|
||||
return;
|
||||
}
|
||||
var result = null;
|
||||
if (rows.length > 0) {
|
||||
result = {
|
||||
bounds: rows[0]
|
||||
};
|
||||
}
|
||||
callback(null, result);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
var Step = require('step');
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
function PgConnection(metadataBackend) {
|
||||
@@ -24,13 +24,13 @@ PgConnection.prototype.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(
|
||||
step(
|
||||
function getId() {
|
||||
self.metadataBackend.getUserId(username, this);
|
||||
},
|
||||
function(err, user_id) {
|
||||
if (err) throw err;
|
||||
user_params['user_id'] = user_id;
|
||||
user_params.user_id = user_id;
|
||||
var dbuser = _.template(auth_user, user_params);
|
||||
_.extend(params, {dbuser:dbuser});
|
||||
|
||||
@@ -42,7 +42,7 @@ PgConnection.prototype.setDBAuth = function(username, params, callback) {
|
||||
},
|
||||
function(err, user_password) {
|
||||
if (err) throw err;
|
||||
user_params['user_password'] = user_password;
|
||||
user_params.user_password = user_password;
|
||||
if ( auth_pass ) {
|
||||
var dbpass = _.template(auth_pass, user_params);
|
||||
_.extend(params, {dbpassword:dbpass});
|
||||
@@ -76,7 +76,7 @@ PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
|
||||
dbhost: global.environment.postgres.host,
|
||||
dbport: global.environment.postgres.port
|
||||
});
|
||||
Step(
|
||||
step(
|
||||
function getConnectionParams() {
|
||||
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
|
||||
},
|
||||
|
||||
44
lib/cartodb/backends/pg_query_runner.js
Normal file
44
lib/cartodb/backends/pg_query_runner.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var PSQL = require('cartodb-psql');
|
||||
var step = require('step');
|
||||
|
||||
function PgQueryRunner(pgConnection) {
|
||||
this.pgConnection = pgConnection;
|
||||
}
|
||||
|
||||
module.exports = PgQueryRunner;
|
||||
|
||||
|
||||
PgQueryRunner.prototype.run = 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);
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
18
lib/cartodb/cache/backend/fastly.js
vendored
Normal file
18
lib/cartodb/cache/backend/fastly.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
var FastlyPurge = require('fastly-purge');
|
||||
|
||||
function FastlyCacheBackend(apiKey, serviceId, softPurge) {
|
||||
this.serviceId = serviceId;
|
||||
this.fastlyPurge = new FastlyPurge(apiKey, { softPurge: softPurge || true });
|
||||
}
|
||||
|
||||
module.exports = FastlyCacheBackend;
|
||||
|
||||
/**
|
||||
* @param cacheObject should respond to `key() -> String` method
|
||||
* @param {Function} callback
|
||||
*/
|
||||
FastlyCacheBackend.prototype.invalidate = function(cacheObject, callback) {
|
||||
this.fastlyPurge.key(this.serviceId, cacheObject.key(), callback);
|
||||
};
|
||||
|
||||
module.exports = FastlyCacheBackend;
|
||||
23
lib/cartodb/cache/surrogate_keys_cache.js
vendored
23
lib/cartodb/cache/surrogate_keys_cache.js
vendored
@@ -1,9 +1,11 @@
|
||||
var queue = require('queue-async');
|
||||
|
||||
/**
|
||||
* @param cacheBackend should respond to `invalidate(cacheObject, callback)` method
|
||||
* @param {Array|Object} cacheBackends each backend backend should respond to `invalidate(cacheObject, callback)` method
|
||||
* @constructor
|
||||
*/
|
||||
function SurrogateKeysCache(cacheBackend) {
|
||||
this.cacheBackend = cacheBackend;
|
||||
function SurrogateKeysCache(cacheBackends) {
|
||||
this.cacheBackends = Array.isArray(cacheBackends) ? cacheBackends : [cacheBackends];
|
||||
}
|
||||
|
||||
module.exports = SurrogateKeysCache;
|
||||
@@ -22,5 +24,18 @@ SurrogateKeysCache.prototype.tag = function(response, cacheObject) {
|
||||
* @param {Function} callback
|
||||
*/
|
||||
SurrogateKeysCache.prototype.invalidate = function(cacheObject, callback) {
|
||||
this.cacheBackend.invalidate(cacheObject, callback);
|
||||
var invalidationQueue = queue(this.cacheBackends.length);
|
||||
|
||||
this.cacheBackends.forEach(function(cacheBackend) {
|
||||
invalidationQueue.defer(function(cacheBackend, done) {
|
||||
cacheBackend.invalidate(cacheObject, done);
|
||||
}, cacheBackend);
|
||||
});
|
||||
|
||||
invalidationQueue.awaitAll(function(err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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,12 +1,16 @@
|
||||
var _ = require('underscore');
|
||||
var Step = require('step');
|
||||
var step = require('step');
|
||||
var Windshaft = require('windshaft');
|
||||
var Cache = require('./cache_validator');
|
||||
var os = require('os');
|
||||
var HealthCheck = require('./monitoring/health_check');
|
||||
|
||||
if ( ! process.env['PGAPPNAME'] )
|
||||
process.env['PGAPPNAME']='cartodb_tiler';
|
||||
var SurrogateKeysCache = require('./cache/surrogate_keys_cache');
|
||||
var NamedMapsCacheEntry = require('./cache/model/named_maps_entry');
|
||||
var VarnishHttpCacheBackend = require('./cache/backend/varnish_http');
|
||||
var FastlyCacheBackend = require('./cache/backend/fastly');
|
||||
|
||||
if ( ! process.env.PGAPPNAME )
|
||||
process.env.PGAPPNAME='cartodb_tiler';
|
||||
|
||||
var CartodbWindshaft = function(serverOptions) {
|
||||
// Perform keyword substitution in statsd
|
||||
@@ -18,55 +22,54 @@ 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});
|
||||
|
||||
var templateMaps = serverOptions.templateMaps;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// This is for Templated maps
|
||||
//
|
||||
// "named" is the official, "template" is for backward compatibility up to 1.6.x
|
||||
//
|
||||
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
|
||||
|
||||
var 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),
|
||||
surrogateKeysCache = new SurrogateKeysCache(varnishHttpCacheBackend);
|
||||
var surrogateKeysCacheBackends = [];
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
surrogateKeysCacheBackends.push(
|
||||
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
|
||||
);
|
||||
}
|
||||
|
||||
['update', 'delete'].forEach(function(eventType) {
|
||||
templateMaps.on(eventType, invalidateNamedMap);
|
||||
if (!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
|
||||
surrogateKeysCacheBackends.push(
|
||||
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
|
||||
);
|
||||
}
|
||||
|
||||
var surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends);
|
||||
|
||||
function invalidateNamedMap (owner, templateName) {
|
||||
var startTime = Date.now();
|
||||
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
|
||||
var logMessage = JSON.stringify({
|
||||
username: owner,
|
||||
type: 'named_map_invalidation',
|
||||
elapsed: Date.now() - startTime,
|
||||
error: !!err ? JSON.stringify(err.message) : undefined
|
||||
});
|
||||
if (err) {
|
||||
console.warn(logMessage);
|
||||
} else {
|
||||
console.info(logMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
['update', 'delete'].forEach(function(eventType) {
|
||||
templateMaps.on(eventType, invalidateNamedMap);
|
||||
});
|
||||
|
||||
// boot
|
||||
var ws = new Windshaft.Server(serverOptions);
|
||||
@@ -84,8 +87,12 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
// POST/PUT/DELETE requests are never cached anyway.
|
||||
var noCacheGETRoutes = [
|
||||
'/',
|
||||
'/version',
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
|
||||
serverOptions.base_url_mapconfig,
|
||||
serverOptions.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
|
||||
template_baseurl,
|
||||
template_baseurl + '/:template_id',
|
||||
template_baseurl + '/:template_id/jsonp'
|
||||
];
|
||||
ws.sendResponse = function(res, args) {
|
||||
@@ -102,7 +109,7 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
}
|
||||
}
|
||||
var req = res.req;
|
||||
Step (
|
||||
step (
|
||||
function addCacheChannel() {
|
||||
if ( ! req ) {
|
||||
// having no associated request can happen when
|
||||
@@ -124,10 +131,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;
|
||||
@@ -150,73 +158,35 @@ var CartodbWindshaft = function(serverOptions) {
|
||||
* Routing
|
||||
******************************************************************************************************************/
|
||||
|
||||
var TemplateMapsController = require('./controllers/template_maps'),
|
||||
templateMapsController = new TemplateMapsController(
|
||||
var NamedMapsController = require('./controllers/named_maps'),
|
||||
namedMapsController = new NamedMapsController(
|
||||
ws,
|
||||
serverOptions,
|
||||
templateMaps,
|
||||
cartoData,
|
||||
template_baseurl,
|
||||
surrogateKeysCache,
|
||||
NamedMapsCacheEntry,
|
||||
serverOptions.pgConnection
|
||||
surrogateKeysCache
|
||||
);
|
||||
templateMapsController.register(ws);
|
||||
namedMapsController.register(ws);
|
||||
|
||||
var TablesExtentApi = require('./api/tables_extent_api');
|
||||
var tablesExtentApi = new TablesExtentApi(serverOptions.pgQueryRunner);
|
||||
|
||||
var NamedStaticMapsController = require('./controllers/named_static_maps');
|
||||
var namedStaticMapsController = new NamedStaticMapsController(
|
||||
ws,
|
||||
serverOptions,
|
||||
templateMaps,
|
||||
ws.staticMapBackend,
|
||||
surrogateKeysCache,
|
||||
tablesExtentApi
|
||||
);
|
||||
namedStaticMapsController.register(ws);
|
||||
|
||||
/*******************************************************************************************************************
|
||||
* 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.
|
||||
* Keep endpoint for backwards compatibility
|
||||
*/
|
||||
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
|
||||
ws.doCORS(res);
|
||||
ws.sendResponse(res, [{status: 'ok'}, 200]);
|
||||
});
|
||||
|
||||
var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive);
|
||||
ws.get('/health', function(req, res) {
|
||||
var healthConfig = global.environment.health || {};
|
||||
|
||||
372
lib/cartodb/controllers/named_maps.js
Normal file
372
lib/cartodb/controllers/named_maps.js
Normal file
@@ -0,0 +1,372 @@
|
||||
var step = require('step');
|
||||
var assert = require('assert');
|
||||
var _ = require('underscore');
|
||||
var templateName = require('../template_maps').templateName;
|
||||
var CdbRequest = require('../models/cdb_request');
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
|
||||
function NamedMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache) {
|
||||
this.app = app;
|
||||
this.serverOptions = serverOptions;
|
||||
this.templateMaps = templateMaps;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
}
|
||||
|
||||
module.exports = NamedMapsController;
|
||||
|
||||
var cdbRequest = new CdbRequest();
|
||||
|
||||
NamedMapsController.prototype.register = function(app) {
|
||||
app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this));
|
||||
app.post(this.templateBaseUrl, this.create.bind(this));
|
||||
app.put(this.templateBaseUrl + '/:template_id', this.update.bind(this));
|
||||
app.get(this.templateBaseUrl + '/:template_id', this.retrieve.bind(this));
|
||||
app.del(this.templateBaseUrl + '/:template_id', this.destroy.bind(this));
|
||||
app.get(this.templateBaseUrl, this.list.bind(this));
|
||||
app.options(this.templateBaseUrl + '/:template_id', this.options.bind(this));
|
||||
app.post(this.templateBaseUrl + '/:template_id', this.instantiate.bind(this));
|
||||
};
|
||||
|
||||
// Add a template
|
||||
NamedMapsController.prototype.create = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function addTemplate(err, authenticated) {
|
||||
assert.ifError(err);
|
||||
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
|
||||
ifInvalidContentType(req, 'template POST data must be of type application/json');
|
||||
var cfg = req.body;
|
||||
self.templateMaps.addTemplate(cdbuser, cfg, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_id){
|
||||
assert.ifError(err);
|
||||
return { template_id: tpl_id };
|
||||
},
|
||||
finishFn(self.app, res, 'POST TEMPLATE')
|
||||
);
|
||||
};
|
||||
|
||||
// Update a template
|
||||
NamedMapsController.prototype.update = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
assert.ifError(err);
|
||||
ifUnauthenticated(authenticated, 'Only authenticated user can update templated maps');
|
||||
ifInvalidContentType(req, 'template PUT data must be of type application/json');
|
||||
|
||||
template = req.body;
|
||||
tpl_id = templateName(req.params.template_id);
|
||||
self.templateMaps.updTemplate(cdbuser, tpl_id, template, this);
|
||||
},
|
||||
function prepareResponse(err){
|
||||
assert.ifError(err);
|
||||
|
||||
return { template_id: tpl_id };
|
||||
},
|
||||
finishFn(self.app, res, 'PUT TEMPLATE')
|
||||
);
|
||||
};
|
||||
|
||||
// Get a specific template
|
||||
NamedMapsController.prototype.retrieve = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.get_template');
|
||||
}
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var tpl_id;
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
assert.ifError(err);
|
||||
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
|
||||
|
||||
tpl_id = templateName(req.params.template_id);
|
||||
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val) {
|
||||
if ( err ) throw err;
|
||||
if ( ! tpl_val ) {
|
||||
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
// auth_id was added by ourselves,
|
||||
// so we remove it before returning to the user
|
||||
delete tpl_val.auth_id;
|
||||
return { template: tpl_val };
|
||||
},
|
||||
finishFn(self.app, res, 'GET TEMPLATE')
|
||||
);
|
||||
};
|
||||
|
||||
// Delete a specific template
|
||||
NamedMapsController.prototype.destroy = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.delete_template');
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
var tpl_id;
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
assert.ifError(err);
|
||||
ifUnauthenticated(authenticated, 'Only authenticated users can delete template maps');
|
||||
|
||||
tpl_id = templateName(req.params.template_id);
|
||||
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err/*, tpl_val*/){
|
||||
if ( err ) throw err;
|
||||
return { status: 'ok' };
|
||||
},
|
||||
finishFn(self.app, res, 'DELETE TEMPLATE', ['', 204])
|
||||
);
|
||||
};
|
||||
|
||||
// Get a list of owned templates
|
||||
NamedMapsController.prototype.list = function(req, res) {
|
||||
var self = this;
|
||||
if ( req.profiler ) {
|
||||
req.profiler.start('windshaft-cartodb.get_template_list');
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
|
||||
step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function listTemplates(err, authenticated) {
|
||||
assert.ifError(err);
|
||||
ifUnauthenticated(authenticated, 'Only authenticated user can list templated maps');
|
||||
|
||||
self.templateMaps.listTemplates(cdbuser, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_ids){
|
||||
assert.ifError(err);
|
||||
return { template_ids: tpl_ids };
|
||||
},
|
||||
finishFn(self.app, res, 'GET TEMPLATE LIST')
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.instantiate = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
}
|
||||
step(
|
||||
function() {
|
||||
ifInvalidContentType(req, 'template POST data must be of type application/json');
|
||||
|
||||
self.instantiateTemplate(req, res, req.body, this);
|
||||
}, function(err, response) {
|
||||
self.finish_instantiation(err, response, res);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.options = function(req, res, next) {
|
||||
this.app.doCORS(res, "Content-Type");
|
||||
return next();
|
||||
};
|
||||
|
||||
/**
|
||||
* jsonp endpoint, allows to instantiate a template with a json call.
|
||||
* callback query argument is mandatory
|
||||
*/
|
||||
NamedMapsController.prototype.jsonp = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
}
|
||||
step(
|
||||
function() {
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
throw new Error('callback parameter should be present and be a function name');
|
||||
}
|
||||
var config = {};
|
||||
if(req.query.config) {
|
||||
try {
|
||||
config = JSON.parse(req.query.config);
|
||||
} catch(e) {
|
||||
throw new Error('badformed config parameter, should be a valid JSON');
|
||||
}
|
||||
}
|
||||
self.instantiateTemplate(req, res, config, this);
|
||||
}, function(err, response) {
|
||||
self.finish_instantiation(err, response, res);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Instantiate a template
|
||||
NamedMapsController.prototype.instantiateTemplate = function(req, res, template_params, callback) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var template;
|
||||
var layergroup;
|
||||
var fakereq; // used for call to createLayergroup
|
||||
var cdbuser = cdbRequest.userByReq(req);
|
||||
// Format of template_id: [<template_owner>]@<template_id>
|
||||
var tpl_id = templateName(req.params.template_id);
|
||||
var auth_token = req.query.auth_token;
|
||||
step(
|
||||
function getTemplate(){
|
||||
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function checkAuthorized(err, templateValue) {
|
||||
if ( req.profiler ) req.profiler.done('getTemplate');
|
||||
if ( err ) throw err;
|
||||
if ( ! templateValue ) {
|
||||
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
template = templateValue;
|
||||
|
||||
var authorized = false;
|
||||
try {
|
||||
authorized = self.templateMaps.isAuthorized(template, auth_token);
|
||||
} catch (err) {
|
||||
// we catch to add http_status
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
if ( ! authorized ) {
|
||||
err = new Error('Unauthorized template instanciation');
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.done('authorizedByCert');
|
||||
}
|
||||
|
||||
return self.templateMaps.instance(template, template_params);
|
||||
},
|
||||
function prepareParams(err, instance){
|
||||
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
|
||||
if ( err ) throw err;
|
||||
layergroup = instance;
|
||||
fakereq = {
|
||||
query: {},
|
||||
params: {
|
||||
user: req.params.user
|
||||
},
|
||||
headers: _.clone(req.headers),
|
||||
context: _.clone(req.context),
|
||||
method: req.method,
|
||||
res: res,
|
||||
profiler: req.profiler
|
||||
};
|
||||
self.serverOptions.setDBParams(cdbuser, fakereq.params, this);
|
||||
},
|
||||
function setApiKey(err){
|
||||
if ( req.profiler ) req.profiler.done('setDBParams');
|
||||
if ( err ) throw err;
|
||||
self.metadataBackend.getUserMapKey(cdbuser, this);
|
||||
},
|
||||
function createLayergroup(err, val) {
|
||||
if ( req.profiler ) req.profiler.done('getUserMapKey');
|
||||
if ( err ) throw err;
|
||||
fakereq.params.api_key = val;
|
||||
self.app.createLayergroup(layergroup, fakereq, this);
|
||||
},
|
||||
function prepareResponse(err, layergroup) {
|
||||
if ( err ) {
|
||||
throw err;
|
||||
}
|
||||
var tplhash = self.templateMaps.fingerPrint(template).substring(0,8);
|
||||
layergroup.layergroupid = cdbuser + '@' + tplhash + '@' + layergroup.layergroupid;
|
||||
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, template.name));
|
||||
|
||||
return layergroup;
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.finish_instantiation = function(err, response, res) {
|
||||
if (err) {
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
this.app.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err);
|
||||
} else {
|
||||
this.app.sendResponse(res, [response, 200]);
|
||||
}
|
||||
};
|
||||
|
||||
function finishFn(app, res, description, okResponse) {
|
||||
return function finish(err, response){
|
||||
var statusCode = 200;
|
||||
if (err) {
|
||||
statusCode = 400;
|
||||
response = { error: '' + err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
app.sendError(res, response, statusCode, description, err);
|
||||
} else {
|
||||
app.sendResponse(res, okResponse || [response, statusCode]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ifUnauthenticated(authenticated, description) {
|
||||
if (authenticated !== 1) {
|
||||
var err = new Error(description);
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function ifInvalidContentType(req, description) {
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' ) {
|
||||
throw new Error(description);
|
||||
}
|
||||
}
|
||||
248
lib/cartodb/controllers/named_static_maps.js
Normal file
248
lib/cartodb/controllers/named_static_maps.js
Normal file
@@ -0,0 +1,248 @@
|
||||
var step = require('step');
|
||||
var assert = require('assert');
|
||||
var templateName = require('../template_maps').templateName;
|
||||
var CdbRequest = require('../models/cdb_request');
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
var _ = require('underscore');
|
||||
|
||||
function NamedStaticMapsController(app, serverOptions, templateMaps, staticMapBackend, surrogateKeysCache,
|
||||
tablesExtentApi) {
|
||||
this.app = app;
|
||||
this.serverOptions = serverOptions;
|
||||
this.templateMaps = templateMaps;
|
||||
this.staticMapBackend = staticMapBackend;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.tablesExtentApi = tablesExtentApi;
|
||||
}
|
||||
|
||||
module.exports = NamedStaticMapsController;
|
||||
|
||||
var cdbRequest = new CdbRequest();
|
||||
|
||||
NamedStaticMapsController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', this.named.bind(this));
|
||||
};
|
||||
|
||||
NamedStaticMapsController.prototype.named = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
var cdbUser = cdbRequest.userByReq(req);
|
||||
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
|
||||
var template;
|
||||
var layergroupConfig;
|
||||
var layergroupId;
|
||||
var fakeReq;
|
||||
var cacheChannel;
|
||||
|
||||
step(
|
||||
function reqParams() {
|
||||
self.app.req2params(req, this);
|
||||
},
|
||||
function getTemplate(err) {
|
||||
assert.ifError(err);
|
||||
self.templateMaps.getTemplate(cdbUser, templateName(req.params.template_id), this);
|
||||
},
|
||||
function checkExists(err, tpl) {
|
||||
assert.ifError(err);
|
||||
if (!tpl) {
|
||||
var notFoundErr = new Error(
|
||||
"Template '" + templateName(req.params.template_id) + "' of user '" + cdbUser + "' not found"
|
||||
);
|
||||
notFoundErr.http_status = 404;
|
||||
throw notFoundErr;
|
||||
}
|
||||
return tpl;
|
||||
},
|
||||
function checkAuthorized(err, tpl) {
|
||||
assert.ifError(err);
|
||||
|
||||
var authorized = false;
|
||||
try {
|
||||
authorized = self.templateMaps.isAuthorized(tpl, req.query.auth_token);
|
||||
} catch (err) {
|
||||
// we catch to add http_status
|
||||
var authorizationFailedErr = new Error('Failed to authorize template');
|
||||
authorizationFailedErr.http_status = 403;
|
||||
throw authorizationFailedErr;
|
||||
}
|
||||
if ( ! authorized ) {
|
||||
var unauthorizedErr = new Error('Unauthorized template instantiation');
|
||||
unauthorizedErr.http_status = 403;
|
||||
throw unauthorizedErr;
|
||||
}
|
||||
|
||||
return tpl;
|
||||
},
|
||||
function prepareParams(err, tpl) {
|
||||
assert.ifError(err);
|
||||
|
||||
template = tpl;
|
||||
|
||||
var templateParams = {};
|
||||
if (req.query.config) {
|
||||
try {
|
||||
templateParams = JSON.parse(req.query.config);
|
||||
} catch (e) {
|
||||
throw new Error('malformed config parameter, should be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
return templateParams;
|
||||
},
|
||||
function instantiateTemplate(err, templateParams) {
|
||||
assert.ifError(err);
|
||||
return self.templateMaps.instance(template, templateParams);
|
||||
},
|
||||
function prepareLayergroup(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
layergroupConfig = layergroup;
|
||||
fakeReq = {
|
||||
query: {},
|
||||
params: {
|
||||
user: req.params.user
|
||||
},
|
||||
headers: _.clone(req.headers),
|
||||
context: _.clone(req.context),
|
||||
method: req.method,
|
||||
res: res,
|
||||
profiler: req.profiler
|
||||
};
|
||||
self.serverOptions.setDBParams(cdbUser, fakeReq.params, this);
|
||||
},
|
||||
function setApiKey(err){
|
||||
assert.ifError(err);
|
||||
self.app.createLayergroup(layergroupConfig, fakeReq, this);
|
||||
},
|
||||
function prepareResponse(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
|
||||
// added by createLayergroup
|
||||
cacheChannel = res.header('X-Cache-Channel');
|
||||
res.removeHeader('X-Cache-Channel');
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbUser, template.name));
|
||||
|
||||
layergroupId = layergroup.layergroupid.split(":")[0];
|
||||
|
||||
return null;
|
||||
},
|
||||
function staticImageOptions(err) {
|
||||
assert.ifError(err);
|
||||
getStaticImageOptions(template, this);
|
||||
},
|
||||
function estimateBounds(err, imageOpts) {
|
||||
assert.ifError(err);
|
||||
if (imageOpts) {
|
||||
return imageOpts;
|
||||
}
|
||||
|
||||
var defaultZoomCenter = {
|
||||
zoom: 1,
|
||||
center: {
|
||||
lng: 0,
|
||||
lat: 0
|
||||
}
|
||||
};
|
||||
|
||||
var dbTables = cacheChannel.split(':');
|
||||
if (dbTables.length <= 1 || dbTables[1].length === 0) {
|
||||
return defaultZoomCenter;
|
||||
}
|
||||
|
||||
var tableNames = dbTables[1].split(',');
|
||||
if (tableNames.length === 0) {
|
||||
return defaultZoomCenter;
|
||||
}
|
||||
|
||||
var next = this;
|
||||
self.tablesExtentApi.getBounds(cdbUser, tableNames, function(err, result) {
|
||||
next(null, result || defaultZoomCenter);
|
||||
});
|
||||
},
|
||||
function getImage(err, imageOpts) {
|
||||
assert.ifError(err);
|
||||
|
||||
var staticImageReq = {
|
||||
headers: _.clone(fakeReq.headers),
|
||||
params: _.extend(_.clone(fakeReq.params), {
|
||||
token: layergroupId,
|
||||
format: req.params.format
|
||||
})
|
||||
};
|
||||
|
||||
var width = +req.params.width;
|
||||
var height = +req.params.height;
|
||||
|
||||
if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) {
|
||||
self.staticMapBackend.getImage(staticImageReq, width, height, imageOpts.zoom, imageOpts.center, this);
|
||||
} else {
|
||||
self.staticMapBackend.getImage(staticImageReq, width, height, imageOpts.bounds, this);
|
||||
}
|
||||
},
|
||||
function handleImage(err, image, headers, stats) {
|
||||
if (req.profiler) {
|
||||
req.profiler.done('render-' + format);
|
||||
req.profiler.add(stats || {});
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (!err.error) {
|
||||
err.error = err.message;
|
||||
}
|
||||
self.app.sendError(res, err, self.app.findStatusCode(err), 'STATIC_VIZ_MAP', err);
|
||||
} else {
|
||||
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
|
||||
res.setHeader('Cache-Control', 'public,max-age=7200,must-revalidate');
|
||||
self.app.sendResponse(res, [image, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function getStaticImageOptions(template, callback) {
|
||||
if (template.view) {
|
||||
var zoomCenter = templateZoomCenter(template.view);
|
||||
if (zoomCenter) {
|
||||
return callback(null, zoomCenter);
|
||||
}
|
||||
|
||||
var bounds = templateBounds(template.view);
|
||||
if (bounds) {
|
||||
return callback(null, bounds);
|
||||
}
|
||||
}
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
function templateZoomCenter(view) {
|
||||
if (!_.isUndefined(view.zoom) && view.center) {
|
||||
return {
|
||||
zoom: view.zoom,
|
||||
center: view.center
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function templateBounds(view) {
|
||||
if (view.bounds) {
|
||||
var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) {
|
||||
return !!view.bounds[prop];
|
||||
});
|
||||
if (hasAllBounds) {
|
||||
return {
|
||||
bounds: {
|
||||
west: view.bounds.west,
|
||||
south: view.bounds.south,
|
||||
east: view.bounds.east,
|
||||
north: view.bounds.north
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
var Step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
|
||||
NamedMapsCacheEntry, pgConnection) {
|
||||
this.app = app;
|
||||
this.serverOptions = serverOptions;
|
||||
this.templateMaps = templateMaps;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.NamedMapsCacheEntry = NamedMapsCacheEntry;
|
||||
this.pgConnection = pgConnection;
|
||||
}
|
||||
|
||||
module.exports = TemplateMapsController;
|
||||
|
||||
|
||||
TemplateMapsController.prototype.register = function(app) {
|
||||
app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this));
|
||||
app.post(this.templateBaseUrl, this.create.bind(this));
|
||||
app.put(this.templateBaseUrl + '/:template_id', this.update.bind(this));
|
||||
app.get(this.templateBaseUrl + '/:template_id', this.retrieve.bind(this));
|
||||
app.del(this.templateBaseUrl + '/:template_id', this.destroy.bind(this));
|
||||
app.get(this.templateBaseUrl, this.list.bind(this));
|
||||
app.options(this.templateBaseUrl + '/:template_id', this.options.bind(this));
|
||||
app.post(this.templateBaseUrl + '/:template_id', this.instantiate.bind(this));
|
||||
};
|
||||
|
||||
// Add a template
|
||||
TemplateMapsController.prototype.create = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = self.serverOptions.userByReq(req);
|
||||
|
||||
Step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function addTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can create templated maps");
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
||||
throw new Error('template POST data must be of type application/json');
|
||||
var cfg = req.body;
|
||||
self.templateMaps.addTemplate(cdbuser, cfg, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_id){
|
||||
if ( err ) throw err;
|
||||
// NOTE: might omit "cdbuser" if == dbowner ...
|
||||
return { template_id: cdbuser + '@' + tpl_id };
|
||||
},
|
||||
function finish(err, response){
|
||||
if ( req.profiler ) {
|
||||
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
||||
}
|
||||
if (err){
|
||||
response = { error: ''+err };
|
||||
var statusCode = 400;
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
self.app.sendError(res, response, statusCode, 'POST TEMPLATE', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, [response, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Update a template
|
||||
TemplateMapsController.prototype.update = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can list templated maps");
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
if ( ! req.headers['content-type'] || req.headers['content-type'].split(';')[0] != 'application/json' )
|
||||
throw new Error('template PUT data must be of type application/json');
|
||||
template = req.body;
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
err = new Error("Invalid template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
self.templateMaps.updTemplate(cdbuser, tpl_id, template, this);
|
||||
},
|
||||
function prepareResponse(err){
|
||||
if ( err ) throw err;
|
||||
return { template_id: cdbuser + '@' + tpl_id };
|
||||
},
|
||||
function finish(err, response){
|
||||
if ( req.profiler ) {
|
||||
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
||||
}
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
self.app.sendError(res, response, statusCode, 'PUT TEMPLATE', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, [response, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Get a specific template
|
||||
TemplateMapsController.prototype.retrieve = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if ( req.profiler && req.profiler.statsd_client ) {
|
||||
req.profiler.start('windshaft-cartodb.get_template');
|
||||
}
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated users can get template maps");
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot get template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val){
|
||||
if ( err ) throw err;
|
||||
if ( ! tpl_val ) {
|
||||
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
// auth_id was added by ourselves,
|
||||
// so we remove it before returning to the user
|
||||
delete tpl_val.auth_id;
|
||||
return { template: tpl_val };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
self.app.sendError(res, response, statusCode, 'GET TEMPLATE', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, [response, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Delete a specific template
|
||||
TemplateMapsController.prototype.destroy = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if ( req.profiler && req.profiler.statsd_client ) {
|
||||
req.profiler.start('windshaft-cartodb.delete_template');
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
var template;
|
||||
var tpl_id;
|
||||
Step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function updateTemplate(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated users can delete template maps");
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = req.params.template_id.split('@');
|
||||
if ( tpl_id.length > 1 ) {
|
||||
if ( tpl_id[0] != cdbuser ) {
|
||||
var err = new Error("Cannot find template id '"
|
||||
+ req.params.template_id + "' for user '" + cdbuser + "'");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_val){
|
||||
if ( err ) throw err;
|
||||
return { status: 'ok' };
|
||||
},
|
||||
function finish(err, response){
|
||||
if (err){
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
self.app.sendError(res, response, statusCode, 'DELETE TEMPLATE', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, ['', 204]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Get a list of owned templates
|
||||
TemplateMapsController.prototype.list = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if ( req.profiler && req.profiler.statsd_client ) {
|
||||
req.profiler.start('windshaft-cartodb.get_template_list');
|
||||
}
|
||||
this.app.doCORS(res);
|
||||
|
||||
var cdbuser = this.serverOptions.userByReq(req);
|
||||
|
||||
Step(
|
||||
function checkPerms(){
|
||||
self.serverOptions.authorizedByAPIKey(req, this);
|
||||
},
|
||||
function listTemplates(err, authenticated) {
|
||||
if ( err ) throw err;
|
||||
if (authenticated !== 1) {
|
||||
err = new Error("Only authenticated user can list templated maps");
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
self.templateMaps.listTemplates(cdbuser, this);
|
||||
},
|
||||
function prepareResponse(err, tpl_ids){
|
||||
if ( err ) throw err;
|
||||
// NOTE: might omit "cbduser" if == dbowner ...
|
||||
var ids = _.map(tpl_ids, function(id) { return cdbuser + '@' + id; });
|
||||
return { template_ids: ids };
|
||||
},
|
||||
function finish(err, response){
|
||||
var statusCode = 200;
|
||||
if (err){
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
self.app.sendError(res, response, statusCode, 'GET TEMPLATE LIST', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, [response, statusCode]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TemplateMapsController.prototype.instantiate = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if ( req.profiler && req.profiler.statsd_client) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
}
|
||||
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 ');
|
||||
}
|
||||
self.instantiateTemplate(req, res, req.body, this);
|
||||
}, function(err, response) {
|
||||
self.finish_instantiation(err, response, res, req);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TemplateMapsController.prototype.options = function(req, res) {
|
||||
this.app.doCORS(res, "Content-Type");
|
||||
return next();
|
||||
};
|
||||
|
||||
/**
|
||||
* jsonp endpoint, allows to instantiate a template with a json call.
|
||||
* callback query argument is mandatory
|
||||
*/
|
||||
TemplateMapsController.prototype.jsonp = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
if ( req.profiler && req.profiler.statsd_client) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
}
|
||||
Step(
|
||||
function() {
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
throw new Error('callback parameter should be present and be a function name');
|
||||
}
|
||||
var config = {};
|
||||
if(req.query.config) {
|
||||
try {
|
||||
config = JSON.parse(req.query.config);
|
||||
} catch(e) {
|
||||
throw new Error('badformed config parameter, should be a valid JSON');
|
||||
}
|
||||
}
|
||||
self.instantiateTemplate(req, res, config, this);
|
||||
}, function(err, response) {
|
||||
self.finish_instantiation(err, response, res, req);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Instantiate a template
|
||||
TemplateMapsController.prototype.instantiateTemplate = function(req, res, template_params, callback) {
|
||||
var self = this;
|
||||
|
||||
this.app.doCORS(res);
|
||||
|
||||
var template;
|
||||
var layergroup;
|
||||
var fakereq; // used for call to createLayergroup
|
||||
var cdbuser = self.serverOptions.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 + '"');
|
||||
err.http_status = 403;
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
tpl_id = tpl_id[1];
|
||||
}
|
||||
var auth_token = req.query.auth_token;
|
||||
Step(
|
||||
function getTemplate(){
|
||||
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
|
||||
},
|
||||
function checkAuthorized(err, templateValue) {
|
||||
if ( req.profiler ) req.profiler.done('getTemplate');
|
||||
if ( err ) throw err;
|
||||
if ( ! templateValue ) {
|
||||
err = new Error("Template '" + tpl_id + "' of user '" + cdbuser + "' not found");
|
||||
err.http_status = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
template = templateValue;
|
||||
|
||||
var authorized = false;
|
||||
try {
|
||||
authorized = self.templateMaps.isAuthorized(template, auth_token);
|
||||
} catch (err) {
|
||||
// we catch to add http_status
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
if ( ! authorized ) {
|
||||
err = new Error('Unauthorized template instanciation');
|
||||
err.http_status = 403;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (req.profiler) {
|
||||
req.profiler.done('authorizedByCert');
|
||||
}
|
||||
|
||||
return self.templateMaps.instance(template, template_params);
|
||||
},
|
||||
function prepareParams(err, instance){
|
||||
if ( req.profiler ) req.profiler.done('TemplateMaps_instance');
|
||||
if ( err ) throw err;
|
||||
layergroup = instance;
|
||||
fakereq = { query: {}, params: {}, headers: _.clone(req.headers),
|
||||
method: req.method,
|
||||
res: res,
|
||||
profiler: req.profiler
|
||||
};
|
||||
self.setDBParams(cdbuser, fakereq.params, this);
|
||||
},
|
||||
function setApiKey(err){
|
||||
if ( req.profiler ) req.profiler.done('setDBParams');
|
||||
if ( err ) throw err;
|
||||
self.metadataBackend.getUserMapKey(cdbuser, this);
|
||||
},
|
||||
function createLayergroup(err, val) {
|
||||
if ( req.profiler ) req.profiler.done('getUserMapKey');
|
||||
if ( err ) throw err;
|
||||
fakereq.params.api_key = val;
|
||||
self.app.createLayergroup(layergroup, fakereq, this);
|
||||
},
|
||||
function prepareResponse(err, layergroup) {
|
||||
if ( err ) {
|
||||
throw err;
|
||||
}
|
||||
var tplhash = self.templateMaps.fingerPrint(template).substring(0,8);
|
||||
layergroup.layergroupid = cdbuser + '@' + tplhash + '@' + layergroup.layergroupid;
|
||||
|
||||
self.surrogateKeysCache.tag(res, new self.NamedMapsCacheEntry(cdbuser, template.name));
|
||||
|
||||
return layergroup;
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
TemplateMapsController.prototype.finish_instantiation = function(err, response, res, req) {
|
||||
if ( req.profiler ) {
|
||||
res.header('X-Tiler-Profiler', req.profiler.toJSONString());
|
||||
}
|
||||
if (err) {
|
||||
var statusCode = 400;
|
||||
response = { error: ''+err };
|
||||
if ( ! _.isUndefined(err.http_status) ) {
|
||||
statusCode = err.http_status;
|
||||
}
|
||||
if(global.environment.debug) {
|
||||
response.stack = err.stack;
|
||||
}
|
||||
this.app.sendError(res, response, statusCode, 'POST INSTANCE TEMPLATE', err);
|
||||
} else {
|
||||
this.app.sendResponse(res, [response, 200]);
|
||||
}
|
||||
};
|
||||
|
||||
TemplateMapsController.prototype.setDBParams = function(cdbuser, params, callback) {
|
||||
var self = this;
|
||||
Step(
|
||||
function setAuth() {
|
||||
self.pgConnection.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if ( err ) throw err;
|
||||
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];
|
||||
};
|
||||
@@ -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,53 +1,50 @@
|
||||
var _ = require('underscore');
|
||||
var Step = require('step');
|
||||
var QueryTablesApi = require('./api/query_tables_api');
|
||||
var PgConnection = require('./backends/pg_connection');
|
||||
var crypto = require('crypto');
|
||||
var step = require('step');
|
||||
var LZMA = require('lzma').LZMA;
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
|
||||
var QueryTablesApi = require('./api/query_tables_api');
|
||||
var PgQueryRunner = require('./backends/pg_query_runner');
|
||||
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');
|
||||
|
||||
// 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 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) {
|
||||
redisPool = redisPool
|
||||
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
|
||||
redisPool = redisPool || new RedisPool(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
|
||||
|
||||
var cartoData = require('cartodb-redis')({ pool: redisPool }),
|
||||
lzmaWorker = new LZMA(),
|
||||
pgConnection = new PgConnection(cartoData),
|
||||
queryTablesApi = new QueryTablesApi(pgConnection, cartoData);
|
||||
var cartoData = require('cartodb-redis')({ pool: redisPool });
|
||||
var lzmaWorker = new LZMA();
|
||||
var pgConnection = new PgConnection(cartoData);
|
||||
var pgQueryRunner = new PgQueryRunner(pgConnection);
|
||||
var queryTablesApi = new QueryTablesApi(pgQueryRunner);
|
||||
var 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 = {
|
||||
@@ -73,13 +70,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: {
|
||||
@@ -87,6 +78,7 @@ module.exports = function(redisPool) {
|
||||
statsInterval: rendererConfig.statsInterval
|
||||
},
|
||||
renderer: {
|
||||
mapnik: rendererConfig.mapnik,
|
||||
http: rendererConfig.http
|
||||
},
|
||||
redis: global.environment.redis,
|
||||
@@ -96,6 +88,7 @@ module.exports = function(redisPool) {
|
||||
varnish_http_port: global.environment.varnish.http_port,
|
||||
varnish_secret: global.environment.varnish.secret,
|
||||
varnish_purge_enabled: global.environment.varnish.purge_enabled,
|
||||
fastly: global.environment.fastly || {},
|
||||
cache_enabled: global.environment.cache_enabled,
|
||||
log_format: global.environment.log_format,
|
||||
useProfiler: global.environment.useProfiler
|
||||
@@ -110,6 +103,8 @@ module.exports = function(redisPool) {
|
||||
|
||||
// Re-use pgConnection
|
||||
me.pgConnection = pgConnection;
|
||||
// Re-use pgQueryRunner
|
||||
me.pgQueryRunner = pgQueryRunner;
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
@@ -130,106 +125,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];
|
||||
}
|
||||
|
||||
queryTablesApi.getAffectedTablesInQuery(that.userByReq(req), sql, this); // in addCacheChannel
|
||||
},
|
||||
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
|
||||
@@ -244,11 +214,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;
|
||||
@@ -267,8 +237,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);
|
||||
@@ -280,21 +252,62 @@ module.exports = function(redisPool) {
|
||||
});
|
||||
};
|
||||
|
||||
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
|
||||
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
requestMapConfig.layers = layers;
|
||||
return callback(null, requestMapConfig, datasource)
|
||||
});
|
||||
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 = [];
|
||||
@@ -321,8 +334,12 @@ 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();
|
||||
});
|
||||
|
||||
@@ -333,24 +350,22 @@ module.exports = function(redisPool) {
|
||||
var dbName = req.params.dbname;
|
||||
var cacheKey = dbName + ':' + token;
|
||||
|
||||
Step(
|
||||
step(
|
||||
function getAffectedTablesAndLastUpdatedTime() {
|
||||
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);
|
||||
}
|
||||
@@ -368,28 +383,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];
|
||||
};
|
||||
|
||||
// Check if a request is authorized by a signer
|
||||
//
|
||||
// @param req express request object
|
||||
@@ -397,33 +390,29 @@ 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) {
|
||||
if (err) {
|
||||
return callback(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
|
||||
@@ -444,13 +433,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) {
|
||||
@@ -467,15 +456,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) {
|
||||
@@ -485,28 +476,18 @@ module.exports = function(redisPool) {
|
||||
return;
|
||||
}
|
||||
|
||||
_.extend(req.params, { _authorizedByApiKey: true });
|
||||
|
||||
// authorized by api key, login as the given username and stop
|
||||
pgConnection.setDBAuth(user, req.params, function(err) {
|
||||
callback(err, true); // authorized (or error)
|
||||
});
|
||||
},
|
||||
function checkSignAuthorized(err, signed_by){
|
||||
if (err) throw err;
|
||||
if (req.profiler) {
|
||||
if ( req.params._authorizedByApiKey ) {
|
||||
req.profiler.done('setDBAuth');
|
||||
} else {
|
||||
req.profiler.done('authorizedBySigner');
|
||||
}
|
||||
if (err) {
|
||||
return callback(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.
|
||||
@@ -521,30 +502,32 @@ module.exports = function(redisPool) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorized by "signed_by" !
|
||||
_.extend(req.params, { _authorizedBySigner: signed_by });
|
||||
pgConnection.setDBAuth(signed_by, req.params, function(err) {
|
||||
if (req.profiler) req.profiler.done('setDBAuth');
|
||||
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");
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
me.setDBParams = function(cdbuser, params, callback) {
|
||||
step(
|
||||
function setAuth() {
|
||||
pgConnection.setDBAuth(cdbuser, params, this);
|
||||
},
|
||||
function setConn(err) {
|
||||
if ( err ) throw err;
|
||||
pgConnection.setDBConn(cdbuser, params, this);
|
||||
},
|
||||
function finish(err) {
|
||||
callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// jshint maxcomplexity:10
|
||||
/**
|
||||
* Whitelist input and get database name & default geometry type from
|
||||
* subdomain/user metadata held in CartoDB Redis
|
||||
@@ -555,17 +538,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));
|
||||
@@ -573,38 +560,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);
|
||||
@@ -614,20 +601,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;
|
||||
@@ -636,21 +622,12 @@ module.exports = function(redisPool) {
|
||||
return null;
|
||||
},
|
||||
function getDatabase(err){
|
||||
if(err) throw err;
|
||||
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, {
|
||||
@@ -665,61 +642,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);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -56,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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -71,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);
|
||||
},
|
||||
@@ -89,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);
|
||||
@@ -136,7 +137,6 @@ o._checkInvalidTemplate = function(template) {
|
||||
break;
|
||||
default:
|
||||
return new Error("Unsupported authentication method: " + auth.method);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -205,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;
|
||||
@@ -253,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);
|
||||
},
|
||||
@@ -311,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);
|
||||
},
|
||||
@@ -366,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);
|
||||
},
|
||||
@@ -424,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 || {};
|
||||
@@ -450,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 {
|
||||
@@ -494,3 +492,14 @@ o.fingerPrint = function(template) {
|
||||
.digest('hex')
|
||||
;
|
||||
};
|
||||
|
||||
module.exports.templateName = function templateName(templateId) {
|
||||
var templateIdTokens = templateId.split('@');
|
||||
var name = templateIdTokens[0];
|
||||
|
||||
if (templateIdTokens.length > 1) {
|
||||
name = templateIdTokens[1];
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
1231
npm-shrinkwrap.json
generated
1231
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "1.28.2",
|
||||
"version": "2.3.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -22,28 +22,31 @@
|
||||
"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": "~0.37.2",
|
||||
"windshaft": "0.43.0",
|
||||
"step": "~0.0.5",
|
||||
"queue-async": "~1.0.7",
|
||||
"request": "~2.9.203",
|
||||
"cartodb-redis": "~0.11.0",
|
||||
"cartodb-redis": "~0.13.0",
|
||||
"cartodb-psql": "~0.4.0",
|
||||
"fastly-purge": "~1.0.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));
|
||||
});
|
||||
187
test/acceptance/cache/surrogate_keys_invalidation.js
vendored
187
test/acceptance/cache/surrogate_keys_invalidation.js
vendored
@@ -1,32 +1,41 @@
|
||||
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 FastlyPurge = require('fastly-purge');
|
||||
|
||||
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 sqlApiServer;
|
||||
var redisClient = redis.createClient(global.environment.redis.port);
|
||||
|
||||
// Enable Varnish purge for tests
|
||||
serverOptions.varnish_purge_enabled = true;
|
||||
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 fastlyConfig = global.environment.fastly;
|
||||
var FAKE_FASTLY_API_KEY = 'fastly-api-key';
|
||||
var FAKE_FASTLY_SERVICE_ID = 'fake-service-id';
|
||||
global.environment.fastly = {
|
||||
enabled: true,
|
||||
// the fastly api key
|
||||
apiKey: FAKE_FASTLY_API_KEY,
|
||||
// the service that will get surrogate key invalidation
|
||||
serviceId: FAKE_FASTLY_SERVICE_ID
|
||||
};
|
||||
|
||||
var serverOptions = require('../../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var templateOwner = 'localhost',
|
||||
templateName = 'acceptance',
|
||||
expectedTemplateId = templateOwner + '@' + templateName,
|
||||
expectedTemplateId = templateName,
|
||||
template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
@@ -48,15 +57,31 @@ suite('templates surrogate keys', function() {
|
||||
},
|
||||
expectedBody = { template_id: expectedTemplateId };
|
||||
|
||||
suiteSetup(function(done) {
|
||||
sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done);
|
||||
});
|
||||
var varnishHttpUrl = [
|
||||
'http://', global.environment.varnish.host, ':', global.environment.varnish.http_port
|
||||
].join('');
|
||||
|
||||
var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate;
|
||||
var cacheEntryKey = new NamedMapsCacheEntry(templateOwner, templateName).key();
|
||||
var invalidationMatchHeader = '\\b' + cacheEntryKey + '\\b';
|
||||
var fastlyPurgePath = '/service/' + FAKE_FASTLY_SERVICE_ID + '/purge/' + encodeURIComponent(cacheEntryKey);
|
||||
|
||||
var nock = require('nock');
|
||||
nock.enableNetConnect(/(127.0.0.1:5555|cartocdn.com)/);
|
||||
|
||||
after(function(done) {
|
||||
serverOptions.varnish_purge_enabled = false;
|
||||
global.environment.varnish.host = varnishHost;
|
||||
global.environment.varnish.purge_enabled = varnishPurgeEnabled;
|
||||
|
||||
global.environment.fastly = fastlyConfig;
|
||||
|
||||
nock.restore();
|
||||
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,
|
||||
@@ -65,7 +90,7 @@ suite('templates surrogate keys', function() {
|
||||
data: JSON.stringify(template)
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function postTemplate() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
@@ -92,15 +117,23 @@ suite('templates surrogate keys', function() {
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
var scope = nock(varnishHttpUrl)
|
||||
.intercept('/key', 'PURGE')
|
||||
.matchHeader('Invalidation-Match', invalidationMatchHeader)
|
||||
.reply(204, '');
|
||||
|
||||
var fastlyScope = nock(FastlyPurge.FASTLY_API_ENDPOINT)
|
||||
.post(fastlyPurgePath)
|
||||
.matchHeader('Fastly-Key', FAKE_FASTLY_API_KEY)
|
||||
.matchHeader('Fastly-Soft-Purge', 1)
|
||||
.matchHeader('Accept', 'application/json')
|
||||
.reply(200, {
|
||||
status:'ok'
|
||||
});
|
||||
|
||||
step(
|
||||
function createTemplateToUpdate() {
|
||||
createTemplate(this);
|
||||
},
|
||||
@@ -109,7 +142,7 @@ suite('templates surrogate keys', function() {
|
||||
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,
|
||||
@@ -135,8 +168,8 @@ 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);
|
||||
assert.equal(fastlyScope.pendingMocks().length, 0);
|
||||
|
||||
return null;
|
||||
},
|
||||
@@ -156,16 +189,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(
|
||||
var fastlyScope = nock(FastlyPurge.FASTLY_API_ENDPOINT)
|
||||
.post(fastlyPurgePath)
|
||||
.matchHeader('Fastly-Key', FAKE_FASTLY_API_KEY)
|
||||
.matchHeader('Fastly-Soft-Purge', 1)
|
||||
.matchHeader('Accept', 'application/json')
|
||||
.reply(200, {
|
||||
status:'ok'
|
||||
});
|
||||
|
||||
step(
|
||||
function createTemplateToDelete() {
|
||||
createTemplate(this);
|
||||
},
|
||||
@@ -174,7 +214,7 @@ suite('templates surrogate keys', function() {
|
||||
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,
|
||||
@@ -197,8 +237,8 @@ suite('templates surrogate keys', function() {
|
||||
throw err;
|
||||
}
|
||||
|
||||
assert.ok(surrogateKeysCacheInvalidateMethodInvoked);
|
||||
assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key());
|
||||
assert.equal(scope.pendingMocks().length, 0);
|
||||
assert.equal(fastlyScope.pendingMocks().length, 0);
|
||||
|
||||
return null;
|
||||
},
|
||||
@@ -208,11 +248,66 @@ suite('templates surrogate keys', function() {
|
||||
);
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn;
|
||||
// Enable Varnish purge for tests
|
||||
serverOptions.varnish_purge_enabled = false;
|
||||
sqlApiServer.close(done);
|
||||
it("should update template even if surrogate key invalidation fails", function(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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,11 +1,16 @@
|
||||
var helper = require(__dirname + '/../support/test_helper');
|
||||
require(__dirname + '/../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);
|
||||
|
||||
suite('health checks', function () {
|
||||
var metadataBackend = {};
|
||||
var tilelive = {};
|
||||
var HealthCheck = require('../../lib/cartodb/monitoring/health_check');
|
||||
var healthCheck = new HealthCheck(metadataBackend, tilelive);
|
||||
|
||||
describe('health checks', function () {
|
||||
|
||||
function resetHealthConfig() {
|
||||
global.environment.health = {
|
||||
@@ -25,7 +30,7 @@ suite('health checks', function () {
|
||||
}
|
||||
};
|
||||
|
||||
test('returns 200 and ok=true with enabled configuration', function (done) {
|
||||
it('returns 200 and ok=true with enabled configuration', function (done) {
|
||||
resetHealthConfig();
|
||||
|
||||
assert.response(server,
|
||||
@@ -46,29 +51,44 @@ suite('health checks', function () {
|
||||
);
|
||||
});
|
||||
|
||||
test('fails for invalid user because it is not in redis', function (done) {
|
||||
resetHealthConfig();
|
||||
it('error if disabled file exists', function(done) {
|
||||
var fs = require('fs');
|
||||
|
||||
global.environment.health.username = 'invalid';
|
||||
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;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
assert.response(server,
|
||||
healthCheckRequest,
|
||||
{
|
||||
status: 503
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ok(!err);
|
||||
it('not err if disabled file does not exists', function(done) {
|
||||
resetHealthConfig();
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
global.environment.disabled_file = '/tmp/ftreftrgtrccre';
|
||||
|
||||
assert.equal(parsed.enabled, true);
|
||||
assert.equal(parsed.ok, false);
|
||||
assert.response(server,
|
||||
healthCheckRequest,
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ok(!err);
|
||||
|
||||
assert.equal(parsed.result.redis.ok, false);
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
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,42 +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', '/user/localhost/api/v1/map'].forEach(function(layergroup_url) {
|
||||
|
||||
suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() {
|
||||
var suiteName = 'multilayer:postgres=layergroup_url=' + layergroup_url;
|
||||
suite(suiteName, 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){
|
||||
global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue };
|
||||
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'
|
||||
@@ -61,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);
|
||||
@@ -94,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'
|
||||
@@ -114,19 +99,23 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
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[])');
|
||||
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
|
||||
@@ -135,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'
|
||||
@@ -152,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);
|
||||
});
|
||||
});
|
||||
@@ -170,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);
|
||||
});
|
||||
});
|
||||
@@ -204,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); });
|
||||
@@ -232,7 +219,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url));
|
||||
done();
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -242,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'
|
||||
} }
|
||||
@@ -250,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); });
|
||||
@@ -299,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) {
|
||||
@@ -314,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) {
|
||||
@@ -337,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 from test_table limit 1',
|
||||
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'
|
||||
@@ -347,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);
|
||||
@@ -379,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'
|
||||
@@ -399,16 +381,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
.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.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);
|
||||
});
|
||||
});
|
||||
@@ -418,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'
|
||||
@@ -438,16 +420,16 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
.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.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);
|
||||
});
|
||||
});
|
||||
@@ -457,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);
|
||||
});
|
||||
});
|
||||
@@ -475,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);
|
||||
});
|
||||
});
|
||||
@@ -513,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'
|
||||
} }
|
||||
@@ -525,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;
|
||||
@@ -539,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)
|
||||
@@ -551,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)
|
||||
@@ -574,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) {
|
||||
@@ -612,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)
|
||||
@@ -648,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)
|
||||
@@ -683,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);
|
||||
@@ -716,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'
|
||||
@@ -737,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) {
|
||||
@@ -751,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) {
|
||||
@@ -766,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);
|
||||
});
|
||||
@@ -782,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);
|
||||
});
|
||||
@@ -798,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);
|
||||
});
|
||||
@@ -844,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)
|
||||
@@ -859,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);
|
||||
@@ -878,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'
|
||||
@@ -896,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)
|
||||
@@ -908,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'
|
||||
@@ -954,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)
|
||||
@@ -982,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)
|
||||
@@ -1011,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)
|
||||
@@ -1039,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);
|
||||
}
|
||||
@@ -1068,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) {
|
||||
@@ -1115,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)
|
||||
@@ -1146,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'
|
||||
@@ -1154,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;
|
||||
},
|
||||
@@ -1186,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 = {
|
||||
@@ -1204,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
|
||||
@@ -1223,10 +1180,6 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var token_components = parsedBody.layergroupid.split(':');
|
||||
expected_token = token_components[0];
|
||||
if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API
|
||||
var last_request = sqlapi_server.getLastRequest();
|
||||
assert.equal(last_request.method, 'POST');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function cleanup(err) {
|
||||
@@ -1257,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)
|
||||
@@ -1305,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)
|
||||
@@ -1334,7 +1288,7 @@ 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: {
|
||||
@@ -1413,7 +1367,7 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
{
|
||||
status: 403
|
||||
},
|
||||
function(res, err) {
|
||||
function(res) {
|
||||
assert.ok(res.body.match(/permission denied for relation test_table_private_1/));
|
||||
done();
|
||||
}
|
||||
@@ -1426,11 +1380,11 @@ suite('multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function(
|
||||
// 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 PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
|
||||
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 = PgQueryRunner.prototype.run;
|
||||
PgQueryRunner.prototype.run = 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) {
|
||||
PgQueryRunner.prototype.run = 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 = PgQueryRunner.prototype.run;
|
||||
PgQueryRunner.prototype.run = 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'));
|
||||
PgQueryRunner.prototype.run = runQueryFn;
|
||||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -8,12 +8,11 @@ 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');
|
||||
var step = require('step');
|
||||
|
||||
suite('named_layers', function() {
|
||||
describe('named_layers', function() {
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
@@ -95,7 +94,7 @@ suite('named_layers', function() {
|
||||
}
|
||||
};
|
||||
|
||||
suiteSetup(function(done) {
|
||||
before(function(done) {
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: true};
|
||||
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||
if (err) {
|
||||
@@ -112,7 +111,7 @@ suite('named_layers', function() {
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail for non-existing template name', function(done) {
|
||||
it('should fail for non-existing template name', function(done) {
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
@@ -125,12 +124,12 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -162,7 +161,7 @@ suite('named_layers', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('should return 403 if not properly authorized', function(done) {
|
||||
it('should return 403 if not properly authorized', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
@@ -178,12 +177,12 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -219,7 +218,7 @@ suite('named_layers', function() {
|
||||
|
||||
});
|
||||
|
||||
test('should return 200 and layergroup if properly authorized', function(done) {
|
||||
it('should return 200 and layergroup if properly authorized', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
@@ -235,12 +234,12 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -274,7 +273,7 @@ suite('named_layers', function() {
|
||||
|
||||
});
|
||||
|
||||
test('should return 400 for nested named map layers', function(done) {
|
||||
it('should return 400 for nested named map layers', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
@@ -288,12 +287,12 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -326,7 +325,7 @@ suite('named_layers', function() {
|
||||
|
||||
});
|
||||
|
||||
test('should return 200 and layergroup with private tables', function(done) {
|
||||
it('should return 200 and layergroup with private tables', function(done) {
|
||||
|
||||
var privateTableTemplateName = 'private_table_template';
|
||||
var privateTableTemplate = {
|
||||
@@ -361,7 +360,7 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createTemplate() {
|
||||
templateMaps.addTemplate(username, privateTableTemplate, this);
|
||||
},
|
||||
@@ -373,7 +372,7 @@ suite('named_layers', function() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -408,7 +407,7 @@ suite('named_layers', function() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup/' + layergroupId + '/0/0/0.png',
|
||||
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
@@ -447,7 +446,135 @@ suite('named_layers', function() {
|
||||
|
||||
});
|
||||
|
||||
test('should return 403 when private table is accessed from non named layer', function(done) {
|
||||
it('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);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
it('should return 403 when private table is accessed from non named layer', function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
@@ -469,12 +596,12 @@ suite('named_layers', function() {
|
||||
]
|
||||
};
|
||||
|
||||
Step(
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/tiles/layergroup',
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
@@ -508,7 +635,7 @@ suite('named_layers', function() {
|
||||
});
|
||||
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
after(function(done) {
|
||||
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: false};
|
||||
templateMaps.delTemplate(username, nestedNamedMapTemplateName, function(err) {
|
||||
if (err) {
|
||||
|
||||
189
test/acceptance/named_static_maps.js
Normal file
189
test/acceptance/named_static_maps.js
Normal file
@@ -0,0 +1,189 @@
|
||||
var test_helper = require('../support/test_helper');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var querystring = require('querystring');
|
||||
|
||||
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 TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||
var NamedMapsCacheEntry = require('../../lib/cartodb/cache/model/named_maps_entry');
|
||||
|
||||
describe('named static maps', function() {
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = new 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
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
before(function (done) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function (done) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getStaticMap(name, options, callback) {
|
||||
|
||||
var url = '/api/v1/map/static/named/' + name + '/640/480.png';
|
||||
if (options.params) {
|
||||
url = url + '?' + querystring.stringify(options.params);
|
||||
}
|
||||
var requestOptions = {
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: username
|
||||
}
|
||||
};
|
||||
|
||||
console.log(url);
|
||||
|
||||
var statusCode = options.status || 200;
|
||||
|
||||
var expectedResponse = {
|
||||
status: statusCode,
|
||||
headers: {
|
||||
'Content-Type': statusCode === 200 ? 'image/png' : 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(server,
|
||||
requestOptions,
|
||||
expectedResponse,
|
||||
function (res, err) {
|
||||
return callback(err, res);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
it('should return a 404 error for nonexistent template name', function (done) {
|
||||
var nonexistentName = 'nonexistent';
|
||||
getStaticMap(nonexistentName, { status: 404 }, function(err, res) {
|
||||
assert.ok(!err);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.error, "Template '" + nonexistentName + "' of user '" + username + "' not found");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 403 if not properly authorized', function(done) {
|
||||
getStaticMap(tokenAuthTemplateName, { status: 403 }, function(err, res) {
|
||||
assert.ok(!err);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.error, 'Unauthorized template instantiation');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 200 if properly authorized', function(done) {
|
||||
getStaticMap(tokenAuthTemplateName, { params: { auth_token: 'valid1' } }, function(err, res) {
|
||||
assert.ok(!err);
|
||||
test_helper.checkSurrogateKey(res, new NamedMapsCacheEntry(username, tokenAuthTemplateName).key());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
295
test/acceptance/x_cache_channel.js
Normal file
295
test/acceptance/x_cache_channel.js
Normal file
@@ -0,0 +1,295 @@
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var qs = require('querystring');
|
||||
|
||||
var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft');
|
||||
var serverOptions = require('../../lib/cartodb/server_options')();
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
describe('get requests x-cache-channel', function() {
|
||||
|
||||
var statusOkResponse = {
|
||||
status: 200
|
||||
};
|
||||
|
||||
var mapConfig = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select * from test_table limit 2',
|
||||
cartocss: '#layer { marker-fill:red; }',
|
||||
cartocss_version: '2.3.0',
|
||||
attributes: {
|
||||
id:'cartodb_id',
|
||||
columns: [
|
||||
'name',
|
||||
'address'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var layergroupRequest = {
|
||||
url: '/api/v1/map?config=' + encodeURIComponent(JSON.stringify(mapConfig)),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
};
|
||||
|
||||
function getRequest(url, addApiKey, callbackName) {
|
||||
var params = {};
|
||||
if (!!addApiKey) {
|
||||
params.api_key = '1234';
|
||||
}
|
||||
if (!!callbackName) {
|
||||
params.callback = callbackName;
|
||||
}
|
||||
|
||||
return {
|
||||
url: url + '?' + qs.stringify(params),
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function validateXCacheChannel(done, expectedCacheChannel) {
|
||||
return function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.ok(res.headers['x-cache-channel']);
|
||||
if (expectedCacheChannel) {
|
||||
assert.equal(res.headers['x-cache-channel'], expectedCacheChannel);
|
||||
}
|
||||
|
||||
done();
|
||||
};
|
||||
}
|
||||
|
||||
function noXCacheChannelHeader(done) {
|
||||
return function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
!res.headers['x-cache-channel'],
|
||||
'did not expect x-cache-channel header, got: `' + res.headers['x-cache-channel'] + '`'
|
||||
);
|
||||
done();
|
||||
};
|
||||
}
|
||||
|
||||
function withLayergroupId(callback) {
|
||||
assert.response(
|
||||
server,
|
||||
layergroupRequest,
|
||||
statusOkResponse,
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, JSON.parse(res.body).layergroupid);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
describe('header should be present', function() {
|
||||
|
||||
it('/api/v1/map Map instantiation', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
layergroupRequest,
|
||||
statusOkResponse,
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
|
||||
it ('/api/v1/map/:token/:z/:x/:y@:scale_factor?x.:format Mapnik retina tiles', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/' + layergroupId + '/0/0/0@2x.png'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it ('/api/v1/map/:token/:z/:x/:y@:scale_factor?x.:format Mapnik tiles', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/' + layergroupId + '/0/0/0.png'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it ('/api/v1/map/:token/:layer/:z/:x/:y.(:format) Per :layer rendering', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/' + layergroupId + '/0/0/0/0.png'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it ('/api/v1/map/:token/:layer/attributes/:fid endpoint for info windows', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/' + layergroupId + '/0/attributes/1'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it ('/api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format static maps', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/static/center/' + layergroupId + '/0/0/0/400/300.png'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it ('/api/v1/map/static/bbox/:token/:bbox/:width/:height.:format static maps', function(done) {
|
||||
withLayergroupId(function(err, layergroupId) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/static/bbox/' + layergroupId + '/-45,-45,45,45/400/300.png'),
|
||||
validateXCacheChannel(done, 'test_windshaft_cartodb_user_1_db:public.test_table')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('header should NOT be present', function() {
|
||||
|
||||
it('/', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/'),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
it('/version', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/version'),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
it('/health', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/health'),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
it('/api/v1/map/named list named maps', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/named', true),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
describe('with named maps', function() {
|
||||
|
||||
var templateName = 'x_cache';
|
||||
|
||||
before(function(done) {
|
||||
var template = {
|
||||
version: '0.0.1',
|
||||
name: templateName,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
layergroup: mapConfig
|
||||
};
|
||||
|
||||
var namedMapRequest = {
|
||||
url: '/api/v1/map/named?api_key=1234',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(template)
|
||||
};
|
||||
|
||||
assert.response(
|
||||
server,
|
||||
namedMapRequest,
|
||||
statusOkResponse,
|
||||
function(res, err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/map/named/' + templateName + '?api_key=1234',
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 204
|
||||
},
|
||||
function(res, err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('/api/v1/map/named/:template_id Named map retrieval', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/named/' + templateName, true),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
it('/api/v1/map/named/:template_id/jsonp Named map retrieval', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
getRequest('/api/v1/map/named/' + templateName, true, 'cb'),
|
||||
statusOkResponse,
|
||||
noXCacheChannelHeader(done)
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
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 |
@@ -1,4 +1,4 @@
|
||||
var test_helper = require('../support/test_helper');
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
@@ -6,11 +6,8 @@ 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 redisPool = new RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
@@ -97,8 +94,8 @@ var multipleLayersTemplate = {
|
||||
}
|
||||
};
|
||||
|
||||
suite('named_layers datasources', function() {
|
||||
suiteSetup(function(done) {
|
||||
describe('named_layers datasources', function() {
|
||||
before(function(done) {
|
||||
templateMaps.addTemplate(username, template, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
@@ -224,7 +221,14 @@ suite('named_layers datasources', function() {
|
||||
|
||||
{
|
||||
desc: 'with a mix of datasource and no datasource depending if layers are named or not',
|
||||
config: makeNamedMapLayerConfig([simpleNamedLayer, multipleLayersNamedLayer, wadusLayer, simpleNamedLayer, wadusLayer, multipleLayersNamedLayer]),
|
||||
config: makeNamedMapLayerConfig([
|
||||
simpleNamedLayer,
|
||||
multipleLayersNamedLayer,
|
||||
wadusLayer,
|
||||
simpleNamedLayer,
|
||||
wadusLayer,
|
||||
multipleLayersNamedLayer
|
||||
]),
|
||||
test: function(err, layers, datasource, done) {
|
||||
|
||||
assert.ok(!err);
|
||||
@@ -280,14 +284,16 @@ suite('named_layers datasources', function() {
|
||||
];
|
||||
|
||||
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);
|
||||
});
|
||||
it('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) {
|
||||
after(function(done) {
|
||||
templateMaps.delTemplate(username, templateName, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var testHelper = require('../support/test_helper');
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
@@ -6,13 +6,10 @@ 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() {
|
||||
describe('mapconfig_named_layers_adapter', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = RedisPool(global.environment.redis);
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
var pgConnection = new PgConnection(require('cartodb-redis')({ pool: redisPool }));
|
||||
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
@@ -138,40 +135,46 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
}
|
||||
|
||||
|
||||
suiteSetup(function(done) {
|
||||
before(function(done) {
|
||||
templateMaps.addTemplate(username, template, done);
|
||||
});
|
||||
|
||||
test('should fail for named map layer with missing name', function(done) {
|
||||
it('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');
|
||||
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();
|
||||
});
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('should fail for non-existing template name', function(done) {
|
||||
it('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");
|
||||
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();
|
||||
});
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('should fail if not properly authorized', function(done) {
|
||||
it('should fail if not properly authorized', function(done) {
|
||||
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
@@ -180,18 +183,20 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
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");
|
||||
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);
|
||||
});
|
||||
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should fail for nested named map layers', function(done) {
|
||||
it('should fail for nested named map layers', function(done) {
|
||||
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
@@ -200,32 +205,36 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
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');
|
||||
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);
|
||||
});
|
||||
templateMaps.delTemplate(username, nestedNamedMapTemplateName, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return an expanded list of layers for a named map layer', function(done) {
|
||||
it('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);
|
||||
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();
|
||||
});
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('should return on auth=token with valid tokens provided', function(done) {
|
||||
it('should return on auth=token with valid tokens provided', function(done) {
|
||||
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
@@ -235,17 +244,19 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
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);
|
||||
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);
|
||||
});
|
||||
templateMaps.delTemplate(username, tokenAuthTemplateName, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return an expanded list of layers for a named map layer, multiple layers version', function(done) {
|
||||
it('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);
|
||||
@@ -255,24 +266,26 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
name: multipleLayersTemplateName,
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
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[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);
|
||||
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);
|
||||
});
|
||||
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should replace template params with the given config', function(done) {
|
||||
it('should replace template params with the given config', function(done) {
|
||||
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
@@ -289,24 +302,26 @@ suite('mapconfig_named_layers_adapter', function() {
|
||||
},
|
||||
auth_tokens: ['valid2']
|
||||
});
|
||||
mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, pgConnection, function(err, layers, datasource) {
|
||||
assert.ok(!err);
|
||||
assert.equal(layers.length, 2);
|
||||
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[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);
|
||||
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);
|
||||
});
|
||||
templateMaps.delTemplate(username, multipleLayersTemplateName, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
after(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();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -16,9 +16,9 @@ BEGIN
|
||||
|
||||
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;
|
||||
IF NOT ( rec.q ilike 'select%' or rec.q ilike 'with%' ) THEN
|
||||
--RAISE WARNING 'Skipping %', rec.q;
|
||||
CONTINUE;
|
||||
END IF;
|
||||
|
||||
BEGIN
|
||||
|
||||
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,16 +1,15 @@
|
||||
var assert = require('assert')
|
||||
//, _ = require('underscore')
|
||||
, RedisPool = require('redis-mpool')
|
||||
, TemplateMaps = require('../../../lib/cartodb/template_maps.js')
|
||||
, test_helper = require('../../support/test_helper')
|
||||
, Step = require('step')
|
||||
, _ = require('underscore')
|
||||
, tests = module.exports = {};
|
||||
require('../../support/test_helper');
|
||||
|
||||
suite('template_maps', function() {
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
|
||||
var step = require('step');
|
||||
var _ = require('underscore');
|
||||
|
||||
describe('template_maps', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redis_pool = RedisPool(global.environment.redis);
|
||||
var redis_pool = new RedisPool(global.environment.redis);
|
||||
|
||||
var wadusLayer = {
|
||||
options: {
|
||||
@@ -20,24 +19,12 @@ suite('template_maps', function() {
|
||||
}
|
||||
};
|
||||
|
||||
var validTemplate = {
|
||||
version:'0.0.1',
|
||||
name: 'first',
|
||||
auth: {},
|
||||
layergroup: {
|
||||
layers: [
|
||||
wadusLayer
|
||||
]
|
||||
}
|
||||
};
|
||||
var owner = 'me';
|
||||
|
||||
test('does not accept template with unsupported version', function(done) {
|
||||
it('does not accept template with unsupported version', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'6.6.6',
|
||||
name:'k', auth: {}, layergroup: {layers:[wadusLayer]} };
|
||||
Step(
|
||||
step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
@@ -52,12 +39,12 @@ suite('template_maps', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept template with missing name', function(done) {
|
||||
it('does not accept template with missing name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
auth: {}, layergroup: {layers:[wadusLayer]} };
|
||||
Step(
|
||||
step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
@@ -72,7 +59,7 @@ suite('template_maps', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept template with invalid name', function(done) {
|
||||
it('does not accept template with invalid name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
@@ -87,8 +74,7 @@ suite('template_maps', function() {
|
||||
done(new Error("Unexpected success with invalid name '" + n + "'"));
|
||||
}
|
||||
else if ( ! err.message.match(/template.*name/i) ) {
|
||||
done(new Error("Unexpected error message with invalid name '" + n
|
||||
+ "': " + err));
|
||||
done(new Error("Unexpected error message with invalid name '" + n + "': " + err));
|
||||
}
|
||||
else {
|
||||
testNext();
|
||||
@@ -98,7 +84,7 @@ suite('template_maps', function() {
|
||||
testNext();
|
||||
});
|
||||
|
||||
test('does not accept template with invalid placeholder name', function(done) {
|
||||
it('does not accept template with invalid placeholder name', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
@@ -115,8 +101,7 @@ suite('template_maps', function() {
|
||||
done(new Error("Unexpected success with invalid name '" + n + "'"));
|
||||
}
|
||||
else if ( ! err.message.match(/invalid.*name/i) ) {
|
||||
done(new Error("Unexpected error message with invalid name '" + n
|
||||
+ "': " + err));
|
||||
done(new Error("Unexpected error message with invalid name '" + n + "': " + err));
|
||||
}
|
||||
else {
|
||||
testNext();
|
||||
@@ -126,7 +111,7 @@ suite('template_maps', function() {
|
||||
testNext();
|
||||
});
|
||||
|
||||
test('does not accept template with missing placeholder default', function(done) {
|
||||
it('does not accept template with missing placeholder default', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
@@ -137,8 +122,7 @@ suite('template_maps', function() {
|
||||
done(new Error("Unexpected success with missing placeholder default"));
|
||||
}
|
||||
else if ( ! err.message.match(/missing default/i) ) {
|
||||
done(new Error("Unexpected error message with missing placeholder default: "
|
||||
+ err));
|
||||
done(new Error("Unexpected error message with missing placeholder default: " + err));
|
||||
}
|
||||
else {
|
||||
done();
|
||||
@@ -146,7 +130,7 @@ suite('template_maps', function() {
|
||||
});
|
||||
});
|
||||
|
||||
test('does not accept template with missing placeholder type', function(done) {
|
||||
it('does not accept template with missing placeholder type', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var tpl = { version:'0.0.1',
|
||||
@@ -157,8 +141,7 @@ suite('template_maps', function() {
|
||||
done(new Error("Unexpected success with missing placeholder type"));
|
||||
}
|
||||
else if ( ! err.message.match(/missing type/i) ) {
|
||||
done(new Error("Unexpected error message with missing placeholder default: "
|
||||
+ err));
|
||||
done(new Error("Unexpected error message with missing placeholder default: " + err));
|
||||
}
|
||||
else {
|
||||
done();
|
||||
@@ -167,7 +150,7 @@ suite('template_maps', function() {
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft-cartodb/issues/128
|
||||
test('does not accept template with invalid token auth (undefined tokens)',
|
||||
it('does not accept template with invalid token auth (undefined tokens)',
|
||||
function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
@@ -179,8 +162,7 @@ suite('template_maps', function() {
|
||||
done(new Error("Unexpected success with invalid token auth (undefined tokens)"));
|
||||
}
|
||||
else if ( ! err.message.match(/invalid 'token' authentication/i) ) {
|
||||
done(new Error("Unexpected error message with invalid token auth (undefined tokens): "
|
||||
+ err));
|
||||
done(new Error("Unexpected error message with invalid token auth (undefined tokens): " + err));
|
||||
}
|
||||
else {
|
||||
done();
|
||||
@@ -188,14 +170,14 @@ suite('template_maps', function() {
|
||||
});
|
||||
});
|
||||
|
||||
test('add, get and delete a valid template', function(done) {
|
||||
it('add, get and delete a valid template', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
var tpl_id;
|
||||
var tpl = { version:'0.0.1',
|
||||
name: 'first', auth: {}, layergroup: {layers:[wadusLayer]} };
|
||||
Step(
|
||||
step(
|
||||
function() {
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
@@ -224,15 +206,14 @@ suite('template_maps', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('add multiple templates, list them', function(done) {
|
||||
it('add multiple templates, list them', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
var tpl1 = { version:'0.0.1', name: 'first', auth: {}, layergroup: {layers:[wadusLayer]} };
|
||||
var tpl1_id;
|
||||
var tpl2 = { version:'0.0.1', name: 'second', auth: {}, layergroup: {layers:[wadusLayer]} };
|
||||
var tpl2_id;
|
||||
Step(
|
||||
step(
|
||||
function addTemplate1() {
|
||||
tmap.addTemplate('me', tpl1, this);
|
||||
},
|
||||
@@ -283,7 +264,7 @@ suite('template_maps', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('update templates', function(done) {
|
||||
it('update templates', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
var expected_failure = false;
|
||||
@@ -294,7 +275,7 @@ suite('template_maps', function() {
|
||||
layergroup: {layers:[wadusLayer]}
|
||||
};
|
||||
var tpl_id;
|
||||
Step(
|
||||
step(
|
||||
function addTemplate() {
|
||||
tmap.addTemplate(owner, tpl, this);
|
||||
},
|
||||
@@ -323,7 +304,6 @@ suite('template_maps', function() {
|
||||
},
|
||||
function updateUnexistentTemplate(err) {
|
||||
if ( err && ! expected_failure) throw err;
|
||||
expected_failure = false;
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/unsupported.*version/i), err);
|
||||
tpl.version = '0.0.1';
|
||||
@@ -343,7 +323,7 @@ suite('template_maps', function() {
|
||||
);
|
||||
});
|
||||
|
||||
test('instanciate templates', function() {
|
||||
it('instanciate templates', function() {
|
||||
var tmap = new TemplateMaps(redis_pool);
|
||||
assert.ok(tmap);
|
||||
|
||||
@@ -356,7 +336,7 @@ suite('template_maps', function() {
|
||||
color: { type: "css_color", default: "#a0fF9A" },
|
||||
name: { type: "sql_literal", default: "test" },
|
||||
zoom: { type: "number", default: "0" },
|
||||
test_number: { type: "number", default: 23 },
|
||||
test_number: { type: "number", default: 23 }
|
||||
},
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
@@ -400,48 +380,48 @@ suite('template_maps', function() {
|
||||
|
||||
// Invalid css_color
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'##ff00ff'}); }
|
||||
try { tmap.instance(tpl1, {color:'##ff00ff'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid css_color 2 (too few digits)
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'#ff'}); }
|
||||
err = null;
|
||||
try { tmap.instance(tpl1, {color:'#ff'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid css_color 3 (too many digits)
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {color:'#1234567'}); }
|
||||
err = null;
|
||||
try { tmap.instance(tpl1, {color:'#1234567'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid css_color/i), err);
|
||||
|
||||
// Invalid number
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'#'}); }
|
||||
err = null;
|
||||
try { tmap.instance(tpl1, {zoom:'#'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid number/i), err);
|
||||
|
||||
// Invalid number 2
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'23e'}); }
|
||||
err = null;
|
||||
try { tmap.instance(tpl1, {zoom:'23e'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(err);
|
||||
assert.ok(err.message.match(/invalid number/i), err);
|
||||
|
||||
// Valid number
|
||||
var err = null;
|
||||
try { inst = tmap.instance(tpl1, {zoom:'-.23e10'}); }
|
||||
err = null;
|
||||
try { tmap.instance(tpl1, {zoom:'-.23e10'}); }
|
||||
catch (e) { err = e; }
|
||||
assert.ok(!err);
|
||||
});
|
||||
|
||||
// Can set a limit on the number of user templates
|
||||
test('can limit number of user templates', function(done) {
|
||||
it('can limit number of user templates', function(done) {
|
||||
var tmap = new TemplateMaps(redis_pool, {
|
||||
max_user_templates: 2
|
||||
});
|
||||
@@ -450,7 +430,7 @@ suite('template_maps', function() {
|
||||
var expectErr = false;
|
||||
var idMe = [];
|
||||
var idYou = [];
|
||||
Step(
|
||||
step(
|
||||
function oneForMe() {
|
||||
tpl.name = 'oneForMe';
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
@@ -470,7 +450,7 @@ suite('template_maps', function() {
|
||||
expectErr = true;
|
||||
tmap.addTemplate('me', tpl, this);
|
||||
},
|
||||
function errForMe(err, id) {
|
||||
function errForMe(err/*, id*/) {
|
||||
if ( err && ! expectErr ) throw err;
|
||||
expectErr = false;
|
||||
assert.ok(err);
|
||||
@@ -508,7 +488,7 @@ suite('template_maps', function() {
|
||||
expectErr = true;
|
||||
tmap.addTemplate('you', tpl, this);
|
||||
},
|
||||
function errForYou(err, id) {
|
||||
function errForYou(err/*, id*/) {
|
||||
if ( err && ! expectErr ) throw err;
|
||||
expectErr = false;
|
||||
assert.ok(err);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../../lib/cartodb/template_maps');
|
||||
var test_helper = require('../../support/test_helper');
|
||||
var Step = require('step');
|
||||
var tests = module.exports = {};
|
||||
|
||||
suite('template_maps_auth', function() {
|
||||
var TemplateMaps = require('../../../lib/cartodb/template_maps');
|
||||
|
||||
describe('template_maps_auth', function() {
|
||||
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = new RedisPool(global.environment.redis),
|
||||
@@ -84,15 +84,15 @@ suite('template_maps_auth', function() {
|
||||
];
|
||||
|
||||
authorizationTestScenarios.forEach(function(testScenario) {
|
||||
test(testScenario.desc, function(done) {
|
||||
it(testScenario.desc, function(done) {
|
||||
var debugMessage = testScenario.expected ? 'should be authorized' : 'unexpectedly authorized';
|
||||
var result = templateMaps.isAuthorized(testScenario.template, testScenario.token);
|
||||
assert.equal(result, testScenario.expected, debugMessage);
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
test("auth as 'open' string is authorized", function(done) {
|
||||
it("auth as 'open' string is authorized", function(done) {
|
||||
var template = {
|
||||
name: 'wadus_template',
|
||||
auth: 'open'
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
|
||||
var test_helper = require('../../support/test_helper');
|
||||
var _ = require('underscore');
|
||||
|
||||
suite('template_maps', function() {
|
||||
describe('template_maps', function() {
|
||||
|
||||
var redisPool = new RedisPool(global.environment.redis),
|
||||
templateMaps = new TemplateMaps(redisPool);
|
||||
@@ -88,7 +89,7 @@ suite('template_maps', function() {
|
||||
];
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
test('adding template returns a new instance with ' + testScenario.desc, function(done) {
|
||||
it('adding template returns a new instance with ' + testScenario.desc, function(done) {
|
||||
|
||||
templateMaps.addTemplate(owner, testScenario.template, function(err, templateId, template) {
|
||||
assert.ok(!err, 'Unexpected error adding template: ' + (err && err.message));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../../lib/cartodb/template_maps.js');
|
||||
var test_helper = require('../../support/test_helper');
|
||||
var _ = require('underscore');
|
||||
|
||||
suite('template_maps', function() {
|
||||
describe('template_maps', function() {
|
||||
|
||||
var redisPool = new RedisPool(global.environment.redis),
|
||||
templateMaps = new TemplateMaps(redisPool);
|
||||
@@ -87,7 +88,7 @@ suite('template_maps', function() {
|
||||
];
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
test(testScenario.desc, function(done) {
|
||||
it(testScenario.desc, function(done) {
|
||||
|
||||
templateMaps.addTemplate(owner, testScenario.template, function(err) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user