Compare commits

...

1299 Commits

Author SHA1 Message Date
Raul Ochoa
778860c81f Release 2.39.0 2016-05-05 18:09:44 +02:00
Raul Ochoa
ee3c56efba Update news 2016-05-05 18:09:15 +02:00
Raul Ochoa
5dc328724a Merge pull request #440 from CartoDB/node-status-cache-control-header
Use a more aggressive cache control header for node status endpoint
2016-05-05 18:07:16 +02:00
Raul Ochoa
c77ea49594 Use a more aggressive cache control header for node status endpoint 2016-05-05 17:52:37 +02:00
Raul Ochoa
73aa159b98 Upgrade istanbul 2016-05-05 16:06:52 +02:00
Raul Ochoa
b7d7cffb67 Reformat package.json with npm cli tool 2016-05-05 16:05:46 +02:00
Raul Ochoa
eba8db292c Merge pull request #439 from CartoDB/avoid-profiler-dots
Upgrades step-profiler to 0.3.0 to avoid dots in json keys
2016-05-05 15:41:30 +02:00
Raul Ochoa
d5391ef15b Merge remote-tracking branch 'origin/master' into avoid-profiler-dots
Conflicts:
	NEWS.md
2016-05-05 15:30:12 +02:00
Francisco Dans
685cbb1eec updates news 2016-05-05 14:34:19 +02:00
Francisco Dans
5842a239fd shrinkwrap 2016-05-05 14:32:57 +02:00
Francisco Dans
285363aa40 bumps current version and turbocarto's 2016-05-05 14:32:11 +02:00
Raul Ochoa
92e130d8de Upgrades step-profiler to 0.3.0 to avoid dots in json keys
Closes #438
2016-05-05 13:45:25 +02:00
Francisco Dans
f674f90eba Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-05-05 12:28:01 +02:00
Francisco Dans
0542b65cbb shrinkwrap changes for 2.38.0 2016-05-05 12:26:12 +02:00
Francisco Dans
60fa94781e adds upgrade to NEWS 2016-05-05 12:25:12 +02:00
Raul Ochoa
38d57533c2 Discourage console usage, global.logger should be used when required 2016-05-05 12:18:22 +02:00
Raul Ochoa
e3d6da06a7 Remove console usage 2016-05-05 12:17:51 +02:00
Raul Ochoa
3af05bb734 Remove console usage 2016-05-05 12:17:33 +02:00
Francisco Dans
ffd58cc7ca bumps minor 2016-05-05 12:07:26 +02:00
Francisco Dans
e103c52d27 upgrades turbo carto 2016-05-05 12:04:14 +02:00
Daniel García Aubert
00a71af95d Stubs next version 2016-05-03 17:58:47 +02:00
Daniel García Aubert
2fb8036740 Release 2.37.0 2016-05-03 17:53:08 +02:00
Daniel García Aubert
6902a8c9b3 Upgraded camashaft version to 0.6.0 2016-05-03 17:46:38 +02:00
Raul Ochoa
c19a345f6c Stubs next version 2016-04-29 12:54:47 +02:00
Raul Ochoa
bdc3ecff5a Release 2.36.1 2016-04-29 12:53:58 +02:00
Raul Ochoa
6c4ec29e18 Add test to validate new behaviour in camshaft 0.5.1 2016-04-29 12:51:48 +02:00
Raul Ochoa
412712e62e Upgrades camshaft to 0.5.1 2016-04-29 12:51:39 +02:00
Raul Ochoa
0edcb30f75 Stubs next version 2016-04-28 18:46:49 +02:00
Raul Ochoa
c0f49b3acb Release 2.36.0 2016-04-28 18:45:51 +02:00
Raul Ochoa
b926244085 Upgrades windshaft to 1.19.0 2016-04-28 17:30:57 +02:00
Raul Ochoa
d21136b475 Stubs next version 2016-04-27 18:44:47 +02:00
Raul Ochoa
8788aaaf25 Release 2.35.0 2016-04-27 18:43:50 +02:00
Raul Ochoa
120e800089 Bump version and update news 2016-04-27 18:42:56 +02:00
Raul Ochoa
92dc4b148c Merge pull request #432 from CartoDB/dataviews-columns
Append dataviews related columns to layers
2016-04-27 18:40:33 +02:00
Raul Ochoa
755dfe6822 Append dataviews related columns to layers 2016-04-27 18:30:05 +02:00
Daniel García Aubert
b787ee1033 Stubs next version 2016-04-27 15:12:13 +02:00
Daniel García Aubert
9ee3612da0 Release 2.34.1 2016-04-27 15:09:13 +02:00
Daniel García Aubert
fe0b0a9c50 Upgraded windshaft to 1.17.3 2016-04-27 15:05:45 +02:00
Raul Ochoa
dea19f20dd Stubs next version 2016-04-27 10:24:16 +02:00
Raul Ochoa
f9f70cc6e7 Release 2.34.0 2016-04-27 10:23:24 +02:00
Raul Ochoa
23ef1157cb Merge pull request #431 from CartoDB/dataviews-filters
Dataviews filters
2016-04-26 19:21:43 +02:00
Raul Ochoa
8ca4d537ce Update news 2016-04-26 19:18:04 +02:00
Raul Ochoa
98d5731555 Add test to validate latest windshaft uses dataviews filters
Windshaft is transforming dataview filters into widget filters
2016-04-26 19:13:30 +02:00
Raul Ochoa
5da8929a5d Upgrade windshaft 2016-04-26 19:13:10 +02:00
Raul Ochoa
e198bafac1 Merge pull request #429 from CartoDB/upgrade-turbo-carto
Starts using turbo-carto dependency
2016-04-26 16:56:11 +02:00
Raul Ochoa
dd731399dc Starts using turbo-carto dependency 2016-04-26 16:28:05 +02:00
csobier
66fd899ffb Merge pull request #426 from CartoDB/docs-801-misplaced-text
removed dup text from static api section
2016-04-24 14:16:40 -04:00
csobier
bca8a33417 removed dup text from static api section 2016-04-24 13:59:38 -04:00
Raul Ochoa
a11c8d882e Each error-case will have different expectations 2016-04-21 17:27:20 +02:00
Raul Ochoa
c5bed48d61 Handle missing analyses nodes for layers pointing to them
Fixes #422
2016-04-21 17:24:52 +02:00
Raul Ochoa
98b7d12796 Merge pull request #424 from CartoDB/analysis-named-metadata
Analysis named maps metadata
2016-04-21 17:10:36 +02:00
Raul Ochoa
93dd8a2213 Add analyses metadata for named maps excluding queries 2016-04-21 17:03:41 +02:00
Raul Ochoa
4e4a223f24 Better naming for analysis mapconfig adapter 2016-04-21 16:25:59 +02:00
Raul Ochoa
4a73f3874d Better naming 2016-04-21 16:18:17 +02:00
Raul Ochoa
bc845b2e8d Validate dataviews format before instantiating 2016-04-21 16:16:00 +02:00
Raul Ochoa
83eceb349c Merge pull request #423 from CartoDB/multierror-support
Adds support to return multiple errors in BaseController.sendError
2016-04-21 16:15:28 +02:00
Raul Ochoa
bb518f0744 Update news 2016-04-21 16:09:57 +02:00
Raul Ochoa
08ad961123 Adds support to return multiple errors in BaseController.sendError 2016-04-21 16:05:48 +02:00
Raul Ochoa
146d494cae Adds dataview example in named map 2016-04-21 15:35:45 +02:00
Javier Goizueta
bb40ecf7c2 Stub next version 2016-04-20 18:30:42 +02:00
Javier Goizueta
7de9f64f2a Release 2.33.1 2016-04-20 18:27:06 +02:00
Javier Goizueta
0bb6178d49 Merge pull request #421 from CartoDB/420-overviews-schema
Support unneeded schema names in overviews queries
2016-04-20 18:16:16 +02:00
Javier Goizueta
084b3e94a6 Remove unneeded variable 2016-04-20 18:01:34 +02:00
Javier Goizueta
a0445b5cdd 💄 Fix indentation
2 spaces were used instead of 4 in some places
2016-04-20 17:47:43 +02:00
Javier Goizueta
1d4ddd373b Remove unneeded callback from synchronous tests 2016-04-20 17:27:52 +02:00
Raul Ochoa
92795963ae Stubs next version 2016-04-20 17:26:30 +02:00
Raul Ochoa
e634ad6f7d Release 2.33.0 2016-04-20 17:24:17 +02:00
Javier Goizueta
ecbae52abe Refactor: use reduce for collecting overviews metadata 2016-04-20 17:24:16 +02:00
Raul Ochoa
e1c5af8602 Bump version and update news 2016-04-20 17:15:17 +02:00
Raul Ochoa
6704a94f36 Merge pull request #418 from CartoDB/analysis-layers
Analysis layers
2016-04-20 17:13:14 +02:00
Raul Ochoa
a35403cd91 Add option to modify host header template for camshaft batch client 2016-04-20 16:36:29 +02:00
Raul Ochoa
41ead9664b Upgrade camshaft to 0.5.0 2016-04-20 16:34:48 +02:00
Raul Ochoa
e04a9a2579 Append dataviews filters after checking if mapconfig must be adapted 2016-04-20 15:40:14 +02:00
Raul Ochoa
d70af7c9c1 Fix tests with typo in s/radio/radius/ 2016-04-20 15:33:17 +02:00
Raul Ochoa
d910f5ef3e Update camshaft to use 0.4.0 fixed version 2016-04-20 15:33:04 +02:00
Javier Goizueta
57cba3d511 Fix comment 2016-04-20 14:30:13 +02:00
Javier Goizueta
7902b276ad Support unneeded schema names in overviews queries
Fixes #420
Keep table schema of overviews base tables and use it
to support queries that use the schema name when not
strictly needed.
2016-04-19 22:50:05 +02:00
Raul Ochoa
f932862ce4 Improve configuration for batch queries 2016-04-18 15:13:00 +02:00
Raul Ochoa
ab55b083b4 Style 2016-04-18 14:48:14 +02:00
Raul Ochoa
263b3e3682 Rename file 2016-04-18 14:47:35 +02:00
Raul Ochoa
5baad96924 remove commented out code 2016-04-18 14:43:29 +02:00
Raul Ochoa
68b19c65fe newline at end of file 2016-04-18 14:40:34 +02:00
Raul Ochoa
3a81302699 Merge branch 'master' into analysis-layers
Conflicts:
	package.json
2016-04-18 14:36:43 +02:00
Raul Ochoa
6b942ecf0b Merge pull request #417 from CartoDB/update-cartodb-psql
Upgrades cartodb-psql to 0.6.1 version
2016-04-18 14:33:59 +02:00
Raul Ochoa
1ed0c73525 Upgrades cartodb-psql to 0.6.1 version. 2016-04-18 14:30:20 +02:00
Raul Ochoa
7175b2b05e Merge branch 'master' into analysis-layers 2016-04-18 14:09:57 +02:00
Raul Ochoa
61137610c7 Merge pull request #416 from CartoDB/update-windshaft
Upgrades windshaft to 1.17.1
2016-04-18 13:53:57 +02:00
Raul Ochoa
b53031972e Upgrades windshaft to 1.17.1 2016-04-18 13:18:54 +02:00
Raul Ochoa
da602eeda0 Use inline execution in camshaft instead of a database service stub 2016-04-14 17:25:08 +02:00
Raul Ochoa
a26025b259 Add analysis backend so it's possible to inject configuration 2016-04-14 17:09:07 +02:00
Raul Ochoa
25a61c8479 Remove console log 2016-04-14 17:08:23 +02:00
Raul Ochoa
e73b64bfed Merge branch 'master' into analysis-layers 2016-04-14 13:40:51 +02:00
Raul Ochoa
b5b8085444 Reformat for better indentation 2016-04-14 13:40:02 +02:00
Raul Ochoa
388f08a277 Remove unneeded comma 2016-04-14 13:39:19 +02:00
Raul Ochoa
aa36236ed2 Rename analysis to analysis status backend, making room for analysis backend 2016-04-14 13:25:56 +02:00
Raul Ochoa
a9ca453b17 Remove some JSON.stringify 2016-04-14 13:20:22 +02:00
Raul Ochoa
9ab4eb5801 Change error expectation 2016-04-14 12:56:20 +02:00
Raul Ochoa
26149e7755 cdb_analysis_catalog is already retrieved from camshaft 2016-04-14 11:53:48 +02:00
Raul Ochoa
2d4fd62acf Do not create triggers for tests 2016-04-14 11:44:17 +02:00
Raul Ochoa
e037c8c1b2 Do not use layer index as analysis node is are unique 2016-04-14 11:08:39 +02:00
Raul Ochoa
09687b3811 Proper endpoint to check node status from analysis 2016-04-14 10:59:51 +02:00
Raul Ochoa
28400f4544 Better naming 2016-04-13 19:15:06 +02:00
Raul Ochoa
7a87b8ebef Use node id instead of param id for endpoint
It will be easier to retrieve node status with that id
2016-04-13 18:04:49 +02:00
Raul Ochoa
9ff661480b Do not append root as it is already included in sorted nodes 2016-04-13 18:02:28 +02:00
Raul Ochoa
ca564bfaad Add fake analysis node status endpoint 2016-04-11 18:49:56 +02:00
Raul Ochoa
dd36877a20 Add per node status 2016-04-11 18:49:43 +02:00
Raul Ochoa
1d860fd202 Remove top level url for analysis 2016-04-11 18:49:22 +02:00
Raul Ochoa
e9111ec3bb Update routes document 2016-04-08 11:13:40 +02:00
Raul Ochoa
0981ccd0c4 Add metadata information about analyses 2016-04-07 17:58:12 +02:00
Raul Ochoa
077c4ab907 Adds analysis MapConfig adapter to named maps 2016-04-07 16:18:48 +02:00
Raul Ochoa
efacafaa0d Merge remote-tracking branch 'origin/master' into analysis-layers 2016-04-07 15:04:25 +02:00
Raul Ochoa
1c250bf243 Remove dependency 2016-04-07 14:30:49 +02:00
Daniel García Aubert
c974b91c6b Stubs next version 2016-04-06 19:32:16 +02:00
Daniel García Aubert
6aebe26cc9 Release 2.32.0 2016-04-06 19:29:07 +02:00
Daniel García Aubert
f93794717e Upgrades windshaft to 1.17.0 2016-04-06 19:26:55 +02:00
Raul Ochoa
0ebf482936 Merge pull request #410 from CartoDB/named-dynamic-styling
Overrided cartocss in the instantiation of named maps
2016-04-06 18:09:11 +02:00
Daniel García Aubert
b5b8083acd Overrided cartocss in the instantiation of named maps 2016-04-06 17:43:25 +02:00
Raul Ochoa
ab6bae6a7f Merge branch 'master' into analysis-layers 2016-04-04 16:24:31 +02:00
Javier Goizueta
219658761f Stub next version 2016-04-04 14:34:35 +02:00
Javier Goizueta
e3a68a6b4d Release 2.31.2
This fixes a couple of overviews-related bugs
2016-04-04 14:31:57 +02:00
Javier Goizueta
01218c6ea1 Merge pull request #409 from CartoDB/405-wrapped-overviews-queries
Support overviews for named layer wrapped queries
2016-04-04 14:18:57 +02:00
Javier Goizueta
83d27a8e29 Merge pull request #407 from CartoDB/400-named-layer-overviews
Fix overviews integration for named layers
2016-04-04 14:18:34 +02:00
Javier Goizueta
fa2e884605 Support overviews for named layer wrapped queries
Fix #405
2016-04-01 15:40:44 +02:00
Javier Goizueta
f4554f41d2 Add tests for named maps overviews 2016-03-31 18:36:50 +02:00
Javier Goizueta
b97a67b844 Fix overviews integration for named layers
Fixes #400
2016-03-31 18:33:41 +02:00
csobier
3b13fad5e7 Merge pull request #403 from CartoDB/docs-592-consistent-placeholder-examples
added tip about placeholder format and errors
2016-03-28 07:12:59 -04:00
Raul Ochoa
4d39d30f6e Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-23 16:09:28 +01:00
Raul Ochoa
9a8964bd39 Stubs next version 2016-03-23 15:14:52 +01:00
Raul Ochoa
aae251b178 Release 2.31.1 2016-03-23 15:09:38 +01:00
Raul Ochoa
2db7a3d110 Merge pull request #404 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.16.1
2016-03-23 15:08:23 +01:00
Raul Ochoa
5fe96a618d Upgrade windshaft to 1.16.1 2016-03-23 15:01:30 +01:00
Raul Ochoa
499178319d Add search endpoint/backend for dataviews 2016-03-23 12:14:17 +01:00
Raul Ochoa
7b7e9ffe59 Upgrade cartodb-psql module to get some bufixes 2016-03-23 12:13:26 +01:00
csobier
43d1e5c613 changed documentation variable to username throughout, and other clean-up 2016-03-22 11:23:04 -04:00
csobier
8547cb836f consistent placeholder examples with brackets 2016-03-22 09:19:50 -04:00
Raul Ochoa
2bd3e46a4d Build dataviews with factory to generalise them 2016-03-22 13:10:42 +01:00
Raul Ochoa
e44b5eaccd Fix test 2016-03-22 13:10:37 +01:00
Raul Ochoa
26512f6485 Remove unused function 2016-03-22 12:22:48 +01:00
Raul Ochoa
90b92f0180 Adds support for category filters 2016-03-22 12:22:04 +01:00
Raul Ochoa
ebe25761d2 Extract variable 2016-03-22 10:52:02 +01:00
Raul Ochoa
f928147559 Fix bbox template 2016-03-21 18:16:54 +01:00
Raul Ochoa
d5c5c7bdbb Do not remove analysis, camshaft takes care of source root nodes now 2016-03-21 18:02:19 +01:00
Raul Ochoa
ff147ca3bf Add dataviews to layergroup metadata 2016-03-18 18:09:17 +01:00
Raul Ochoa
f745e915d3 Own filter test for dataviews 2016-03-18 17:49:20 +01:00
Raul Ochoa
1e239658d8 Just remove analysis if there are analysis 2016-03-18 17:34:40 +01:00
Raul Ochoa
52f35d74b9 Allow a higher jshint maxcomplexity 2016-03-18 17:31:28 +01:00
Raul Ochoa
57e6e49749 Another workaround to not delete analyses if there are dataviews 2016-03-18 17:28:36 +01:00
Raul Ochoa
b3bbb9d97a Initial checkin for dataviews
It only supports histograms.
2016-03-18 17:22:02 +01:00
Raul Ochoa
697749b204 Add timer helper 2016-03-18 17:21:43 +01:00
Raul Ochoa
a769545e39 Rely on camshaft.reference for now 2016-03-18 17:16:51 +01:00
Raul Ochoa
df40302fd0 Add camshaft-reference 2016-03-18 13:04:00 +01:00
csobier
85e5a89298 added tip about placeholder format and errors 2016-03-17 17:42:57 -04:00
Raul Ochoa
5bd30b6b5f Analysis layers adapter skips analysis if there is only source nodes 2016-03-17 12:50:42 +01:00
Raul Ochoa
ed84ed8475 Test client detect png request based on regex 2016-03-17 12:45:05 +01:00
Raul Ochoa
a444b80c96 Fix typo 2016-03-17 12:44:51 +01:00
Daniel García Aubert
fc94f3ad94 Fixed stub next version 2016-03-16 18:07:28 +01:00
Daniel García Aubert
d43b766448 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-03-16 17:40:49 +01:00
Daniel García Aubert
6ceca348cb Stubs next version 2016-03-16 17:40:30 +01:00
Daniel García Aubert
bff6f056d0 Stubs next version 2016-03-16 17:33:26 +01:00
Daniel García Aubert
366a66b331 Release 2.31.0 2016-03-16 17:30:58 +01:00
Raul Ochoa
43c56af0fc Merge remote-tracking branch 'origin/master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-16 16:13:44 +01:00
Daniel García Aubert
c0370c5703 Stubs next version 2016-03-15 17:45:50 +01:00
Daniel García Aubert
8d5551343b Release 2.30.0 2016-03-15 17:40:26 +01:00
Daniel García Aubert
4089e8537a Upgraded windshaft to version 1.15.0 2016-03-15 17:35:13 +01:00
csobier
cf06c6e974 Merge pull request #401 from CartoDB/docs-685-named-map-tiles-and-cleanup
moved fetch tiles section up one heading level
2016-03-15 09:16:40 -04:00
csobier
2ebab4c89e moved fetch tiles section up one heading level 2016-03-15 09:08:28 -04:00
csobier
8023fbae24 Merge pull request #379 from CartoDB/docs-685-named-map-tiles-and-cleanup
Docs 685 named map tiles and cleanup
2016-03-15 08:50:20 -04:00
csobier
35cb652c6c edited token placeholder for consistency 2016-03-14 13:01:10 -04:00
csobier
c6085779a4 use the layergroupid, not SRID, as the mapnik token 2016-03-14 12:25:59 -04:00
csobier
86232eb239 clarified mapnik description and removed unnecessary content, as instructed by Carla 2016-03-14 11:51:07 -04:00
Raul Ochoa
6db48a24b8 Adds test for analysis with no api key 2016-03-14 16:42:51 +01:00
csobier
5cc3e914fa added missing bracket, removed note about API key 2016-03-14 11:34:52 -04:00
Raul Ochoa
1da937d639 Add commented code to generate image output for validation 2016-03-14 16:19:55 +01:00
Raul Ochoa
4924bcc298 Validate image from analysis 2016-03-14 16:16:27 +01:00
Raul Ochoa
a05f3d6ee9 Add cdb_analysis_catalog table and first test using source:id 2016-03-14 16:06:25 +01:00
Daniel García Aubert
d52d3d909f Fixed double-check error in turbo-cartocss preprocesing. 2016-03-14 15:25:56 +01:00
Raul Ochoa
eec44dd62d Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
	test/support/test-client.js
2016-03-14 15:13:19 +01:00
Daniel García Aubert
e7da9a151b Stubs next version 2016-03-14 14:47:02 +01:00
Daniel García Aubert
f75ba9d2c3 Release 2.29.0 2016-03-14 14:42:40 +01:00
Daniel García Aubert
be61d41f5e Stubs next version 2016-03-14 12:12:56 +01:00
Daniel García Aubert
f619a97f1a Release 2.28.0 2016-03-14 12:06:33 +01:00
Raul Ochoa
3f41f19ab9 Rename adapter 2016-03-14 11:50:52 +01:00
Daniel
e47449e357 Merge pull request #399 from CartoDB/move-turbo-cartocss
Move turbo cartocss
2016-03-14 11:35:06 +01:00
Daniel García Aubert
178345ab12 Fixed typo 2016-03-14 11:18:32 +01:00
Daniel García Aubert
a8340fef68 Bump image tolerance in turbo-cartocss test 2016-03-11 18:33:52 +01:00
Daniel García Aubert
052b58ab90 Moved turbo-cartocss integration from named maps admin to named map provider 2016-03-11 18:28:14 +01:00
Daniel García Aubert
cc5443152b Now turbo-cartocss is also parsed in template modification. 2016-03-11 11:06:51 +01:00
Daniel García Aubert
d937d8970d Fixed broken test in turbo-cartocss for named maps 2016-03-10 21:25:01 +01:00
Daniel García Aubert
dab4b6d56b Implemented integration of turbo-cartocss for named maps 2016-03-10 20:45:00 +01:00
Raul Ochoa
1535820d4f Regenerate npm-shrinkwrap.json 2016-03-10 11:32:43 +01:00
Raul Ochoa
b2378939c5 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb into analysis-layers 2016-03-10 11:14:10 +01:00
Daniel García Aubert
46b212b2cd Merge branch 'move-turbo-cartocss' of github.com:CartoDB/Windshaft-cartodb into move-turbo-cartocss 2016-03-09 20:14:06 +01:00
Daniel García Aubert
f47842c96d Integrated turbo-cartocss adapter for named maps 2016-03-09 20:12:51 +01:00
Raul Ochoa
050f90a07b Regenerate npm-shrinkwrap.json 2016-03-09 18:11:34 +01:00
Raul Ochoa
c1642dfa73 Merge branch 'master' into move-turbo-cartocss
Conflicts:
	lib/cartodb/controllers/map.js
2016-03-09 18:08:46 +01:00
Raul Ochoa
bbfcc640d1 Style 2016-03-09 18:08:06 +01:00
Raul Ochoa
e9b8c512c9 Re-indent 2016-03-09 18:07:11 +01:00
Raul Ochoa
15b54a2918 Re-indent 2016-03-09 18:05:17 +01:00
Alejandro Martínez
cf8ce42049 Release 2.27.0 2016-03-09 17:57:18 +01:00
Raul Ochoa
7fa8d1e0c9 Analyses are now an array and layers consume from their nodes
Layers now can define a `source: {id: 'a0'}` option to point to an
analysis node that will be used as the query for that layer.
2016-03-09 17:39:20 +01:00
Daniel García Aubert
eefa9f4222 Merge branch 'master' into move-turbo-cartocss 2016-03-09 12:03:29 +01:00
Daniel García Aubert
a0073da4b3 Added regression test for turbo-cartocss' integration 2016-03-09 11:48:07 +01:00
Daniel García Aubert
c6fbb08c8f Regenerated npm-shrinkwrap 2016-03-09 10:36:57 +01:00
Daniel García Aubert
affa254b9d Moved and adapted acceptance test for turbo-cartocss integration 2016-03-08 20:06:43 +01:00
Raul Ochoa
d5fbcfcecb Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
2016-03-08 16:06:01 +01:00
Raul Ochoa
ecd33e5561 TestClient with method to retrieve tiles 2016-03-08 15:56:08 +01:00
Raul Ochoa
9d77aea6be Regenerate npm-shrinkwrap.json 2016-03-08 15:23:56 +01:00
Raul Ochoa
20609bc37e Merge pull request #397 from CartoDB/remove-deprecated-tools
Remove deprecated tools directory
2016-03-08 15:14:06 +01:00
Raul Ochoa
b56d110f50 Remove deprecated tools directory 2016-03-08 15:07:37 +01:00
Daniel García Aubert
3e0c19a669 Fixed typo 2016-03-08 14:41:10 +01:00
Daniel García Aubert
ab6004f21e Integrated turbo-cartocss for anonymous maps 2016-03-08 14:34:57 +01:00
Alejandro Martínez
34863765ed Freeze cartodb-query-tables version 2016-03-08 10:05:49 +00:00
csobier
32223baaef removed internal code, displayed somexyz examples 2016-03-07 10:53:51 -05:00
Raul Ochoa
3cb007d147 Merge pull request #388 from CartoDB/new_querytables_library
Use new querytables library
2016-03-07 16:42:25 +01:00
csobier
2f038f006b applied consistent terminology and applied rest of Carla's edits 2016-03-07 09:08:38 -05:00
Raul Ochoa
634a4c2a01 Debug option for internal nodes: it allows to display and customize cartocss 2016-03-04 16:20:23 +01:00
Raul Ochoa
f504807812 Regenerate npm-shrinkwrap.json 2016-03-04 12:12:12 +01:00
Raul Ochoa
c7bdabfc65 Merge branch 'new_querytables_library' into analysis-layers 2016-03-04 12:08:57 +01:00
Raul Ochoa
16a7c4fa3d Merge branch 'master' into new_querytables_library
Conflicts:
	test/support/prepare_db.sh
	test/support/sql/CDB_QueryStatements.sql
2016-03-04 00:39:14 +01:00
Raul Ochoa
3979cda8c2 Change comment about regex 2016-03-04 00:11:06 +01:00
Raul Ochoa
250d52f72c Merge pull request #395 from CartoDB/no-plpythonu-pg-dependency
No plpythonu pg dependency
2016-03-03 23:58:18 +01:00
Raul Ochoa
26e5b4f404 Do not install postgresql-plpython-9.3 in travis, so no more sudo:true 2016-03-03 20:51:53 +01:00
Raul Ochoa
f19c1a34ec Implement CDB_QueryStatements as SQL language function
Do not relies on cartodb-postgresql extension which relies on
plpythonu language. That avoid installing it in travis-ci.
2016-03-03 20:48:37 +01:00
Raul Ochoa
94c7bc41be Merge branch 'master' into new_querytables_library 2016-03-03 19:30:51 +01:00
Raul Ochoa
df0597f12a Rename suite 2016-03-03 19:29:42 +01:00
Raul Ochoa
52cb224225 Add integration test with QueryTables module
This tests should be better placed at cartodb-query-tables repo but
it's easier to do it here. Lazy dev.
2016-03-03 19:27:44 +01:00
Raul Ochoa
baf87e90d7 Just callback as result is handled internally 2016-03-03 19:25:32 +01:00
Javier Goizueta
6c3fde70e8 Stub next version 2016-03-03 19:05:06 +01:00
Raul Ochoa
d9f6df9815 Remove nested step call 2016-03-03 19:01:58 +01:00
Javier Goizueta
b2539f52b8 Release 2.26.3 2016-03-03 19:01:40 +01:00
Raul Ochoa
7c154dd405 Add notes about why we keep feeding the layergroupAffectedTables cache 2016-03-03 19:01:21 +01:00
Javier Goizueta
47dfdf964e Merge pull request #390 from CartoDB/overviews-optimization
Optimize overviews queries
2016-03-03 18:59:24 +01:00
Raul Ochoa
10a602a4f3 Merge remote-tracking branch 'origin/master' into analysis-layers 2016-03-03 17:52:05 +01:00
Raul Ochoa
e3a5c52ebf Merge branch 'master' into analysis-layers 2016-03-03 17:51:46 +01:00
Raul Ochoa
66aea5e10f Merge pull request #393 from CartoDB/travis-pg-93
Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build
2016-03-03 17:39:28 +01:00
Raul Ochoa
e0d18e3c20 Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build 2016-03-03 17:20:22 +01:00
Raul Ochoa
4b79d06ae3 Back to pg 9.3 and postgresql-plpython-9.3 using sudo=true build 2016-03-03 16:20:20 +01:00
Raul Ochoa
f9c0e29db0 Dataviews separated from analysis
They are just another consumer of the analysis as layers are.
2016-03-03 12:07:05 +01:00
Raul Ochoa
e53d823b5a Fix total population column name for widget 2016-03-03 12:04:03 +01:00
Raul Ochoa
31dede5d06 Notes to make clear the total-population analysis 2016-03-03 12:01:49 +01:00
Raul Ochoa
69142964c6 fix trade-area params 2016-03-03 11:54:50 +01:00
Raul Ochoa
2eac808e18 Change analysis name so it's easier to understand 2016-03-03 11:45:37 +01:00
Javier Goizueta
4e40a61795 Change form of overviews queries so they can be optimized
The PostgreSQL planner wasn't applying the spatial filtering of
tile bounds to the queries efficiently.
2016-03-02 19:25:08 +01:00
Raul Ochoa
011b60eeab Change ids 2016-03-02 13:27:53 +01:00
Raul Ochoa
16654c016a Style 2016-03-02 13:27:45 +01:00
Raul Ochoa
9b9e6b13b7 Fix query table 2016-03-02 13:27:28 +01:00
Raul Ochoa
3b11525cfb Add analysis use cases that we need to support 2016-03-02 12:43:14 +01:00
Raul Ochoa
6823fd8b03 Better datasets for analysis 2016-03-02 12:43:00 +01:00
Raul Ochoa
ce032fcc96 Improve styling in analysis layers 2016-03-02 12:42:42 +01:00
Raul Ochoa
a44477dddc TestClient with method to retrieve tiles 2016-03-02 12:40:53 +01:00
Javier Goizueta
2a789b5a5b Merge pull request #386 from CartoDB/384-overviews-error-message
Change error messages when getting overviews metadata fails
2016-02-26 19:10:09 +01:00
Raul Ochoa
3709d1f1d5 Merge branch 'master' into analysis-layers 2016-02-25 11:46:41 +01:00
Raul Ochoa
1789993467 Stubs next version 2016-02-25 11:43:36 +01:00
Raul Ochoa
604b50ffb5 Release 2.26.2 2016-02-25 11:42:53 +01:00
Raul Ochoa
2818413c5a Update windshaft to 1.13.2 2016-02-25 11:42:28 +01:00
Raul Ochoa
e958f925d3 Merge branch 'master' into analysis-layers 2016-02-24 17:28:03 +01:00
Raul Ochoa
06164af17f Stubs next version 2016-02-24 17:23:15 +01:00
Raul Ochoa
6131c4a66a Release 2.26.1 2016-02-24 17:22:04 +01:00
Raul Ochoa
465dde7a51 Merge pull request #389 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.13.1
2016-02-24 17:20:58 +01:00
Raul Ochoa
7894acf830 Upgrade windshaft to 1.13.1 2016-02-24 17:16:30 +01:00
Raul Ochoa
d923b343fc Merge branch 'master' into analysis-layers 2016-02-24 10:56:19 +01:00
Raul Ochoa
86c6f6040d Stubs next version 2016-02-24 10:51:12 +01:00
Raul Ochoa
b79b2d4e7e Release 2.26.0 2016-02-24 10:49:45 +01:00
Raul Ochoa
b9d2e297b6 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb into analysis-layers 2016-02-24 10:35:28 +01:00
Raul Ochoa
f2778a3292 Merge pull request #387 from CartoDB/upgrade-windshaft
Upgrade windshaft and regenerate npm-shrinkwrap.json
2016-02-24 02:25:49 +01:00
Raul Ochoa
f6f9f203d2 Update news and bump version 2016-02-24 02:21:37 +01:00
Raul Ochoa
f6c519a9e7 Upgrade windshaft to 1.13.0 2016-02-24 02:18:55 +01:00
Alejandro Martínez
0036056c07 Reopen PR 2016-02-23 19:31:38 +01:00
Alejandro Martínez
dcf156ba21 Merge remote-tracking branch 'origin/master' into new_querytables_library 2016-02-23 19:20:10 +01:00
Javier Goizueta
f0a1e7a0e0 Simplify error passing 2016-02-23 18:15:14 +01:00
Raul Ochoa
b931178e59 Upgrade windshaft and regenerate npm-shrinkwrap.json 2016-02-23 17:06:34 +01:00
Javier Goizueta
21f3c8a387 Change error messages when getting overviews metadata fails
Remove the detail that the error occurred trying to get overviews
metadata from the error message. This should be less confusing
to the user.
2016-02-23 11:45:26 +01:00
Alejandro Martínez
e491c0b825 Rename node-cartodb-query-tables to cartodb-query-tables 2016-02-22 19:11:54 +01:00
Raul Ochoa
2ac2974414 Stubs next version 2016-02-22 18:02:29 +01:00
Raul Ochoa
ce8c21261f Release 2.25.2 2016-02-22 18:00:58 +01:00
Raul Ochoa
dd8340b400 Do not leak redis connections
Reuse redis client in afterEach and quit client in function
2016-02-22 17:51:53 +01:00
csobier
14f58e12bb moved response info from template.json section to the related Response section, and left the name description as part of the template.json description 2016-02-22 11:41:06 -05:00
Raul Ochoa
b93d33e065 Merge pull request #385 from CartoDB/test-no-imagemagick
Tests without imagemagick dep
2016-02-22 16:51:54 +01:00
Raul Ochoa
4c06c9ade4 Remove imagemagick reference from install instructions 2016-02-22 16:43:43 +01:00
Raul Ochoa
2393a611a8 dry 2016-02-22 16:41:55 +01:00
Raul Ochoa
495fdaf8ec Rename assert.imageEqualsFile 2016-02-22 16:36:06 +01:00
Raul Ochoa
da680ec2a8 Code re-org 2016-02-22 16:08:16 +01:00
Raul Ochoa
3cadf7f2a2 Make imagesAreSimilar private 2016-02-22 16:07:26 +01:00
Raul Ochoa
7c7bec6f31 Remove imagemagick reference 2016-02-22 16:05:12 +01:00
Raul Ochoa
0683f638ce Do not take optional name hint 2016-02-22 16:04:31 +01:00
Raul Ochoa
ae9daed43f Better naming for imageBuffersAreSimilar 2016-02-22 16:02:15 +01:00
Raul Ochoa
5301e748de Do not create intermediate files when there is no need 2016-02-22 16:00:30 +01:00
Raul Ochoa
37ae6b4fa0 Rely on mapnik.Image instead of compare from imagemagick 2016-02-22 15:38:29 +01:00
Raul Ochoa
6695e1128c Merge pull request #382 from CartoDB/widgets-urls-in-namedmaps
Widgets urls in namedmaps
2016-02-22 15:37:30 +01:00
Alejandro Martínez
37fcfe69c7 Merge remote-tracking branch 'origin/master' into new_querytables_library 2016-02-22 15:35:36 +01:00
Raul Ochoa
fb146f164c Use before/after to not alter global configuration 2016-02-22 15:31:01 +01:00
Alejandro Martínez
850f1cb7f4 Remove stray spaces 2016-02-22 15:28:14 +01:00
Alejandro Martínez
e67f7b0d0e Drop old QueryTablesApi 2016-02-22 15:26:06 +01:00
Alejandro Martínez
ba8e3d419e Fix package.json 2016-02-22 15:09:09 +01:00
Raul Ochoa
877425267e Correct URLs for widgets in named maps
Fixes #381
2016-02-22 15:06:39 +01:00
csobier
adefa8b819 applied Carla's edits to date 2016-02-22 09:01:04 -05:00
Raul Ochoa
36b7377662 URLs for widgets are broken in named maps 2016-02-22 15:00:06 +01:00
Alejandro Martínez
2d6ee93448 Delete query_tables_api.js, wrap shrinkwrap 2016-02-22 13:40:20 +00:00
Raul Ochoa
f78c6fbc63 Stubs next version 2016-02-22 13:08:43 +01:00
Raul Ochoa
62e8868e4b Release 2.25.1 2016-02-22 12:58:51 +01:00
Raul Ochoa
aed0e03f7d Merge pull request #380 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.11.1
2016-02-22 12:55:29 +01:00
Raul Ochoa
3b67efeab1 Update news 2016-02-22 12:51:09 +01:00
Raul Ochoa
dcfa38e29c Upgrade windshaft to 1.11.1 2016-02-22 12:46:51 +01:00
Alejandro Martínez
cf06ff86c2 Use node-cartodb-query-tables library 2016-02-22 11:40:25 +01:00
Raul Ochoa
1c567ec455 Add tests with named maps + private dataset + widgets 2016-02-19 17:48:55 +01:00
Raul Ochoa
842fa4dfd2 Create populated places private table for named maps + widgets 2016-02-19 17:48:19 +01:00
csobier
cfa2714dbb added fetch tile in for Maps API 2016-02-19 11:45:06 -05:00
Raul Ochoa
30f8234bd0 Use analysis configuration as per new camshaft api 2016-02-19 17:13:28 +01:00
Raul Ochoa
ae8e3f2ef8 Merge branch 'master' into analysis-layers 2016-02-19 15:45:56 +01:00
csobier
4d6b4b1755 added xyz tile info-first draft 2016-02-18 16:58:28 -05:00
csobier
0994571895 clean-up portion of Named Maps doc-WIP 2016-02-18 13:15:28 -05:00
Raul Ochoa
3161939de9 Stubs next version 2016-02-18 15:15:09 +01:00
Raul Ochoa
4d8b341b6f Release 2.25.0 2016-02-18 15:14:18 +01:00
Raul Ochoa
b7fff960a2 Ignore CDB_ sql files downloaded for tests 2016-02-18 14:52:08 +01:00
Raul Ochoa
b6273cfef3 Merge pull request #377 from CartoDB/upgrade-windshaft
Upgrade windshaft
2016-02-18 14:49:18 +01:00
Raul Ochoa
b3f62e1631 Update news and bump version 2016-02-18 14:27:44 +01:00
Raul Ochoa
65e539d4c8 Upgrade windshaft 2016-02-18 13:49:10 +01:00
Raul Ochoa
6d91172630 Remove console.log 2016-02-18 13:43:39 +01:00
Alejandro Martínez
e1732076fc Merge branch 'new_querytables' of github.com:CartoDB/Windshaft-cartodb into new_querytables 2016-02-17 15:37:06 +01:00
Alejandro Martínez
587f66c23d Sort cache channels and keys alphabetically 2016-02-17 15:36:26 +01:00
Raul Ochoa
3d0c0f34ad Use a set to compare surrogate keys, avoiding key order errors 2016-02-17 12:18:57 +01:00
Raul Ochoa
8d4ebc171b Use a set to compare surrogate keys, avoiding key order errors 2016-02-17 12:15:43 +01:00
Raul Ochoa
4d3c21f1bc Merge remote-tracking branch 'origin/new_querytables' into analysis-layers 2016-02-17 11:48:09 +01:00
Raul Ochoa
6ece30fa2c Ignore CDB_ sql files downloaded for tests 2016-02-17 11:47:27 +01:00
Raul Ochoa
dfcb3b6dc1 Merge branch 'new_querytables' into analysis-layers 2016-02-17 11:31:55 +01:00
Raul Ochoa
87b4a37f3f Move camshaft dep to avoid future conflicts while updating windshaft 2016-02-17 11:29:40 +01:00
Raul Ochoa
4db2c715a0 Merge branch 'master' into analysis-layers
Conflicts:
	npm-shrinkwrap.json
	package.json
2016-02-17 11:29:11 +01:00
csobier
76653a4417 Merge pull request #368 from CartoDB/docs-setParam-torque-note
add note regarding setParams and torque
2016-02-16 10:36:40 -05:00
Raul Ochoa
cd81a59418 Adds some notes about how to npm link windshaft for development 2016-02-15 16:53:31 +01:00
Alejandro Martínez
19596245b8 Fix long line 2016-02-15 16:21:13 +01:00
Alejandro Martínez
0e83420e24 Fix long line 2016-02-15 16:15:43 +01:00
Alejandro Martínez
119846b56b Fix specs 2016-02-15 16:04:13 +01:00
Raul Ochoa
121c146ef9 Stubs next version 2016-02-15 14:47:36 +01:00
Raul Ochoa
3ab94c7c91 Release 2.24.0 2016-02-15 14:46:43 +01:00
Raul Ochoa
a44ed65a0b Merge pull request #376 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.10.0
2016-02-15 13:29:02 +01:00
Raul Ochoa
d0f84b2440 windshaft@1.10.1 2016-02-15 13:22:35 +01:00
Alejandro Martínez
33ba629c6d Add publicuser creation to Travis config 2016-02-15 13:00:21 +01:00
Alejandro Martínez
9e7b288f44 Merge remote-tracking branch 'origin/master' into new_querytables 2016-02-15 11:47:29 +00:00
Raul Ochoa
fe5c6faff1 Upgrade windshaft to 1.10.0 2016-02-15 12:16:56 +01:00
Raul Ochoa
2f51ad9c3f Regenerate npm-shrinkwrap.json to include camshaft dep 2016-02-12 18:49:01 +01:00
Raul Ochoa
ed1f753690 Fix style 2016-02-12 18:45:46 +01:00
Raul Ochoa
bcf3ce71ef Adds experimental adapter to use queries based on camshaft analysis 2016-02-12 18:38:06 +01:00
Alejandro Martínez
a656285001 Run tests against master cartodb-postgresql 2016-02-12 17:25:57 +01:00
Raul Ochoa
39cb463fbd Fix jsdoc 2016-02-12 16:13:40 +01:00
Raul Ochoa
354c982ea0 Fix jsdoc 2016-02-12 16:12:02 +01:00
Raul Ochoa
f71d38fb79 Stubs next version 2016-02-12 16:06:20 +01:00
Alejandro Martínez
b7ff554209 Use new _Updated_At function and new names 2016-02-11 11:45:09 +01:00
Javier Goizueta
ba0cf1eddf Release 2.23.0 2016-02-10 13:07:12 +01:00
Javier Goizueta
b381e8bad7 Release 2.23.0 2016-02-10 13:05:15 +01:00
Javier Goizueta
700335062e Merge pull request #365 from CartoDB/overviews-work
Version 2.23.0 with overviews support
2016-02-10 12:58:17 +01:00
Javier Goizueta
cd2bc319d8 Fix: bad error message 2016-02-10 12:27:39 +01:00
Javier Goizueta
4f8534afb3 Fix: accept empty layers in the MapConfigOverviewsAdapter 2016-02-10 12:16:37 +01:00
Javier Goizueta
c5b7d400f5 Merge branch 'master' into overviews-work 2016-02-10 11:56:54 +01:00
Raul Ochoa
ef58d7bcbd Add test for empty layers mapconfig 2016-02-10 11:49:56 +01:00
Alejandro Martínez
95ab99be4d Use new CDB_QueryTablesUpdatedAt function 2016-02-09 19:06:34 +01:00
Javier Goizueta
bbb8841f5a Upgrade Windshaft to 1.9.0
This version supports the current (provisional) QueryRewriter interface
2016-02-09 17:31:10 +01:00
Javier Goizueta
5b50e784cd Merge branch 'master' into overviews-work 2016-02-09 17:14:08 +01:00
Daniel García Aubert
f0af107ffa Stubs next version 2016-02-08 18:36:00 +01:00
Daniel García Aubert
0606fca484 Release 2.22.0 2016-02-08 18:31:28 +01:00
Daniel García Aubert
e6812ef6c1 Upgrade Windshaft version to 1.8.3 2016-02-08 18:26:26 +01:00
Daniel García Aubert
260e5ec25f tubs next version 2016-02-05 15:09:32 +01:00
Daniel García Aubert
097f68f98c Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-02-05 15:05:15 +01:00
Daniel García Aubert
45d72b2bc6 Release 2.21.1 2016-02-05 15:03:34 +01:00
Raul Ochoa
82d4c20586 Merge pull request #371 from CartoDB/widgets-tests
Widgets tests
2016-02-05 14:58:07 +01:00
Raul Ochoa
03e3f7f13c Merge pull request #370 from CartoDB/fix-geojson-config
Fixed default geojson config
2016-02-05 14:53:43 +01:00
Raul Ochoa
b571b39b38 Aggregations in their own file 2016-02-05 13:32:38 +01:00
Raul Ochoa
f42d20f2c3 Histograms in their own file 2016-02-05 13:24:39 +01:00
Raul Ochoa
74cb876771 Move list to own file 2016-02-05 13:01:34 +01:00
Raul Ochoa
d78e01b7a4 Extract getWidget to TestClient 2016-02-05 12:59:33 +01:00
Raul Ochoa
73478ed0e9 Rename widgets tests file 2016-02-05 12:36:25 +01:00
Daniel García Aubert
887d71a9ad Fixed default geojson config 2016-02-05 12:35:03 +01:00
Javier Goizueta
56095926e0 Remove CartCSS handling from QueryRewriter
QueryRewriter doesn't require a style method anymore
2016-02-05 08:23:02 +01:00
Daniel García Aubert
13c3fbae70 Stubs next version 2016-02-04 19:35:23 +01:00
Daniel García Aubert
0b6845235a Release 2.21.0 2016-02-04 19:31:57 +01:00
Raul Ochoa
d2558197d2 Merge pull request #369 from CartoDB/geojson-renderer
Geojson renderer
2016-02-04 19:26:41 +01:00
Raul Ochoa
d005521aa4 Upgrade windshaft version 2016-02-04 19:06:54 +01:00
Daniel García Aubert
336aaa3840 Updated travis config in order to use npm@2 2016-02-04 17:24:02 +01:00
Daniel García Aubert
edbdd95f79 Upgrades npm version to 2.14.16 2016-02-04 17:09:30 +01:00
Daniel García Aubert
bb5a8fd0bf Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-02-04 17:02:14 +01:00
Daniel García Aubert
3284e709c3 Regenerate npm-shrinkwrap 2016-02-04 17:00:44 +01:00
Raul Ochoa
d33ae29211 Revert "Fixed missing map key for named layers"
This reverts commit a4041524a3.
2016-02-04 16:26:32 +01:00
Daniel García Aubert
da51a173d7 Upgrades windshaft to 1.8.1 2016-02-04 15:46:13 +01:00
Raul Ochoa
425ec83209 Use a valid SPDX license expression 2016-02-04 15:22:08 +01:00
Daniel García Aubert
971b77451d Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-02-04 12:46:52 +01:00
Daniel García Aubert
6407101709 Upgrade to Windshaft 1.8.0 2016-02-04 12:40:53 +01:00
Javier Goizueta
0a218da835 Implement an Overviews query rewriter
Use the Windshaft query-rewriter interface to adapt queries so
they use available overview tables.

This requires a version of Windshaft that implements the query-rewriter
interface (package.json/npm-shrinkwap.json have yet to be updated)
2016-02-04 10:26:31 +01:00
Daniel
89033d2cd4 Merge pull request #367 from CartoDB/metadata-refactor
Added property 'enableLayerMetadata' to config files
2016-02-03 15:37:05 +01:00
Javier Goizueta
870688309a Fix syntax 2016-02-02 19:29:10 +01:00
Javier Goizueta
a5070162c2 Merge branch 'overviews-work' of github.com:CartoDB/Windshaft-cartodb into overviews-work 2016-02-02 19:24:49 +01:00
Javier Goizueta
8348f74513 Provide OverviewsHandler configuration to Windshaft
A parameter has been added to Windshaft Mapnik renderer configuration
to define how queries will be adapted to use overviews.

Here we're using the default OverviewHandler providen in Windshaft,
with a parameter to define how the zoom level is determined.
2016-02-02 19:23:07 +01:00
csobier
e1babd05c1 add note regarding setParams and torque 2016-02-02 13:14:00 -05:00
Daniel García Aubert
08d43a8620 Added property 'enableLayerMetadata' to config files 2016-02-02 16:34:58 +01:00
Daniel García Aubert
8601a67e97 Improved assertions for multilayer tests 2016-02-01 18:39:34 +01:00
Daniel García Aubert
640500a0e3 Updated config to use Windshaft's geojson renderer 2016-02-01 17:12:50 +01:00
Daniel García Aubert
6ee1f1a8bf Fixed bad assertion in multilayer tests 2016-02-01 16:31:03 +01:00
Daniel García Aubert
a4041524a3 Fixed missing map key for named layers 2016-02-01 16:29:36 +01:00
Carlos Matallín
c3b17df3e7 Merge pull request #366 from CartoDB/docs-minor-clarification-added
Docs minor clarification added
2016-02-01 14:47:20 +01:00
csobier
5e77c50102 changed relative links to absolute links 2016-01-29 16:46:42 -05:00
csobier
1620cbc8df modified title 2016-01-29 12:11:21 -05:00
csobier
912b8f6ff4 fixed title of named maps 2016-01-29 12:07:47 -05:00
csobier
486a55ed7f edited intro text and fixed typo 2016-01-29 11:52:17 -05:00
csobier
af50af325d added links to new mapconfig, updated named map intro, and added cartodbjs named map examples 2016-01-29 11:46:03 -05:00
Daniel García Aubert
feef31d1bf Merge branch 'geojson-renderer' of github.com:CartoDB/Windshaft-cartodb into geojson-renderer 2016-01-29 15:36:47 +01:00
Raul Ochoa
f5b12d81ed Fix indent 2016-01-28 19:49:16 +01:00
Raul Ochoa
81200b72b4 Merge branch 'master' into overviews-work
Conflicts:
	test/acceptance/multilayer.js
2016-01-28 19:48:11 +01:00
Raul Ochoa
d6ecb8c793 Remove trailing whitespace 2016-01-28 19:44:25 +01:00
Javier Goizueta
5038ae6b1b Fix data for overviews integration tests 2016-01-27 17:40:12 +01:00
Javier Goizueta
37a4aaeeb4 Refactor findStatusCode for legibility
...disregarding jshint opinion
2016-01-27 17:39:24 +01:00
Javier Goizueta
6dbb0cb1c1 Emulate new overview table naming schme in the tests 2016-01-26 15:08:55 +01:00
Javier Goizueta
3b6abb5c9f Refactor construction of OverviewsApi 2016-01-26 11:49:41 +01:00
Javier Goizueta
ef9e9f8c78 Adapt to changes in CDB_Overviews SQL function
Now data for multiple tables is obtained in one call, simplifying the
use of this function. Also base table is returned as an oid, so we
now have the overview base table names with schema only when needed.
2016-01-26 11:38:21 +01:00
Javier Goizueta
2a819e559b Remove commented code 2016-01-22 11:15:25 +01:00
Javier Goizueta
8d691b2048 Refactor OverviewsApi
Separate metadata processing into collecting each layer's information (map)
and then organizing metadata per tables/zoom levels (reduce).
2016-01-22 11:03:01 +01:00
Javier Goizueta
1f6d5cfd6d Fix signature of CDB_Overviews 2016-01-21 18:39:31 +01:00
Javier Goizueta
81cb75f821 Refactor statusFromErrorMessage
...to make jshint happy
2016-01-21 18:24:49 +01:00
Javier Goizueta
8592136683 Change status code assigned to some errors
Errors without an explicit status code with the error message
containing 'does not exist' were assigned codes 404 or 403.
Now if the error message is 'function X does not exist'
(originated in SQL) the error code assigned is 400.
2016-01-21 18:02:21 +01:00
Javier Goizueta
18246418a0 Adapt test to new behaviour
Now an error occurs before craeeteLayergroup when checking affected
tables for overviews information. This prevents the creation of the
map configuration, so the corresponding redis keys need not be deleted.

The error message changes also because now the error originates in
a different function call, QueryTablesApi.prototype.getAffectedTablesInQuery
vs getAffectedTablesAndLastUpdatedTime.
2016-01-21 17:49:51 +01:00
Javier Goizueta
a6e3b07439 Reformat long lines 2016-01-21 17:42:52 +01:00
Javier Goizueta
c8033700c3 Fix equality operator use 2016-01-21 17:40:57 +01:00
Javier Goizueta
77f529d519 Add acceptance test for overviews 2016-01-21 17:36:25 +01:00
Javier Goizueta
6532024330 Add tests for MapConfigOverviewsAdapter 2016-01-21 15:44:22 +01:00
Javier Goizueta
62cc53228c OverviewApi: skip tables with no overlays in result 2016-01-21 13:35:56 +01:00
Javier Goizueta
532654eea8 Add tests for the OverviewsApi 2016-01-21 13:33:45 +01:00
Javier Goizueta
87bffb9657 Fix: overviews entry should be inside options 2016-01-21 12:06:01 +01:00
Javier Goizueta
094c9076be Fix: only mapnik layers can have overviews 2016-01-21 12:04:40 +01:00
Javier Goizueta
cc0385d614 Fix class name 2016-01-21 12:03:50 +01:00
Javier Goizueta
ed9b3e1230 Bring in code commented out for tests 2016-01-21 10:58:50 +01:00
Javier Goizueta
ffd89edaa7 Add overviews metadata to MapController instantiateTemplate
As in MapController create.
2016-01-20 18:36:06 +01:00
Javier Goizueta
5543fcb736 Fix: handle error properly when augmented layers with overviews 2016-01-20 18:09:00 +01:00
Javier Goizueta
7c897a40bf Fix bug in tests
The invalid SQL in this test (missing comma) was unnoticed because
the test was provoking a failed before the SQL was parsed, but new
features may cause the SQL to be evaluated (to get affected tables)
before the CartoCSS validity is checked.
2016-01-20 18:07:35 +01:00
Javier Goizueta
528574c550 Add dummy CDB_Overviews SQL function for tests 2016-01-20 17:10:12 +01:00
Javier Goizueta
b9f8812c98 Update comments 2016-01-20 17:09:15 +01:00
Javier Goizueta
09568050d6 Fix for changes in pgQueryRunner 2016-01-20 13:13:02 +01:00
Javier Goizueta
3dad225568 Fix bug 2016-01-20 13:12:45 +01:00
Javier Goizueta
4ca8ecf64c Refactor/fix potential problems 2016-01-20 12:44:00 +01:00
Javier Goizueta
2f2f6114e8 Refactor coding style
Hide the fact that we define functions in a loop from jshint!
2016-01-20 12:42:43 +01:00
Raul Ochoa
e4e7d6c840 Stubs next version 2016-01-20 12:26:16 +01:00
Raul Ochoa
d266d9a590 Release 2.20.0 2016-01-20 12:25:12 +01:00
Raul Ochoa
1e8525162a Bump version 2016-01-20 12:22:56 +01:00
Raul Ochoa
5fed5afe1f Update news 2016-01-20 12:22:24 +01:00
Javier Goizueta
8a49e46626 Accept minor jshint suggestions 2016-01-20 11:51:46 +01:00
Javier Goizueta
9feae66173 Bugfixes 2016-01-20 11:49:17 +01:00
Raul Ochoa
f8e4deb4b9 Merge pull request #364 from CartoDB/upgrade-windshaft
Upgrade windshaft to 1.7.0
2016-01-20 11:45:03 +01:00
Raul Ochoa
9c2fe9eac4 Upgrade windshaft to 1.7.0 2016-01-20 11:39:53 +01:00
Javier Goizueta
6aa9515fd1 Merge branch 'master' into overviews-work 2016-01-20 10:19:27 +01:00
Javier Goizueta
54854f0984 Avoid wrapper-functions to capture looping variable values
Use async-queue defer additional parameters
2016-01-20 10:07:19 +01:00
Javier Goizueta
89590d32df Sketch of vector overviews support 2016-01-19 19:31:43 +01:00
Raul Ochoa
d53a293f28 Merge pull request #363 from CartoDB/fix-statsd-key
Change redis pool name to report with a valid statsd key
2016-01-19 13:26:48 +01:00
Raul Ochoa
1dea84f9bf Change redis pool name to report with a valid statsd key 2016-01-19 13:00:02 +01:00
Raul Ochoa
e72bd77265 Merge branch 'master' into geojson-renderer
Conflicts:
	npm-shrinkwrap.json
	package.json
2016-01-18 14:52:01 +01:00
Raul Ochoa
8b50097a12 Merge pull request #361 from CartoDB/mapconfig-extension
Mapconfig widgets extension
2016-01-18 14:39:35 +01:00
Raul Ochoa
fbc1bccb48 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
2016-01-18 14:32:33 +01:00
Raul Ochoa
b5b881e662 Regenerate npm-shrinkwrap.json 2016-01-18 14:24:23 +01:00
Raul Ochoa
30e479094f Fix tests for windshaft master 2016-01-18 14:08:01 +01:00
Raul Ochoa
2b3244440f Merge remote-tracking branch 'origin/master' into mapconfig-extension 2016-01-15 18:31:30 +01:00
Raul Ochoa
ba4db870e4 Merge pull request #362 from CartoDB/prepare-tests-for-next-windshaft
Prepare tests for next windshaft
2016-01-15 18:30:50 +01:00
Raul Ochoa
d3f5b03f13 Validate current torque metadata keys 2016-01-15 17:25:09 +01:00
Raul Ochoa
e00661aa34 Remove substitution tokens in attributes service test 2016-01-15 17:17:20 +01:00
Raul Ochoa
3afb7a0eb3 Adds some notes about unsupported endpoints 2016-01-15 17:02:09 +01:00
Raul Ochoa
1eb69ae3d1 Remove make target to update windshaft 2016-01-15 16:02:02 +01:00
Raul Ochoa
5eeaa0272c Use master branch from windshaft and regenerate npm-shrinkwrap.json 2016-01-15 16:01:01 +01:00
Raul Ochoa
3e601cc2e6 Use geojson renderer branch from windshaft 2016-01-15 15:53:40 +01:00
Raul Ochoa
3e9f2a1319 Merge branch 'master' into mapconfig-extension 2016-01-14 18:45:30 +01:00
Raul Ochoa
45acdd9f39 fix json, 2nd attempt 2016-01-14 18:16:31 +01:00
Raul Ochoa
62b1df970a fix json 2016-01-14 18:16:12 +01:00
Raul Ochoa
1aac4316ab Merge pull request #360 from CartoDB/travis-container-based
Travis container based
2016-01-13 20:40:00 +01:00
Raul Ochoa
97d3bed1d2 test with pg 9.4 2016-01-13 20:33:12 +01:00
Raul Ochoa
19216eaa88 Use create language instead of extension 2016-01-13 20:21:54 +01:00
Raul Ochoa
3780aed1b7 Check with no postgresql-plpython-9.3 at all 2016-01-13 20:14:04 +01:00
Raul Ochoa
04a2d1d33c Remove postgresql-plpython-9.3 2016-01-13 20:11:24 +01:00
Raul Ochoa
18278da4bb Create language instead of extension 2016-01-13 19:36:01 +01:00
Raul Ochoa
553c64bd8b Create extension for plpython 2016-01-13 19:32:50 +01:00
Raul Ochoa
74abee2700 Use apt addon instead of apt-get install 2016-01-13 19:23:30 +01:00
Raul Ochoa
8ea159e0a1 Remove sudo calls 2016-01-13 19:14:58 +01:00
Raul Ochoa
353919239d Attempt to use travis' container based builds 2016-01-13 18:59:33 +01:00
Raul Ochoa
9e74e8633a Merge pull request #359 from CartoDB/query-runner-improvements
Query runner improvements
2016-01-13 18:56:26 +01:00
Raul Ochoa
98611be544 Merge pull request #351 from CartoDB/install_instructions
Install instructions
2016-01-12 16:39:58 +01:00
Raul Ochoa
3b7ff2285c Merge pull request #356 from CartoDB/http-409-status-on-template-limit-reached
Send 409 error code when maximum number of templates limit is reached
2016-01-12 16:14:37 +01:00
Raul Ochoa
8203c878f4 Send 409 error code when maximum number of templates limit is reached
Closes #346
2016-01-12 15:53:16 +01:00
Javier Goizueta
216e7b7f1d Use attributes-substitution-tokens of Windshaft
Intended for installation in a staging server
2016-01-08 12:07:51 +01:00
Carlos Matallín
4f4480dc9b Merge pull request #339 from CartoDB/newdocs
docs: split Map-API.md in different files
2016-01-07 15:38:31 +01:00
Carlos Matallín
16e1abe376 replicate #355 2016-01-07 15:33:16 +01:00
Carlos Matallín
2d0ebc821f Merge pull request #355 from CartoDB/353-named-maps-limit
added limit amount regarding named maps
2016-01-07 10:41:22 +01:00
csobier
67e921017c added limit amount regarding named maps 2016-01-06 08:44:53 -05:00
Paul Norman
7ed96ef0bb Add a list of packages and postgres instructions 2015-12-30 23:37:12 -08:00
Paul Norman
6f7bbe4ff5 Move install instructions to their own file 2015-12-30 17:29:19 -08:00
Raul Ochoa
74898e4261 Integration tests for QueryTablesApi
Again this is more about how it would be possible to use it isolated.
2015-12-30 17:48:24 +01:00
Raul Ochoa
c664d5392c Adds some tests about how to use PgQueryRunner isolated 2015-12-30 17:48:00 +01:00
Raul Ochoa
76cbc2f863 Improve PgQueryRunner public run method
Last param is callback function, receiving err + result, no need to
keep passing two functions, the query handler and the final callback.

It should be easier to understand now as query handler was in a position
where it had to know about receiving a callback, that was exposing an
implementation detail of PgQueryRunner.
2015-12-30 17:44:49 +01:00
Raul Ochoa
69fdaca41f Merge pull request #350 from CartoDB/fix-tests
Do not test `all` layers in blend suite
2015-12-30 16:04:58 +01:00
Raul Ochoa
5ac327272f Do not test all layers. Test is also present in windshaft suite. 2015-12-30 15:52:52 +01:00
Raul Ochoa
53a2d52523 Merge pull request #349 from CartoDB/pnorman-patch-1
Fix readme typo
2015-12-29 12:55:06 +01:00
Paul Norman
cd847adfb3 Fix readme typo
[no ci]
2015-12-28 11:53:54 -08:00
Raul Ochoa
e65dc2d790 Merge branch 'master' into mapconfig-extension 2015-12-22 12:59:39 +01:00
Raul Ochoa
e0d8f5afac Tests for named maps + torque layers with params instantiation 2015-12-21 20:01:44 +01:00
Raul Ochoa
7d2f543284 Allow column_type query string param 2015-12-02 18:50:11 +01:00
Raul Ochoa
ad566d8ff0 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-24 13:36:39 +01:00
Raul Ochoa
67ba517a0e Stubs next version 2015-11-23 16:08:32 +01:00
Raul Ochoa
9a01c8b26e Release 2.19.1 2015-11-23 16:07:37 +01:00
Raul Ochoa
b40fd29228 Merge pull request #344 from CartoDB/upgrade-windshaft
Upgrade windshaft
2015-11-23 16:03:16 +01:00
Raul Ochoa
152440e611 Upgrade windshaft to 1.6.1
Regenerates npm-shrinkwrap.json, including bump in torque.js@2.11.4
2015-11-23 15:51:57 +01:00
Raul Ochoa
ea2a94be88 Fix news formatting 2015-11-23 15:51:00 +01:00
Raul Ochoa
0a38549c55 Regenerate npm-shrinkwrap.json 2015-11-23 11:00:55 +01:00
Raul Ochoa
e3b25f3080 Dependencies installation example 2015-11-19 12:50:34 +01:00
Raul Ochoa
1de4753daa fix tests 2015-11-16 18:34:36 +01:00
Raul Ochoa
d9614cc1c5 Add search endpoint 2015-11-16 13:15:01 +01:00
Raul Ochoa
37ff13493c Use mapconfig-extension from windshaft 2015-11-16 12:23:05 +01:00
Raul Ochoa
9264f4a668 Re-adds npm-shrinkwrap.json 2015-11-16 12:13:54 +01:00
Raul Ochoa
e254379244 Use windshaft's histogram branch 2015-11-13 10:35:52 +01:00
Raul Ochoa
4c851a0d09 Remove npm-shrinkwrap.json to ease development 2015-11-13 10:35:30 +01:00
Raul Ochoa
a84dd7cd29 Add more url params 2015-11-12 19:45:49 +01:00
Raul Ochoa
86abc392a1 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-12 11:38:42 +01:00
Raul Ochoa
9e00cb3309 Stubs next version 2015-11-12 11:30:58 +01:00
Raul Ochoa
6d55544d70 Release 2.19.0 2015-11-12 11:29:50 +01:00
Raul Ochoa
d8e38e7e81 Merge pull request #343 from CartoDB/upgrade-mapnik
Upgrade windshaft/mapnik
2015-11-11 16:34:10 +01:00
Raul Ochoa
3dbcf5f293 Upgrade windshaft/mapnik 2015-11-11 16:28:21 +01:00
Raul Ochoa
7d230cc15d Widgets returning two results 2015-11-04 17:21:33 +01:00
Raul Ochoa
2320b0b852 Reference Windshaft's notes on Installing Mapnik 2015-11-03 16:56:37 +01:00
Raul Ochoa
837f99f31c Restrict npm supported versions to >=1.2.1 <2.0.0 2015-11-03 16:55:42 +01:00
Raul Ochoa
663703abae Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-11-02 11:13:34 +01:00
Raul Ochoa
5844c031c9 Stubs next version 2015-11-02 10:55:57 +01:00
Raul Ochoa
c9e95ccc9d Release 2.18.0 2015-11-02 10:38:50 +01:00
Raul Ochoa
98b14de02e Merge pull request #342 from CartoDB/upgrade-mapnik
Upgrade mapnik
2015-11-02 10:31:49 +01:00
Carlos Matallín
4bc8c57729 docs: remove code-title 2015-10-30 11:25:42 +01:00
Raul Ochoa
479861e612 Update news 2015-10-29 18:31:15 +01:00
Raul Ochoa
a746445b88 Upgrade windshaft to 1.5.0 2015-10-29 18:30:02 +01:00
Raul Ochoa
2c4cb6d42e Adds some tests for per widget bbox filters 2015-10-29 17:18:14 +01:00
Carlos Matallín
b2cd421e2e docs: rearrange overview 2015-10-29 16:59:51 +01:00
Raul Ochoa
8a81828a3d Enable bbox query string param 2015-10-29 16:25:56 +01:00
Raul Ochoa
8e568d0f20 Adapt tests to new widgets format 2015-10-29 13:19:49 +01:00
Raul Ochoa
3b9759d5e4 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-28 19:41:44 +01:00
Raul Ochoa
a20a1f692b Stubs next version 2015-10-28 19:27:53 +01:00
Raul Ochoa
628b1921b4 Release 2.17.0 2015-10-28 19:27:03 +01:00
Raul Ochoa
fd8131201d Merge pull request #341 from CartoDB/upgrade-mapnik
Upgrades windshaft to 1.4.0
2015-10-28 19:26:11 +01:00
Raul Ochoa
ee39450864 Regenerate npm-shrinkwrap.json 2015-10-28 19:21:22 +01:00
Raul Ochoa
34e866007e Use windshaft 1.4.0 2015-10-28 19:15:26 +01:00
Carlos Matallín
277c0ee818 docs: fixes 2015-10-28 17:38:46 +01:00
Carlos Matallín
a3c7b3fc35 docs: fixes 2015-10-28 17:31:20 +01:00
Carlos Matallín
034b492788 docs: static maps 2015-10-28 17:02:16 +01:00
Carlos Matallín
cd00680c80 docs: static maps 2015-10-28 17:00:22 +01:00
Carlos Matallín
cc0ebf70a7 docs: static maps 2015-10-28 16:55:11 +01:00
Carlos Matallín
8087f838ef docs: named maps 2015-10-28 16:46:50 +01:00
Raul Ochoa
3fa4f8573e Upgrades windshaft to 1.3.0 2015-10-28 16:31:11 +01:00
Carlos Matallín
3eda1750cc docs: named maps 2015-10-28 16:26:33 +01:00
Carlos Matallín
2c35e27095 docs: anonymous_maps 2015-10-28 16:07:04 +01:00
Carlos Matallín
90487819bf docs: anonymous_maps 2015-10-28 16:02:23 +01:00
Carlos Matallín
50a943c131 docs: quickstart 2015-10-28 15:53:39 +01:00
Carlos Matallín
36b91180e4 docs: quickstart 2015-10-28 15:51:31 +01:00
Carlos Matallín
df44a84bfc Merge branch 'master' into newdocs 2015-10-28 15:31:35 +01:00
Raul Ochoa
e5afdb1e04 Adds test combining two widgets + filters 2015-10-28 12:21:43 +01:00
Raul Ochoa
cf2774c852 Fix test description 2015-10-28 12:06:06 +01:00
Raul Ochoa
36369068e1 Use match instead of full url 2015-10-27 19:11:29 +01:00
Raul Ochoa
af0812e990 Fix jshint 2015-10-27 19:08:46 +01:00
Raul Ochoa
0285f015e2 Pass filters within params 2015-10-27 13:26:11 +01:00
Raul Ochoa
473b20596a Adds helper function to retrieve widgets 2015-10-27 13:26:11 +01:00
Raul Ochoa
fda405f35d Unify widgets tests 2015-10-27 13:20:15 +01:00
Raul Ochoa
4047cb82b9 Merge pull request #340 from CartoDB/fix-widget-urls
Adds trailing slash to API endpoint in widget URLs
2015-10-26 17:52:55 +01:00
Pablo Alonso Garcia
7b57d22444 Added trailing slash to API endpoint in widget URLs 2015-10-26 17:50:50 +01:00
Raul Ochoa
50e63bf83d Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-26 17:06:13 +01:00
Raul Ochoa
8a9e257cdb Adds target to Makefile to just remove and re-install windshaft dep 2015-10-26 16:51:21 +01:00
Raul Ochoa
3a05d8c2e8 Widgets inside layer options 2015-10-26 16:40:39 +01:00
Raul Ochoa
8b222914c5 Add URL to widgets with their endpoints 2015-10-26 14:35:51 +01:00
Raul Ochoa
6d9182aba8 Rename widgets endpoint to honour its name 2015-10-26 13:42:06 +01:00
Raul Ochoa
f9d3e419a0 Renames list -> histogram 2015-10-26 11:12:26 +01:00
Raul Ochoa
4b0ecb1251 Adds histogram support, a bit of code duplication 2015-10-26 11:10:59 +01:00
Raul Ochoa
36a6af3266 Replaces List with Widget backend and changes list access to be layer based 2015-10-26 10:23:56 +01:00
Raul Ochoa
2f592e6f62 Stubs next version 2015-10-22 16:28:49 +02:00
Raul Ochoa
860d4bb475 Release 2.16.0 2015-10-22 16:27:52 +02:00
Raul Ochoa
c7b5fb9187 Be more clear about how to upgrade shrinkwrap file 2015-10-22 16:26:37 +02:00
Carlos Matallín
5e0c9377f2 newdocs: fix titles 2015-10-22 15:02:01 +02:00
Carlos Matallín
8db1ad6f19 newdocs: split files 2015-10-22 13:28:08 +02:00
Raul Ochoa
f513db837d Merge pull request #338 from CartoDB/upgrade-windshaft
Upgrades windshaft to 1.2.0
2015-10-21 18:56:45 +02:00
Raul Ochoa
d7181ec6c2 Upgrades windshaft to 1.2.0 2015-10-21 18:38:19 +02:00
Raul Ochoa
7761f56d36 Stubs next version 2015-10-21 13:58:26 +02:00
Raul Ochoa
f136b9f6e7 Release 2.15.1 2015-10-21 13:56:53 +02:00
Raul Ochoa
12175f10a6 Upgrades windshaft to 1.1.1 2015-10-21 13:55:36 +02:00
Raul Ochoa
52dbe14af2 Merge branch 'master' into mapconfig-extension
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-10-13 16:06:04 +02:00
Raul Ochoa
01bd09040e Stubs next version 2015-10-13 12:44:29 +02:00
Raul Ochoa
ab38e7069a Release 2.15.0 2015-10-13 12:36:25 +02:00
Raul Ochoa
9449642773 Remove soft-purge option when purging fastly 2015-10-09 16:37:17 +02:00
Raul Ochoa
1f489a4537 Fix broken test due fastly-purge upgrade 2015-10-09 16:02:11 +02:00
Raul Ochoa
6effcfb62e Upgrades windshaft to 1.1.0 and fastly-purge to 1.0.1 2015-10-09 15:50:39 +02:00
Raul Ochoa
9bc95a6071 List endpoint for layergroups 2015-10-06 19:47:44 +02:00
Raul Ochoa
b80e80bd61 Do not pass MapStore as it is no longer needed in Attributes backend 2015-10-06 19:46:52 +02:00
Raul Ochoa
59fb1dea54 Start using mapconfig-extension branch from windshaft 2015-10-06 18:55:27 +02:00
Raul Ochoa
2cc4cc5deb Stubs next version 2015-09-30 19:26:33 +02:00
Raul Ochoa
4c5630ac43 Release 2.14.1 2015-09-30 18:52:08 +02:00
Raul Ochoa
3181bcc63e Remove app dependency from controllers 2015-09-30 18:00:54 +02:00
Raul Ochoa
bf9cb33d63 Fix tests after upgrading windshaft, which includes a fix for layergroupid 2015-09-30 17:55:17 +02:00
Raul Ochoa
e7aa6bbdf9 Upgrade windshaft 2015-09-30 17:44:40 +02:00
Raul Ochoa
550b73ce23 Update news 2015-09-30 17:41:37 +02:00
Raul Ochoa
ffaa756637 Merge pull request #337 from CartoDB/safe-user-extraction
Safe user extraction
2015-09-30 17:25:12 +02:00
Raul Ochoa
9cd67f06c1 User extraction from request middleware
Used only where potentially a user is required.

It doesn't make sense to extract a user for request that don't need
a user in the context.
2015-09-30 17:17:01 +02:00
Raul Ochoa
79375616d5 Default host to empty string when header is not found
A String object is required to not fail on `.match` interface.
2015-09-30 16:31:56 +02:00
Raul Ochoa
ba6b8fbff1 Stubs next version 2015-09-30 11:28:38 +02:00
Raul Ochoa
6c37901824 Release 2.14.0 2015-09-30 11:26:03 +02:00
Raul Ochoa
0cf8661432 Be more specific about windshaft version upgrade 2015-09-30 11:25:07 +02:00
Raul Ochoa
ff561157ac Use Windshaft from registry 2015-09-30 11:21:25 +02:00
Raul Ochoa
a7cf9fd74f Regenerate npm-shrinkwrap.json 2015-09-30 11:04:40 +02:00
Raul Ochoa
788d24a455 Merge pull request #335 from CartoDB/standalone-server
Standalone server using Windshaft as library
2015-09-30 01:13:16 +02:00
Raul Ochoa
f9e3870941 Update news with all changes related to use Windshaft as library 2015-09-30 00:59:42 +02:00
Raul Ochoa
3a92fdd0f3 Remove sql api related configuration from configure script 2015-09-29 20:00:39 +02:00
Raul Ochoa
6a38defac5 Use Windshaft master branch 2015-09-29 19:42:13 +02:00
Raul Ochoa
3dfa7a8427 Name functions 2015-09-29 19:39:08 +02:00
Raul Ochoa
8dca835a8e Regenerate npm-shrinkwrap.json 2015-09-29 12:58:48 +02:00
Raul Ochoa
f2278d47a5 Increment map views on static preview images 2015-09-29 12:21:11 +02:00
Raul Ochoa
de3f195187 Merge pull request #333 from CartoDB/remove-outdated-docs
Remove outdated docs
2015-09-28 22:15:13 +02:00
Raul Ochoa
f33c3ce21a rmdirRecursiveSync DRY 2015-09-25 19:56:28 +02:00
Raul Ochoa
67ba424a19 Check style 2015-09-25 19:47:09 +02:00
Raul Ochoa
3376a08eb8 Remove unneeded resources server 2015-09-25 19:45:05 +02:00
Raul Ochoa
1ae15fe209 Remove unneeded resources server 2015-09-25 19:44:11 +02:00
Raul Ochoa
3c2820a5e1 Remove unneeded resources server 2015-09-25 19:43:20 +02:00
Raul Ochoa
8da6088f10 Remove outdated benchmark client 2015-09-25 19:39:49 +02:00
Raul Ochoa
8961a266b3 Remove redis key with test helper 2015-09-25 19:37:53 +02:00
Raul Ochoa
db8ab80bef Remove redis client 2015-09-25 19:32:30 +02:00
Raul Ochoa
36efc359f7 Remove redis client 2015-09-25 19:30:01 +02:00
Raul Ochoa
4f4dab143e Make tests to fail if unexpected keys are found in redis
Make test fail if they try to remove unexistent redis key
2015-09-25 19:23:33 +02:00
Raul Ochoa
af1e31fb29 Remove console 2015-09-25 19:22:34 +02:00
Raul Ochoa
1f757a7378 check style 2015-09-25 19:21:20 +02:00
Raul Ochoa
6028172018 Delegates redis keys deletion to test client[A 2015-09-25 19:18:28 +02:00
Raul Ochoa
2d374160a1 Delegates redis keys deletion to test client 2015-09-25 19:18:03 +02:00
Raul Ochoa
587fbfb4ff Delegates redis keys deletion to test client 2015-09-25 19:17:33 +02:00
Raul Ochoa
84820a87c8 Ask for specific redis key removal 2015-09-25 19:16:57 +02:00
Raul Ochoa
937d417e80 Delegates redis keys deletion to test client 2015-09-25 19:14:42 +02:00
Raul Ochoa
5b76cfd4dd Delegates redis keys deletion to test client 2015-09-25 19:13:46 +02:00
Raul Ochoa
957ed22b95 Delegates redis keys deletion to test client 2015-09-25 19:13:13 +02:00
Raul Ochoa
ebbe89cfb8 Ask for specific redis key removal 2015-09-25 19:11:48 +02:00
Raul Ochoa
c8eb6f275f Ask for specific redis key removal 2015-09-25 19:07:57 +02:00
Raul Ochoa
5f1213415b Ask for specific redis key removal 2015-09-25 19:06:04 +02:00
Raul Ochoa
d36ccb2602 Adapts tests to after test client changes 2015-09-25 19:05:37 +02:00
Raul Ochoa
486b803856 Delete redis keys using test helper 2015-09-25 19:04:59 +02:00
Raul Ochoa
d35630329f Ask for specific redis key removal 2015-09-25 18:41:44 +02:00
Raul Ochoa
aa9f742852 Ask for specific redis key removal 2015-09-25 18:37:21 +02:00
Raul Ochoa
960906e00c Ask for specific redis key removal 2015-09-25 18:22:45 +02:00
Raul Ochoa
432acd2b0e Ask for specific redis key removal 2015-09-25 18:20:30 +02:00
Raul Ochoa
84a03a81a0 Ask for specific redis key removal 2015-09-25 18:17:58 +02:00
Raul Ochoa
f64791eadd Ask for specific redis key removal 2015-09-25 18:04:45 +02:00
Raul Ochoa
51db76ac41 Ask for specific redis key removal 2015-09-25 17:59:00 +02:00
Raul Ochoa
1ec7f71b6c Ask for specific redis key removal 2015-09-25 17:51:19 +02:00
Raul Ochoa
bb3abdcc48 Remove redis keys after each test 2015-09-25 17:08:38 +02:00
Raul Ochoa
4d3ce038bc Remove redis keys after each test 2015-09-25 17:04:08 +02:00
Raul Ochoa
d66db547cb Remove redis keys after each test 2015-09-25 16:25:26 +02:00
Raul Ochoa
ebf1627753 Remove redis keys after each test 2015-09-25 16:23:14 +02:00
Raul Ochoa
bc818ca9cf Remove redis keys after each test 2015-09-25 16:23:08 +02:00
Raul Ochoa
0827124492 Remove redis keys after each test 2015-09-25 16:16:51 +02:00
Raul Ochoa
fea970f434 Remove redis keys after each test 2015-09-25 16:16:37 +02:00
Raul Ochoa
61cc14939b Remove redis keys after each test 2015-09-25 16:10:50 +02:00
Raul Ochoa
835d8c867e Remove redis keys after each test 2015-09-25 16:09:41 +02:00
Raul Ochoa
62601e4252 Remove redis keys after each test 2015-09-25 16:06:19 +02:00
Raul Ochoa
cc0b482c7a Remove redis keys after each test 2015-09-25 16:05:01 +02:00
Raul Ochoa
ff94f9ca0c Remove redis keys after each test 2015-09-25 16:03:20 +02:00
Raul Ochoa
8cd25dbd4f Remove redis keys after each test 2015-09-25 14:51:21 +02:00
Raul Ochoa
8ecf2e10c8 style 2015-09-25 14:50:09 +02:00
Raul Ochoa
5fb13bb545 Remove redis keys after each test 2015-09-25 14:49:59 +02:00
Raul Ochoa
a24b745f5c Remove redis keys after each test 2015-09-25 14:46:46 +02:00
Raul Ochoa
a127ff8d89 Remove redis keys after each test 2015-09-25 14:44:17 +02:00
Raul Ochoa
d3aa27533a Remove redis keys after each test 2015-09-25 14:42:40 +02:00
Raul Ochoa
35ec9e0063 Remove redis keys after each test 2015-09-25 14:42:30 +02:00
Raul Ochoa
1bac98d086 Remove redis keys after each test 2015-09-25 14:40:12 +02:00
Raul Ochoa
fc2f759dd3 Remove redis keys after each test 2015-09-25 14:39:39 +02:00
Raul Ochoa
f337a13577 Remove redis keys after each test 2015-09-25 14:39:15 +02:00
Raul Ochoa
eebd89aedb Remove redis keys after each test 2015-09-25 14:35:58 +02:00
Raul Ochoa
a9573987ec Remove redis keys after each test 2015-09-25 14:23:13 +02:00
Raul Ochoa
be8c82870f Add database id for found keys 2015-09-25 14:21:04 +02:00
Raul Ochoa
e667da8453 Remove redis keys being used in tests 2015-09-25 14:20:21 +02:00
Raul Ochoa
1258466529 Remove redis keys used in tests after each test 2015-09-25 14:09:35 +02:00
Raul Ochoa
8495b223c6 Early return when no keys to delete 2015-09-25 14:09:14 +02:00
Raul Ochoa
8339e4a4cb Remove redis keys used in tests after each test 2015-09-25 14:08:59 +02:00
Raul Ochoa
fa7288e03e Remove redis keys used in tests after each test 2015-09-25 14:08:35 +02:00
Raul Ochoa
763b6fce6e Remove redis keys used in each test 2015-09-25 14:07:12 +02:00
Raul Ochoa
7224acca84 Clean all redis keys after each test 2015-09-25 14:06:53 +02:00
Raul Ochoa
e12e7c4170 Don't allow to use suite related functions anymore 2015-09-25 13:37:32 +02:00
Raul Ochoa
0060d751b6 Use describe instead of suite 2015-09-25 13:37:10 +02:00
Raul Ochoa
b368463670 Use describe instead of suite 2015-09-25 13:31:51 +02:00
Raul Ochoa
21eb931701 Remove specific template maps doc as it's redundant info from Map-API.md 2015-09-25 11:21:03 +02:00
Raul Ochoa
e12b44ab2b Removes internal doc as it's mostly a duplication of Map-API.md
It also contains outdated terms and features.
2015-09-25 11:19:07 +02:00
Raul Ochoa
723dc59490 Show some redis stats after running tests
By commenting out you can monitor to a file
2015-09-25 09:52:14 +02:00
Raul Ochoa
5e1bc3e199 Named map updates does not emit update event if template didn't change
closes #323
2015-09-23 19:59:39 +02:00
Raul Ochoa
857548bbe4 Adds support for named layers in named tiles/static maps 2015-09-23 18:44:11 +02:00
Raul Ochoa
56070f3017 Show some redis stats after running tests
By commenting out you can monitor to a file
2015-09-23 17:18:58 +02:00
Raul Ochoa
f553efa69e Named map mapconfig provider takes care of template modifications
If a template changes it will flush the provider so it recreates the mapconfig
2015-09-23 16:45:20 +02:00
Raul Ochoa
84bf375f72 Makes cache async 2015-09-23 14:32:26 +02:00
Raul Ochoa
807455eb3d Force VACUUM ANALYZE in test table to stabilize test 2015-09-23 14:07:51 +02:00
Raul Ochoa
57284a9398 style 2015-09-23 13:05:35 +02:00
Raul Ochoa
c8705a8022 Use provider to get affected tables in static maps 2015-09-23 13:04:46 +02:00
Raul Ochoa
bbdc4591df Adds methods to assert mapnik images 2015-09-23 13:04:08 +02:00
Raul Ochoa
0a13e7943b Split tiles/static 2015-09-22 19:59:27 +02:00
Raul Ochoa
1e0bc57d32 Rename test file 2015-09-22 19:49:01 +02:00
Raul Ochoa
14e6cb05b3 Adds tests for named maps authentication for tiles 2015-09-22 19:48:08 +02:00
Raul Ochoa
b617bb0277 Health check will always return error if file is found even if empty 2015-09-22 15:15:57 +02:00
Raul Ochoa
ac7b02a434 Adds test for corner case in health check 2015-09-22 14:55:50 +02:00
Raul Ochoa
17458f3f4e Use windshaft's backend-foundations branch as it already has mvt support 2015-09-22 14:13:09 +02:00
Raul Ochoa
e87fc8a839 Merge branch 'standalone-server' into standalone-server-mvt 2015-09-22 12:28:16 +02:00
Raul Ochoa
6cf4d53b7c Merge branch 'master' into standalone-server
Conflicts:
	lib/cartodb/controllers/named_static_maps.js
2015-09-21 23:55:17 +02:00
Raul Ochoa
619545b993 Stubs next version 2015-09-21 22:49:06 +02:00
Raul Ochoa
e75d17f0e1 Release 2.13.0 2015-09-21 22:45:50 +02:00
Raul Ochoa
77a5b70576 Update news and bump version 2015-09-21 22:43:33 +02:00
Raul Ochoa
59b0a00c6f Merge pull request #332 from CartoDB/static-namedmaps-x-cache
Keep x-cache-channel in named map static maps
2015-09-21 22:40:42 +02:00
Raul Ochoa
5d24da4f2b Keep x-cache-channel in named map static maps 2015-09-21 22:36:13 +02:00
Raul Ochoa
0d3e96ee9f Merge branch 'standalone-server' into standalone-server-mvt 2015-09-18 17:23:58 +02:00
Raul Ochoa
16480a6c44 log as warn 2015-09-18 17:23:37 +02:00
Raul Ochoa
813a59a36e Removes function from control flow 2015-09-18 17:16:30 +02:00
Raul Ochoa
23fd33030d remove console.log 2015-09-18 17:13:37 +02:00
Raul Ochoa
2bdce4baa7 Replaces console.log with global logger 2015-09-18 17:13:22 +02:00
Raul Ochoa
d2df0b7c84 Configure log4js in test environment so it doesn't output by default 2015-09-18 16:59:45 +02:00
Raul Ochoa
16468b1216 remove console.log 2015-09-18 16:45:35 +02:00
Raul Ochoa
3abdee5e87 use debug for corner case 2015-09-18 16:40:55 +02:00
Raul Ochoa
d69a69da94 remove console.log 2015-09-18 16:39:52 +02:00
Raul Ochoa
bc806bba34 remove console.log 2015-09-18 16:39:30 +02:00
Raul Ochoa
81081ba2d4 Remove console.log from test 2015-09-18 16:26:54 +02:00
Raul Ochoa
5ad567803d Set backlog on server listen 2015-09-18 16:25:10 +02:00
Raul Ochoa
486cafba9d re-generate npm-shrinkwrap.json 2015-09-18 03:06:26 +02:00
Raul Ochoa
c0a1119d1d Merge branch 'standalone-server' into standalone-server-mvt 2015-09-18 02:58:39 +02:00
Raul Ochoa
bd4894ebd3 Starts modifying readme 2015-09-18 02:58:16 +02:00
Raul Ochoa
e9e95eda0a Merge branch 'standalone-server' into standalone-server-mvt 2015-09-18 02:31:38 +02:00
Raul Ochoa
1b3f1ba2e8 Merge pull request #331 from CartoDB/standalone-server-express-4
Upgrade express to 4.x
2015-09-18 02:09:23 +02:00
Raul Ochoa
5e27f981de Regenerate npm-shrinkwrap.json 2015-09-18 01:58:47 +02:00
Raul Ochoa
c97f78bb39 Merge branch 'standalone-server' into standalone-server-mvt
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-09-18 01:34:14 +02:00
Raul Ochoa
f7b1032b7a Do not fail for now if there are pending keys in redis 2015-09-17 18:24:12 +02:00
Raul Ochoa
7ee2649feb Remove redis keys having in mind last updated time 2015-09-17 18:12:45 +02:00
Raul Ochoa
281320f2c4 use 127.0.0.1 instead of localhost 2015-09-17 17:51:34 +02:00
Raul Ochoa
30f7c74aee Reenable external resources tests using 127.0.0.1 2015-09-17 17:36:23 +02:00
Raul Ochoa
2dfd7257dd Try to run in travis with external resources disabled 2015-09-17 17:19:31 +02:00
Raul Ochoa
1f9dd5fd8c re-enable nock after every suite 2015-09-17 17:14:32 +02:00
Raul Ochoa
dd83c05a89 restore nock globally after each suite 2015-09-17 15:10:23 +02:00
Raul Ochoa
30cba053da Check there is no unexepcted keys on redis after tests 2015-09-17 15:07:54 +02:00
Raul Ochoa
5428d3f0b0 Remove __dirname 2015-09-17 13:58:45 +02:00
Raul Ochoa
95398354e3 require test helper 2015-09-17 13:58:22 +02:00
Raul Ochoa
7e73216539 Remove unused variables 2015-09-17 13:44:37 +02:00
Raul Ochoa
bbac1df463 Moves acceptance test about statsd uncaugth exception to integration 2015-09-17 13:40:01 +02:00
Raul Ochoa
208dd209a4 Merge branch 'standalone-server' into standalone-server-express-4
Conflicts:
	lib/cartodb/controllers/base.js
2015-09-17 12:57:33 +02:00
Raul Ochoa
9139feaa30 Move error message handling test to unit 2015-09-17 12:48:29 +02:00
Raul Ochoa
f9f6c8b700 Use debug instead of console 2015-09-17 12:03:58 +02:00
Raul Ochoa
db8457af60 status + send on syntax error handler 2015-09-17 11:07:02 +02:00
Raul Ochoa
361dd00e9d Use debug instead of console 2015-09-17 11:06:46 +02:00
Raul Ochoa
7fd870cfd2 Rewrite assert.response using request module 2015-09-17 02:06:46 +02:00
Raul Ochoa
967ef99277 Fix jsonp tests 2015-09-17 02:06:32 +02:00
Raul Ochoa
d93abe8e7d Change to delete 2015-09-17 02:05:47 +02:00
Raul Ochoa
a4ba21f9db Call send with correct params 2015-09-17 02:05:25 +02:00
Raul Ochoa
feabb20748 Send depending on body type 2015-09-17 02:04:30 +02:00
Raul Ochoa
31fe06e3ce Use listener and remove etag 2015-09-17 02:04:10 +02:00
Raul Ochoa
ef86bacf7f Set headers with set method 2015-09-17 02:03:09 +02:00
Raul Ochoa
beabe48aec Upgrade express, adds body-parser
- basic changes in server
- basic changes in unit tests
2015-09-17 00:19:00 +02:00
Raul Ochoa
e335f51dbc Makefile getting tests with find command 2015-09-16 23:18:38 +02:00
Raul Ochoa
38e422e84c Moves sendError and sendResponse to Base controller
Test for findStatusCode moved to controller
2015-09-16 21:54:56 +02:00
Raul Ochoa
1d6d11171d Fix test to not rely on network 2015-09-16 19:53:14 +02:00
Raul Ochoa
e32ced107e Fix all ported tests related to req2params 2015-09-16 18:09:39 +02:00
Raul Ochoa
99d78ce9b8 Remove unused variables 2015-09-16 17:02:35 +02:00
Raul Ochoa
066aff16f1 Use global for req2params number of request assert 2015-09-16 16:58:08 +02:00
Raul Ochoa
352dc6b311 BaseController to encapsulate req2params method
All controllers now extending BaseController
- Most of the acceptance ported tests will be broken
2015-09-16 16:18:26 +02:00
Raul Ochoa
a176ddbf3f Regenerate npm-shrinkwrap.json 2015-09-16 13:25:57 +02:00
Raul Ochoa
1c6571d1db Upgrade dependencies and regenerate npm-shrinkwrap.json
Surrogate keys tests sleeping :-(
2015-09-16 13:17:03 +02:00
Raul Ochoa
bf7b35230f Regenerate npm-shrinkwrap.json 2015-09-16 11:44:12 +02:00
Raul Ochoa
46d901ada7 Merge branch 'standalone-server' into standalone-server-mvt 2015-09-16 11:09:27 +02:00
Raul Ochoa
66f94d9452 Fix test 2015-09-16 02:49:18 +02:00
Raul Ochoa
62f428f434 Remove app dependency 2015-09-16 01:48:54 +02:00
Raul Ochoa
713ad03c3b No need to expose findStatusCode at app level 2015-09-16 01:44:30 +02:00
Raul Ochoa
ad2ebc11dd Remove unused require 2015-09-16 01:39:15 +02:00
Raul Ochoa
72a0c4a487 New sendResponse and sendError methods
- fixes response for static named map error cases
2015-09-16 01:36:51 +02:00
Raul Ochoa
fba5a35514 Move sendResponse and sendError to response object 2015-09-15 19:28:02 +02:00
Raul Ochoa
e2e5e40ea9 Fix maxcomplexity 2015-09-15 19:01:34 +02:00
Raul Ochoa
f5660667c8 Move req.profiler call to req2params itself 2015-09-15 18:16:50 +02:00
Raul Ochoa
64e225c4aa Merge branch 'master' into standalone-server 2015-09-15 11:48:33 +02:00
Raul Ochoa
126491fc93 Remove unused xml file that was used in health check 2015-09-15 11:46:19 +02:00
Raul Ochoa
ea872d96f8 Listen to renderer cache events and log stats 2015-09-14 19:24:24 +02:00
Raul Ochoa
3f7202d89c Port tests for stats 2015-09-14 19:07:53 +02:00
Raul Ochoa
2d3088ba27 Port everything related to stats from windshaft 2015-09-14 18:47:01 +02:00
Raul Ochoa
469b602484 Attributes backend using mapconfig provider to access attributes 2015-09-14 18:46:22 +02:00
Raul Ochoa
aa6f00f927 Merge branch 'standalone-server' into standalone-server-mvt 2015-09-10 12:06:21 +02:00
Raul Ochoa
67afd8738b Remove renderer cache param from attributes backend 2015-09-10 00:47:32 +02:00
Raul Ochoa
98d6170870 Adds notes about contributing 2015-09-08 16:44:44 +02:00
Raul Ochoa
b2e1e5361f Merge branch 'standalone-server' into standalone-server-mvt
Conflicts:
	npm-shrinkwrap.json
2015-09-08 15:46:23 +02:00
Raul Ochoa
f96c80d7a1 Merge branch 'master' into standalone-server
Conflicts:
	lib/cartodb/cartodb_windshaft.js
2015-09-08 15:42:30 +02:00
Raul Ochoa
7ae034d746 Remove no longer needed health check params 2015-09-07 18:40:20 +02:00
Raul Ochoa
c409c146bf Upgrade CDB_QueryTables to use latest version 2015-09-07 17:17:40 +02:00
Raul Ochoa
e0a7eb01cc Use torque renderer config
Adds some notes about db pool params in torque
2015-09-04 16:33:40 +02:00
Raul Ochoa
3af2136770 Merge branch 'master' into standalone-server
Conflicts:
	lib/cartodb/cartodb_windshaft.js
	lib/cartodb/monitoring/health_check.js
2015-09-04 13:21:54 +02:00
Raul Ochoa
13a2001a2b Merge pull request #329 from CartoDB/health-check-no-results
Do not return results from health check
2015-08-28 18:50:00 +02:00
Raul Ochoa
d6102284a4 Do not return results from health check
It also removed old dependencies and takes disabled file path in ctor.
2015-08-28 17:41:40 +02:00
Raul Ochoa
c56ef8e0da Merge branch 'master' into standalone-server
Conflicts:
	app.js
	npm-shrinkwrap.json
	package.json
2015-08-28 13:54:56 +02:00
Raul Ochoa
bb17609ea3 Stubs next version 2015-08-27 17:48:06 +02:00
Raul Ochoa
f2f342e14c Release 2.12.0 2015-08-27 17:46:29 +02:00
Raul Ochoa
11bd07ee6b Merge pull request #328 from CartoDB/upgrade-windshaft
Upgrades windshaft to 0.51.0
2015-08-27 17:39:14 +02:00
Raul Ochoa
fa85dcbc4c Upgrades windshaft to 0.51.0 2015-08-27 17:24:38 +02:00
Raul Ochoa
473ae13a03 Merge pull request #327 from CartoDB/http-agent-configuration
Make http and https globalAgent options configurable
2015-08-27 16:44:55 +02:00
Raul Ochoa
0561a28a4d Make http and https globalAgent options configurable 2015-08-27 16:28:16 +02:00
Raul Ochoa
b3467116fe Stubs next version 2015-08-26 09:55:15 +02:00
Raul Ochoa
e667d10eb8 Release 2.11.0 2015-08-26 09:52:53 +02:00
Raul Ochoa
a5bda00cdd Merge pull request #326 from CartoDB/upgrade-windshaft
Upgrades windshaft to 0.50.0
2015-08-25 19:32:28 +02:00
Raul Ochoa
627bc672bc Upgrades windshaft to 0.50.0 2015-08-25 19:27:00 +02:00
Raul Ochoa
9ef96080a6 Log PID on start 2015-08-24 12:45:21 +02:00
Raul Ochoa
bf4844e664 Stubs next version 2015-08-18 16:35:02 +02:00
Raul Ochoa
06f454abcf Release 2.10.0 2015-08-18 16:33:44 +02:00
Raul Ochoa
9d4e4a99bc Merge pull request #325 from CartoDB/upgrade-windshaft-tilelive-cache
Upgrades windshaft
2015-08-18 16:29:22 +02:00
Raul Ochoa
b77be76f51 Bump version and add news 2015-08-18 15:22:27 +02:00
Raul Ochoa
3121ed9a95 Config samples for metatileCache with default behaviour 2015-08-18 15:18:58 +02:00
Raul Ochoa
37dfd8fc12 Upgrade windshaft 2015-08-18 15:18:44 +02:00
Raul Ochoa
de2719b0c5 Stubs next version 2015-08-06 18:03:00 +02:00
Raul Ochoa
e3d5abc9a2 Release 2.9.0 2015-08-06 18:02:09 +02:00
Raul Ochoa
ea7a5da1c1 Merge pull request #324 from CartoDB/memory-stats
Send memory usage stats
2015-08-06 16:12:18 +02:00
Raul Ochoa
2e063cc2d2 Send memory usage stats 2015-08-06 16:06:42 +02:00
Raul Ochoa
943509864d Improve uniqueness of named map map config provider 2015-07-31 12:24:34 +02:00
Raul Ochoa
2ac228359f Fallback to image/png header 2015-07-31 12:23:36 +02:00
Raul Ochoa
909f8da2ff Adds lru cache for layergroups and named maps mapconfig provider 2015-07-15 16:51:26 +02:00
Raul Ochoa
5a5832394a Remove console.log 2015-07-15 16:10:59 +02:00
Raul Ochoa
52b60a22fd Makes all tests to run together 2015-07-15 16:09:43 +02:00
Raul Ochoa
116da64e5c More strict cyclomatic complexity check 2015-07-15 15:10:59 +02:00
Raul Ochoa
9c6c63c167 More strict jshint 2015-07-15 15:03:28 +02:00
Raul Ochoa
e7284262e4 Merge branch 'master' into standalone-server
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-07-15 12:20:12 +02:00
Raul Ochoa
decd9077e4 Stubs next version 2015-07-15 11:44:59 +02:00
Raul Ochoa
f6c47bf85e Release 2.8.0 2015-07-15 11:43:54 +02:00
Raul Ochoa
423191c13b Merge pull request #319 from CartoDB/upgrade-windshaft
Upgrades windshaft to 0.48.0
2015-07-15 11:43:09 +02:00
Raul Ochoa
7a5928d957 Upgrades windshaft to 0.48.0 2015-07-15 11:31:26 +02:00
Raul Ochoa
bbec3ae7da Subscribes to named map changes to invalidate cache 2015-07-14 21:18:10 +02:00
Raul Ochoa
1f7daab677 Caching named map providers by template name and config/auth token
Named Map provider cache buster changes on creation
2015-07-14 21:17:58 +02:00
Raul Ochoa
91ab64dda9 Fix cached result in getAffectedTablesAndLastUpdatedTime 2015-07-14 21:00:27 +02:00
Raul Ochoa
722705468f Not adding surrogate keys for empty affected tables 2015-07-14 20:53:26 +02:00
Raul Ochoa
07c920bad5 Use named map provider cache to retrieve providers 2015-07-14 20:53:06 +02:00
Raul Ochoa
6d3ef11a7c Fix cache usage in layergroup affected tables 2015-07-14 20:11:49 +02:00
Raul Ochoa
4aabe9d946 Named maps controller adding cache headers
This requires a cache for affected tables as it is hitting db for
every request right now
2015-07-14 20:10:55 +02:00
Raul Ochoa
7247b20686 Unify sendResponse in named maps controller 2015-07-14 17:34:05 +02:00
Raul Ochoa
8e8f618a22 assert instead of ifs 2015-07-14 17:33:42 +02:00
Raul Ochoa
a50af0ee64 Merge branch 'master' into standalone-server 2015-07-14 16:36:26 +02:00
Raul Ochoa
436c334f5a Stubs next version 2015-07-14 16:33:12 +02:00
Raul Ochoa
4f84138ade Release 2.7.2 2015-07-14 16:32:04 +02:00
Raul Ochoa
a0d86ac5dc Update news 2015-07-14 16:28:51 +02:00
Raul Ochoa
d426702213 Merge branch 'master' into standalone-server 2015-07-14 16:24:39 +02:00
Raul Ochoa
e2be4f1275 Merge pull request #314 from CartoDB/use-cdb-query-tables-text
Use CDB_QueryTablesText instead of CDB_QueryTables
2015-07-14 16:20:23 +02:00
Raul Ochoa
c97610ad59 style 2015-07-14 14:30:37 +02:00
Raul Ochoa
e8b5845174 Shared cache for affected tables in layergroup and map controllers 2015-07-14 13:40:41 +02:00
Raul Ochoa
c295584864 Cache channel now in layergroup controller
Internal cache channel dbname+layergroupid cache must be unified in layergroup
and map controllers
Removes sendWithHeaders
2015-07-14 11:55:49 +02:00
Raul Ochoa
36257f73b9 Better format 2015-07-13 17:18:50 +02:00
Raul Ochoa
9355a5ca24 Tests for surrogate keys in layergroup anonymous instantiation 2015-07-13 16:54:08 +02:00
Raul Ochoa
d6447ef311 Fix tests related to surrogate keys, includes tables 2015-07-13 16:36:41 +02:00
Raul Ochoa
5e2a20fbe0 Tags layergroup instantiation with surrogate keys per affected tables 2015-07-13 16:15:34 +02:00
Raul Ochoa
76823f7529 No need to pass a reference to itself 2015-07-13 15:06:22 +02:00
Raul Ochoa
96a6a0d980 Using MapStore, no need to attach it to app 2015-07-13 15:05:45 +02:00
Raul Ochoa
b05701be61 Authentication/Authorization moves to its own entity 2015-07-13 15:05:03 +02:00
Raul Ochoa
962ac97433 regenerate npm-shrinkwrap.json 2015-07-13 12:52:12 +02:00
Raul Ochoa
9491b81d0c Standalone server with MVT support 2015-07-13 12:34:52 +02:00
Raul Ochoa
316f08df08 named maps tiles sending tile headers 2015-07-13 11:53:23 +02:00
Raul Ochoa
f9554ec761 Re-enable render limits 2015-07-10 19:10:55 +02:00
Raul Ochoa
e128b1d750 remove unused method 2015-07-10 12:33:01 +02:00
Raul Ochoa
e45efbcfb0 New map config provider to allow injecting limits in context 2015-07-10 12:31:56 +02:00
Raul Ochoa
847ab96a48 RendererFactory changes for new signature 2015-07-10 12:30:52 +02:00
Raul Ochoa
1e52f790ad One pass for prepare request and response objects 2015-07-10 11:25:20 +02:00
Raul Ochoa
9bece712a9 Splits controllers and supports after layergroup creation actions 2015-07-10 11:24:32 +02:00
Raul Ochoa
6e0678e084 better style 2015-07-10 01:31:06 +02:00
Raul Ochoa
579cabdc1a Initial refactor of layergroup creation 2015-07-10 01:30:38 +02:00
Raul Ochoa
9f252dfac4 Improve named map config format for cache key 2015-07-09 19:49:11 +02:00
Raul Ochoa
5aad624346 NamedMaps controller using NamedMapMapConfigProvider
PreviewBackend with format param
2015-07-09 18:47:21 +02:00
Raul Ochoa
23d1109910 Adds named maps mapconfig provider
starts using it in named map instantiation
2015-07-09 14:39:25 +02:00
Raul Ochoa
ae2a72a810 Fix named maps controller using MapStoreMapConfig 2015-07-09 13:37:00 +02:00
Raul Ochoa
ed096c3a1a disable tests, time to work on a named map provider before fixing 'em 2015-07-08 20:51:55 +02:00
Raul Ochoa
123346ebdb Refactor controllers to use map store map config provider 2015-07-08 20:51:36 +02:00
Raul Ochoa
8540965696 fix health check tests 2015-07-08 20:50:34 +02:00
Raul Ochoa
c8568b175b Move server info to its own controller 2015-07-08 16:08:38 +02:00
Raul Ochoa
1737cbe1a5 Unifies named map instantiation so it's easy to work on it 2015-07-08 15:50:59 +02:00
Raul Ochoa
c81048312d Context with user 2015-07-08 15:34:46 +02:00
Raul Ochoa
ac3afd5695 Fix jshint 2015-07-08 13:28:07 +02:00
Raul Ochoa
fa84813a37 Manage cors with a middleware 2015-07-08 13:27:56 +02:00
Raul Ochoa
8cd3807100 Split named maps administration from instantiation/usage 2015-07-08 13:11:57 +02:00
Raul Ochoa
7aeb54d53d Enables multilayer ported tests 2015-07-08 12:59:49 +02:00
Raul Ochoa
725ff41fb1 Ports tile stats tests from windshaft 2015-07-08 00:19:11 +02:00
Raul Ochoa
d071fe6d0c Ports windshaft server unit tests 2015-07-08 00:12:32 +02:00
Raul Ochoa
2234a763cb Uses model through model namespace 2015-07-07 23:52:39 +02:00
Raul Ochoa
d52b65470e Ports acceptance tests from windshaft 2015-07-07 23:46:58 +02:00
Raul Ochoa
b63e697934 Handle no layers case 2015-07-07 23:45:56 +02:00
Raul Ochoa
8a036c79c7 Merge branch 'master' into standalone-server
Conflicts:
	app.js
2015-07-07 12:36:39 +02:00
Raul Ochoa
9a393fa793 Adds some notes about uv_threadpool_size and mapnik renderer pool size 2015-07-07 12:27:09 +02:00
Raul Ochoa
7614f72df6 Stubs next version 2015-07-06 11:58:08 +02:00
Raul Ochoa
ef2db78567 Release 2.7.1 2015-07-06 11:56:30 +02:00
Raul Ochoa
8708468444 fix 2.7.0 release date 2015-07-06 11:56:18 +02:00
Raul Ochoa
cd28a4fbcc redis-mpool noReadyCheck and unwatchOnRelease options from config
do not extend them as it disallows to pick from config
2015-07-06 11:52:34 +02:00
Raul Ochoa
e49881d1ed Improve authorizedBySigner 2015-07-06 03:23:51 +02:00
Raul Ochoa
aa266f9b61 Improve authorizedByAPIKey 2015-07-06 03:19:56 +02:00
Raul Ochoa
ccd3d0a3bf Merge branch 'named-maps-tiles' into standalone-server
Conflicts:
	app.js
2015-07-06 02:55:13 +02:00
Raul Ochoa
69fc367f69 Empty results/png directory 2015-07-06 02:32:13 +02:00
Raul Ochoa
a9f24542d5 regenerate npm-shrinkwrap.json 2015-07-06 02:09:06 +02:00
Raul Ochoa
8e4e458a2a fix jshint 2015-07-06 02:08:56 +02:00
Raul Ochoa
feec7805af Merge branch 'master' into named-maps-tiles
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-07-06 01:32:33 +02:00
Raul Ochoa
19f488095b Stubs next version 2015-07-06 01:07:30 +02:00
Raul Ochoa
cd65c6dd0e Release 2.7.0 2015-07-06 01:05:28 +02:00
Raul Ochoa
0c670cfdfd Merge pull request #318 from CartoDB/upgrade-windshaft
Upgrades windshaft to 0.47.0
2015-07-05 21:51:34 +02:00
Raul Ochoa
27ff1ac4f6 Upgrades windshaft to 0.47.0 2015-07-05 21:16:12 +02:00
Raul Ochoa
3f06de93f7 Merge pull request #317 from CartoDB/improve-redis
Improve redis
2015-07-05 21:11:34 +02:00
Raul Ochoa
0da6495330 Fixes unwatchOnRelease redis config 2015-07-05 21:00:25 +02:00
Raul Ochoa
bf24347328 Exposes redis noReadyCheck config 2015-07-05 20:59:52 +02:00
Raul Ochoa
7a5d73f9df Upgrades redis-mpool 2015-07-05 20:58:39 +02:00
Raul Ochoa
7fc403425d metadata backend 2015-07-04 23:44:39 +02:00
Raul Ochoa
ef171bf2af reverts map store changes 2015-07-04 23:38:15 +02:00
Raul Ochoa
b74a6624e3 remove redundant code 2015-07-04 23:32:26 +02:00
Raul Ochoa
19bf1fe56b note about token format 2015-07-04 23:32:19 +02:00
Raul Ochoa
ea6bb8dca3 fix jsdoc 2015-07-04 23:20:12 +02:00
Raul Ochoa
9d6d3f96b2 Unify mapstore 2015-07-04 23:18:09 +02:00
Raul Ochoa
5967c5d1d5 Reorg app.js 2015-07-04 23:09:00 +02:00
Raul Ochoa
a6017c6ade Reorg requires 2015-07-04 21:33:31 +02:00
Raul Ochoa
2d3f2667ca Standalone server initial implementation
- no dependency over Windshaft.Server
2015-07-04 20:41:22 +02:00
Raul Ochoa
ed90cadd75 fix jshint 2015-07-02 16:35:13 +02:00
Raul Ochoa
0d9f34fd48 Stubs next version 2015-07-02 15:48:58 +02:00
Raul Ochoa
da55a3bdd2 Release 2.6.1 2015-07-02 15:47:40 +02:00
Raul Ochoa
333334e598 Upgrades windshaft 2015-07-02 15:41:48 +02:00
Raul Ochoa
7168e4410c Stubs next version 2015-07-02 14:12:41 +02:00
Raul Ochoa
3ff8571f4a Release 2.6.0 2015-07-02 14:11:39 +02:00
Raul Ochoa
75ddcbbd01 Updates windshaft to 0.46.0 and documents new config formatMetatile 2015-07-02 13:28:37 +02:00
Raul Ochoa
91a44980f3 Skips limits tests until beforeRendererCreate is available 2015-07-02 02:03:03 +02:00
Raul Ochoa
034f3c77ce modifies controllers to use new mapbackend signatures 2015-07-02 02:02:22 +02:00
Raul Ochoa
9b3e18f333 Merge pull request #315 from CartoDB/readme_update
Update instructions
2015-07-01 09:37:57 +02:00
Juan Ignacio Sánchez Lara
fcb0a4a7e6 Less specific upgrade help 2015-07-01 07:22:37 +02:00
Raul Ochoa
5a003a7cbe Initial/dummy implementation for named maps tiles
Issues:
 - creates a layergroup per tile:
   - trigges a mapview
   - extracts each time affected tables and last update
 - duplicates a lot of code from NamedStaticMapsController
 - keeps relying on fake request concept
2015-06-30 15:41:57 +02:00
Juan Ignacio Sánchez Lara
94e38cef9d Update instructions 2015-06-30 11:00:32 +02:00
Raul Ochoa
d13d107aea Adds names to functions 2015-06-29 19:18:52 +02:00
Raul Ochoa
4f87796e9c Uses backend-foundations branch to use createLayergroup from backend 2015-06-29 18:58:58 +02:00
Raul Ochoa
837da45f4f Merge branch 'master' into named-maps-tiles 2015-06-29 18:37:11 +02:00
Raul Ochoa
9e30f05e7d Reverts to use cdb branch as is already published 2015-06-29 16:46:07 +02:00
Raul Ochoa
0df725112b Update CDB_QueryTables function 2015-06-29 16:42:55 +02:00
Raul Ochoa
098ed6b203 New endpoint for named maps tiles 2015-06-29 16:39:35 +02:00
Raul Ochoa
c6f9152efe Moves template maps to backends directory 2015-06-29 16:38:13 +02:00
Raul Ochoa
9ea2029f81 Deprecating scripts from tools directory 2015-06-25 18:07:48 +02:00
Raul Ochoa
2715f47a22 Points CDB_QueryTables script to the branch with CDB_QueryTablesText 2015-06-24 19:07:41 +02:00
Rafa de la Torre
90d0b23441 Use CDB_QueryTablesText instead of CDB_QueryTables
This avoids trouble with len(schema.table_name) > 63
See https://github.com/CartoDB/cartodb-postgresql/issues/86
2015-06-24 15:43:04 +02:00
Raul Ochoa
b59e0a00a0 Merge pull request #313 from CartoDB/cartodb-layer-docs
Update Map-API docs
2015-06-23 15:04:16 +02:00
Andrew Thompson
790571fd2c Update Map-API docs
It was not clear to me what the difference was between a mapnik layer and a cartodb layer until I read the comments in the Mapconfig spec! Let's save some users the extra step :)
2015-06-22 11:14:57 -04:00
Raul Ochoa
6ecebae110 Adds test to validate (once it is fixed) long table names do not fail 2015-06-18 16:29:59 +02:00
Raul Ochoa
849470a3c0 Stubs next version 2015-06-18 12:55:38 +02:00
Raul Ochoa
74d0a6f183 Release 2.5.0 2015-06-18 12:53:45 +02:00
Raul Ochoa
61c134215a Bumps version and regenerates npm-shrinkwrap.json 2015-06-18 12:29:02 +02:00
Raul Ochoa
b7218d8832 Fix list style 2015-06-18 12:19:03 +02:00
Raul Ochoa
63e19427af Fix documentation error examples to match 'General Concepts' guidelines 2015-06-18 12:17:17 +02:00
Raul Ochoa
d4f8578fd6 Merge pull request #312 from CartoDB/issue-311
Adds layergroupid header
2015-06-18 11:21:03 +02:00
Raul Ochoa
eaccd062d3 Adds layergroupid header
Closes #311
2015-06-18 01:13:33 +02:00
Raul Ochoa
5f2d5931f4 Regenerate npm-shrinkwrap.json using master branch 2015-06-12 15:48:03 +02:00
Raul Ochoa
ce40fa608e Clarify bounds meaning in template view 2015-06-08 14:31:04 -04:00
Raul Ochoa
0979c75852 Merge pull request #308 from CartoDB/issue-305
Named maps error responses with valid format
2015-06-08 10:49:22 -04:00
Raul Ochoa
f01e8e0866 Merge pull request #304 from CartoDB/docs-revamp
Documentation for named maps static images + view option in templates
2015-06-08 10:49:08 -04:00
Raul Ochoa
a4e303ab63 Remove console.log from tests 2015-06-08 10:37:56 -04:00
Raul Ochoa
c5137c9c29 Update news 2015-06-05 13:41:46 -04:00
Raul Ochoa
9bce88f9b1 Fix tests 2015-06-05 13:39:25 -04:00
Raul Ochoa
68c70effec Named maps returning errors=>Array instead of error=>String 2015-06-05 13:38:38 -04:00
Raul Ochoa
ebae218219 Use windshaft branch with unified error reponses format 2015-06-05 13:37:49 -04:00
Raul Ochoa
6685b759b2 Remove duplicated module.exports 2015-06-04 20:14:36 -04:00
Raul Ochoa
539b0496bf Update news 2015-06-04 12:53:59 -04:00
Raul Ochoa
3c33fac8f4 Merge pull request #307 from CartoDB/template-names
Changes rules for names in templates
2015-06-04 12:52:30 -04:00
Raul Ochoa
9613f76ef5 Keep placeholder key validation independent from name validation 2015-06-04 11:58:24 -04:00
Raul Ochoa
3f0d344313 Changes rules for names in templates
Now valid names can start with numbers and can contain dashes (-).
Closes #306
2015-06-04 10:41:40 -04:00
Raul Ochoa
823ee63c25 Merge branch 'master' into docs-revamp
Conflicts:
	docs/Map-API.md
2015-06-03 17:11:01 -04:00
Raul Ochoa
d5d76f9c63 Adds information about named map static images endpoint 2015-06-03 17:09:10 -04:00
Raul Ochoa
dfc9a6fbb4 Documentation about view in named maps' templates
Closes #287
2015-06-03 17:00:42 -04:00
Raul Ochoa
2bd9aece35 Add header for zoom + center in static maps 2015-06-03 17:00:05 -04:00
Raul Ochoa
21870c9fa2 Merge branch 'master' into docs-revamp 2015-06-03 16:57:24 -04:00
Raul Ochoa
7c315b3afd Merge pull request #303 from CartoDB/links-to-map-config
Updated links to MapConfig 1.3.0
2015-06-03 16:37:24 -04:00
Pablo Alonso Garcia
da0cdb081d Updated links to MapConfig 1.3.0 2015-06-03 10:48:42 +02:00
Carlos Matallín
9bd0a3f1c9 Update Map-API.md 2015-06-01 17:00:53 +02:00
Raul Ochoa
83992895e4 Merge pull request #301 from CartoDB/layers-documentation
Documentation about layers metadata and layer selection/filtering
2015-06-01 16:48:14 +02:00
Raul Ochoa
aa3b336e46 Stubs next version 2015-06-01 15:50:53 +02:00
Raul Ochoa
e17b374fde Release 2.4.1 2015-06-01 15:49:30 +02:00
Raul Ochoa
61158b62f1 Update news 2015-06-01 15:46:32 +02:00
Raul Ochoa
88ed43a92e Merge pull request #302 from CartoDB/upgrade-windshaft
Upgrade windshaft
2015-06-01 14:28:28 +02:00
Raul Ochoa
e5fff6b452 Merge branch 'master' into upgrade-windshaft 2015-06-01 14:19:49 +02:00
Raul Ochoa
044d49c53a Uses windshaft 0.44.1 from registry 2015-06-01 12:20:44 +02:00
Raul Ochoa
69abf8d9b1 uses windshaft's master branch 2015-06-01 11:35:57 +02:00
Andy Eschbacher
14e13899a6 made minor changes 2015-05-28 11:02:34 -04:00
Raul Ochoa
488c246222 Documentation about layers metadata and layer selection 2015-05-28 15:10:55 +02:00
Raul Ochoa
654905a79c Stubs next version 2015-05-26 15:53:07 +02:00
Raul Ochoa
12cb199803 Release 2.4.0 2015-05-26 15:52:28 +02:00
Raul Ochoa
8759cf726b Merge pull request #300 from CartoDB/upgrade-windshaft
Bumps windshaft version to 0.44.0
2015-05-26 15:50:19 +02:00
Raul Ochoa
7a45c9e434 Bumps windshaft version to 0.44.0
- adds a test to validate metadata is returned for unrolled named layers
2015-05-26 15:39:21 +02:00
Raul Ochoa
9ee69dea55 Stubs next version 2015-05-18 18:11:48 +02:00
Raul Ochoa
ebe38e977f Release 2.3.0 2015-05-18 18:10:47 +02:00
Raul Ochoa
40ad143c3e Merge pull request #299 from CartoDB/upgrade-cartodb-redis
Upgrades cartodb-redis for `global` map stats
2015-05-18 18:08:06 +02:00
Raul Ochoa
875159fa5f Upgrades cartodb-redis for global map stats 2015-05-18 17:40:41 +02:00
Raul Ochoa
c97c65de34 Fixes multilayer link 2015-05-18 11:24:06 +02:00
Raul Ochoa
25ae09b2c5 Update patch to generate routes document 2015-04-29 17:18:36 +02:00
Raul Ochoa
853c2b4b85 Adds new route for named maps static previews 2015-04-29 17:17:46 +02:00
Raul Ochoa
682db1ca75 Stubs next version 2015-04-29 15:26:56 +02:00
Raul Ochoa
d56bd8de72 Release 2.2.0 2015-04-29 15:18:47 +02:00
Raul Ochoa
1df91aee6f Merge pull request #293 from CartoDB/named-static-maps
Named static maps
2015-04-29 12:48:02 +02:00
Raul Ochoa
b64eaed7ed Enables any URL in HTTP renderer whitelist in example configurations 2015-04-29 12:15:26 +02:00
Raul Ochoa
03dae8a93a changes about tests 2015-04-29 10:35:11 +02:00
Raul Ochoa
6b93fa0575 Update news 2015-04-29 10:31:47 +02:00
Raul Ochoa
5a9a2d7449 Use windshaft's registry version 2015-04-29 10:21:23 +02:00
Raul Ochoa
8d200686fd regenerate npm-shrinkwrap.json 2015-04-29 09:59:36 +02:00
Raul Ochoa
1c2f84b0cb Rely on windshaft master branch 2015-04-29 00:20:41 +02:00
Raul Ochoa
513fa2af01 Log all named map invalidation with context 2015-04-28 17:25:07 +02:00
Raul Ochoa
7580081a64 Append stats to profiler 2015-04-28 16:14:30 +02:00
Raul Ochoa
1a66f96379 Adds custom cache control header for named map static images 2015-04-28 16:14:19 +02:00
Raul Ochoa
fde680450f Do not use headers from abaculus in combination with sendWithHeaders 2015-04-28 16:14:03 +02:00
Raul Ochoa
6843692f01 Pick format from user params 2015-04-28 16:10:50 +02:00
Raul Ochoa
1f3a073f21 Use headers from fake request 2015-04-28 16:10:30 +02:00
Raul Ochoa
7b4d41464f tests for static named maps 2015-04-27 19:15:06 +02:00
Raul Ochoa
7ae2910061 adds tests as part of the jshint target 2015-04-27 18:08:55 +02:00
Raul Ochoa
ed3517e733 fix jshint 2015-04-27 18:08:40 +02:00
Raul Ochoa
6ac3b4c005 fix jshint 2015-04-27 18:05:39 +02:00
Raul Ochoa
26545af9ae fix jshint 2015-04-27 18:03:15 +02:00
Raul Ochoa
1ee96f14ce fix jshint 2015-04-27 18:02:15 +02:00
Raul Ochoa
2250e6d608 fix jshint 2015-04-27 18:00:47 +02:00
Raul Ochoa
5ad27e4bf5 fix jshint 2015-04-27 17:58:56 +02:00
Raul Ochoa
5f765712b4 fix jshint 2015-04-27 17:54:07 +02:00
Raul Ochoa
cb2e330e0b Uses describe/it instead of suite/test 2015-04-27 17:49:15 +02:00
Raul Ochoa
6de911e5bb Adds fastly invalidation expectations in surrogate key invalidation tests 2015-04-27 17:43:46 +02:00
Raul Ochoa
9edec8ef3f Adds Fastly cache backend 2015-04-27 16:31:47 +02:00
Raul Ochoa
8e8ab09bec Clarify fastly configuration is optional 2015-04-27 16:28:28 +02:00
Raul Ochoa
c06cba81f4 SurrogateKeysCache now accepts several cache backends
- uses queue-async to parallelize the call to invalidate
2015-04-27 16:22:37 +02:00
Raul Ochoa
ad5514dd02 Pick fastly config for server options 2015-04-27 16:20:55 +02:00
Raul Ochoa
a5b9ca706c Adds new fastly cache backend 2015-04-27 16:18:50 +02:00
Raul Ochoa
5a476f9354 Adds fastly-purge dependency 2015-04-27 16:18:03 +02:00
Raul Ochoa
403039b695 Adds new configuration examples for fastly surrogate keys invalidation 2015-04-27 16:17:13 +02:00
Raul Ochoa
5ee19cc2ed Rename template maps controller to named maps to be more clear 2015-04-27 15:01:49 +02:00
Raul Ochoa
8c3f9c7ba0 Inject server options to use setDBParams 2015-04-27 14:59:41 +02:00
Raul Ochoa
b95a001e0b New static maps controller/endpoint for named maps
- loads a template
 - creates a layergroup on the fly
 - checks for view center+zoom or bounds
 - if not found it tries to estimate them
 - if fails it falls to default bounds value
 - returns an static image tagged with a surrogate key
2015-04-27 14:56:38 +02:00
Raul Ochoa
d180305e8b Exposes pgQueryRunner in server options 2015-04-27 14:54:14 +02:00
Raul Ochoa
ef8fcf7e93 Do not inject NamedMapsCacheEntry as template controller knows about them
Also do not inject pgConnection
2015-04-27 14:52:36 +02:00
Raul Ochoa
e7bd5dd644 Moves setDBParams to serverOptions so it can be reused 2015-04-27 14:47:58 +02:00
Raul Ochoa
8503a5c7c9 Tables extent API: returns estimated bounds for a list of tables 2015-04-27 12:55:20 +02:00
Raul Ochoa
2de0e5d52b Extracts psql query run to its own class to be reusable 2015-04-27 12:48:34 +02:00
Raul Ochoa
b9e4b0a90c Removes alpha version from example 2015-04-27 11:56:54 +02:00
Raul Ochoa
8fb3dc7529 Move templateName function to template maps model 2015-04-27 11:55:05 +02:00
Raul Ochoa
a897e36b91 Merge pull request #290 from CartoDB/simplify-template-names
Simplify template names
2015-04-23 12:19:21 +02:00
Raul Ochoa
446c432484 dry content type validation 2015-04-23 12:05:52 +02:00
Raul Ochoa
c49f3aaba5 DRY ifUnauthenticated method 2015-04-23 12:01:53 +02:00
Raul Ochoa
fed29b3b50 Extract finish function 2015-04-23 11:47:01 +02:00
Raul Ochoa
e7d134d70c No more {username}@{template_name} template id
It's still backwards compatible
2015-04-23 11:29:55 +02:00
Raul Ochoa
62dbce4311 Merge pull request #289 from CartoDB/issue-267
Call callback on invalid map store token for named maps
2015-04-22 18:38:58 +02:00
Raul Ochoa
5b5f7fc700 Merge pull request #288 from CartoDB/clean-templates-suite
cleans templates suite: uses describe+it instead of suite+test
2015-04-21 23:58:13 +02:00
Raul Ochoa
026a0750e3 Call callback on invalid map store token for named maps
fixes #267
2015-04-21 18:59:52 +02:00
Raul Ochoa
7045f41252 cleans templates suite: uses describe+it instead of suite+test 2015-04-21 18:28:31 +02:00
Raul Ochoa
eaf6775d9d Stubs next version 2015-04-16 18:06:55 +02:00
Raul Ochoa
ba2a9b81e9 Release 2.1.3 2015-04-16 18:02:36 +02:00
Raul Ochoa
5577600903 Upgrade windshaft and regenerate npm-shrinkwrap.json 2015-04-16 17:48:59 +02:00
Raul Ochoa
a0a455b225 Stubs next version 2015-04-15 16:13:40 +02:00
Raul Ochoa
0019ab495b Release 2.1.2 2015-04-15 16:12:42 +02:00
Raul Ochoa
cbebac1cb1 Merge pull request #286 from CartoDB/profiler-metrics
Profiler metrics improvements
2015-04-15 15:41:12 +02:00
Raul Ochoa
e2fd4aca60 Upgrade windshaft 2015-04-15 15:32:10 +02:00
Raul Ochoa
0c578a193c Remove stack for debug environment option 2015-04-14 16:44:03 +02:00
Raul Ochoa
84f579f0ec Do not add x-profiler header as it's already added by sendResponse 2015-04-14 16:41:04 +02:00
Raul Ochoa
1bf2809355 Do not check statsd_client in profiler 2015-04-14 16:40:15 +02:00
Raul Ochoa
e91bc91057 Adds test suite for x-cache-channel 2015-04-10 13:39:20 +02:00
Raul Ochoa
4f9b6be45b Update routes documentation 2015-04-10 12:00:25 +02:00
Raul Ochoa
95aa74ee34 Stubs next version 2015-04-10 10:57:31 +02:00
Raul Ochoa
e516300825 Release 2.1.1 2015-04-10 10:56:10 +02:00
Raul Ochoa
2d84d38b90 Do not add x-cache-channel header for GET template routes 2015-04-10 10:55:46 +02:00
Raul Ochoa
7cd78be094 Stubs next version 2015-04-09 15:26:21 +02:00
Raul Ochoa
c7a10b048a Release 2.1.0 2015-04-09 15:24:10 +02:00
Raul Ochoa
d143b0235b Upgrades windshaft to 0.42.0 2015-04-09 15:23:42 +02:00
Raul Ochoa
a876c82660 Fixes tests for subdomainless example config change 2015-04-08 16:09:36 +02:00
Raul Ochoa
a6c1aefecc Fixes in example configuration files:
- Puts back the still supported /tiles/template and /tiles/layergroup
 - Changes from /u/:user to /user/:user for subdomainless users
2015-04-08 16:05:33 +02:00
Raul Ochoa
9b122280e1 Stubs next version 2015-04-08 12:27:40 +02:00
Raul Ochoa
4777d1e93c Release 2.0.0 2015-04-08 12:26:07 +02:00
Raul Ochoa
97e8b54b8c Replaces render timeout fallback asset on behalf of @saleiva, thx! 2015-04-08 12:21:57 +02:00
Raul Ochoa
136f68765a Merge pull request #284 from CartoDB/issue-280
Add user from params to fakereq object
2015-04-08 11:37:14 +02:00
Raul Ochoa
0062ec99f1 Merge pull request #283 from CartoDB/render-limits
Render limits
2015-04-08 11:32:14 +02:00
Raul Ochoa
98bc95bc58 Add user from params to fakereq object so it's propagated
fixes #280
2015-04-08 11:11:48 +02:00
Raul Ochoa
47cc1de89b Uses windshaft@0.41.0 registry version 2015-04-07 17:57:21 +02:00
Raul Ochoa
69c623b5b2 Regenerate npm-shrinkwrap.json 2015-04-07 14:36:43 +02:00
Raul Ochoa
ab9ae60958 Merge branch 'master' into render-limits 2015-04-07 14:18:34 +02:00
Raul Ochoa
907ed478cf Tidy app.js 2015-04-07 13:00:20 +02:00
Raul Ochoa
2eb7529efb Pick cacheOnTimeout and render limit from mapnik config
- adds default mapnik configuration values
 - removes old top-level mapnik config, rely on renderer one
2015-04-07 12:52:33 +02:00
Raul Ochoa
1782f240ce Upgrade cartodb-redis package 2015-04-07 11:38:00 +02:00
Raul Ochoa
2ea880ce2c cacheOnTimeout only for mapnik renderer
it does not make sense to have that as a general feature
2015-04-07 11:09:35 +02:00
Raul Ochoa
775b2b75a6 Fix typo 2015-04-07 10:41:19 +02:00
Raul Ochoa
2d050eb43c Merge pull request #282 from CartoDB/remove_per_user_check
Disable per-user healthchecks
2015-04-07 10:07:25 +02:00
Luis Bosque
7934d659fb Removed more unused code from healthcheck 2015-04-06 20:49:29 +02:00
Luis Bosque
21b5ed9c8a Fixed healthcheck for jshint 2015-04-06 20:35:22 +02:00
Luis Bosque
da70839f78 Disable per-user healthchecks 2015-04-06 20:15:26 +02:00
Raul Ochoa
2e1f08d764 Adds a feature flag to cache timed out tile requests: cacheOnTimeout 2015-04-06 18:52:54 +02:00
Raul Ochoa
21072645a4 Tests for both: onTileErrorStrategy enabled and disabled 2015-04-06 18:44:57 +02:00
Raul Ochoa
e3c6569302 Adds an onTileErrorStrategy that intercepts error timeout
- returns an fallback image without error
2015-04-06 18:43:40 +02:00
Raul Ochoa
091352e75b Fix typo 2015-04-06 18:00:46 +02:00
Raul Ochoa
bf7044d723 Adds tests to test new per user limit 2015-04-01 19:36:43 +02:00
Raul Ochoa
38e4812b43 Restore previous beforeLayergroupCreate hook behaviour
Adds new rendercache's beforeRendererCreate hook
2015-04-01 19:35:55 +02:00
Raul Ochoa
b8395010a3 Adds some tests for limits based on user limits 2015-04-01 17:39:02 +02:00
Raul Ochoa
f0eeb393d6 Use windshaft render-limits branch
Bump cartodb-redis version
Regenerate npm-shrinkwrap.json as I accidentaly deleted it
2015-04-01 15:49:01 +02:00
Raul Ochoa
a9ab9f8b5c Pick render limit and add it to request context
- Extends the problematic fake request in templates
 - Picks the value in waterfall, this must be improved because:
   1. It does not make sense if there is no layers with limits
   2. If we want to include it always without considering the layer type
      we can do the operation in parallel
2015-04-01 15:11:58 +02:00
Raul Ochoa
f019f34601 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
2015-04-01 15:04:56 +02:00
Raul Ochoa
400e51f13a Removes rollbar as optional logger 2015-03-31 11:36:29 +02:00
Raul Ochoa
3234c37d62 Merge pull request #278 from CartoDB/257-remove-old-api
Remove old API
2015-03-30 19:04:27 +02:00
Raul Ochoa
e0413c3302 Prepared next release 2015-03-30 18:44:44 +02:00
Raul Ochoa
cd79fc606b Regenerate npm-shrinkwrap.json 2015-03-30 17:57:27 +02:00
Raul Ochoa
a2ac1c23f1 Tests for surrogate keys invalidation
- uses nock to mock request
 - a bit hacky because tests keep environment inconsistent
   * for intance I had to mock cartocdn to not fail in other suite
2015-03-30 17:51:17 +02:00
Raul Ochoa
69f99daa60 Mock better query tables api 2015-03-30 16:28:58 +02:00
Raul Ochoa
f1e8c9a709 Tests for cdb request 2015-03-30 16:28:37 +02:00
Raul Ochoa
1fc0545b5a Replace affected tables tests with multilayer ones 2015-03-30 15:57:53 +02:00
Raul Ochoa
b599e67c35 Replaces affected tables test with multilayer one 2015-03-30 15:27:40 +02:00
Raul Ochoa
85804f9854 Fixes jshint 2015-03-30 15:07:49 +02:00
Raul Ochoa
7df3658d41 Invalid json lzma test 2015-03-30 15:06:00 +02:00
Raul Ochoa
c92e786a5f Replace lzma test with multilayer 2015-03-30 15:01:17 +02:00
Raul Ochoa
d6ef0b7457 Replaces server tests for user db connection with multilayer 2015-03-30 14:39:26 +02:00
Raul Ochoa
434d6d4110 Extract request function 2015-03-30 13:42:59 +02:00
Raul Ochoa
55a78899a4 Removes sql api specific test 2015-03-30 13:39:47 +02:00
Raul Ochoa
124133ceca Replace zoom test with multilayer one 2015-03-30 13:38:34 +02:00
Raul Ochoa
2b9f2ee66c Removes tests as they are covered in multilayer suite 2015-03-30 13:35:20 +02:00
Raul Ochoa
70d1a30c64 style 2015-03-30 13:30:57 +02:00
Raul Ochoa
2161bbf8e9 Remove comments 2015-03-30 13:30:44 +02:00
Raul Ochoa
41521b6776 No need to test style deletion 2015-03-30 12:42:57 +02:00
Raul Ochoa
ec2fcad2e0 No need to test good style cases 2015-03-30 12:42:15 +02:00
Raul Ochoa
ecc67b1d0f Replace test for multiple cartocss errors 2015-03-30 12:41:34 +02:00
Raul Ochoa
4ea1199014 it instead of test 2015-03-30 12:32:48 +02:00
Raul Ochoa
e7544e84c2 Replace server test with multilayer one 2015-03-30 12:32:09 +02:00
Raul Ochoa
ada616ee6a jshint adds mocha globals 2015-03-30 12:31:44 +02:00
Raul Ochoa
000a248ab4 Removes test as is covered in "layergroup creation fails if CartoCSS is bogus" 2015-03-30 12:24:40 +02:00
Raul Ochoa
ff515f8c12 No need to have a test for empty cartocss 2015-03-30 12:20:04 +02:00
Raul Ochoa
848bfacc2d no need to check it used post for long queries 2015-03-30 12:15:26 +02:00
Raul Ochoa
da4b1d5a0f jshint 2015-03-30 12:13:59 +02:00
Raul Ochoa
b2d9e5e822 Merge branch 'master' into 257-remove-old-api 2015-03-30 11:57:03 +02:00
Raul Ochoa
d0313a4228 Remove gc_prob as it is no longer used in grainstore 2015-03-27 17:59:55 +01:00
Raul Ochoa
1f53884722 Remove metrics for authorizedBy* 2015-03-27 17:57:46 +01:00
Raul Ochoa
a664a1c550 Merge pull request #276 from CartoDB/disable_health_check
Return failed health checks with disabled file
2015-03-27 17:27:31 +01:00
Raul Ochoa
d210643d63 Only required query params 2015-03-26 17:19:16 +01:00
Raul Ochoa
4be0a70362 Do not append interactivity to params, it is no longer
it will be used from mapconfig layer definition
2015-03-26 13:05:35 +01:00
Luis Bosque
09e0f86936 jshint fixes 2015-03-25 18:41:27 +01:00
Luis Bosque
e3b7027b24 Remove unnecesary variable in health check 2015-03-25 18:23:03 +01:00
Luis Bosque
0f30b7d7ef Return failed health checks with disabled file 2015-03-25 18:19:40 +01:00
Raul Ochoa
3012b99e15 Remove varnish emu 2015-03-24 10:39:23 +01:00
Raul Ochoa
f683e39aea Remove api sql emulator 2015-03-24 10:38:14 +01:00
Raul Ochoa
985973dfda Split old api and basic endpoints 2015-03-23 19:28:34 +01:00
Raul Ochoa
07bc281e25 Remove check table privacy 2015-03-23 19:19:46 +01:00
Raul Ochoa
1d433bf5b2 Remove table param from generateCacheChannel 2015-03-23 18:58:57 +01:00
Raul Ochoa
d5e20ef558 Remove cache_policy query param 2015-03-23 18:40:59 +01:00
Raul Ochoa
36ea58e750 no longer possible to set cache_buster request param 2015-03-23 18:03:19 +01:00
Raul Ochoa
e1e5f87123 No longer possible to set sql param 2015-03-23 18:02:41 +01:00
Raul Ochoa
ea3d2124dc No sql param in generateCacheChannel 2015-03-23 17:56:51 +01:00
Raul Ochoa
415d0c42d5 jshint 2015-03-23 17:56:18 +01:00
Raul Ochoa
c19f652ff3 Remove some old accepted query params 2015-03-23 17:54:37 +01:00
Raul Ochoa
f5f7be627f Move userByReq to its own model 2015-03-23 17:35:09 +01:00
Raul Ochoa
09b3f0a862 Skip server suite for now until we decide what tests we should port 2015-03-23 17:27:24 +01:00
Raul Ochoa
d9ab1e8810 Deprecates old config URLs: /tiles/template and /tiles/layergroup
Moves all tests to run on new URLs
Deprecated base_url_legacy in config, it will keep working tho
2015-03-23 15:54:45 +01:00
Raul Ochoa
23f5be6c33 Remove config and sql api backend 2015-03-23 15:00:33 +01:00
Raul Ochoa
07297f6bda Remove cdbQueryTablesFromPostgres option, now uses it by default 2015-03-23 14:44:42 +01:00
Raul Ochoa
02bc7b9fbf Remove per-table varnish invalidation 2015-03-23 14:27:41 +01:00
Raul Ochoa
0e3f72ce0b PHONY coverage 2015-03-23 14:21:08 +01:00
Raul Ochoa
f311f6d4df Merge branch 'master' into 257-remove-old-api 2015-03-23 14:13:11 +01:00
Raul Ochoa
65c6559b1a Merge pull request #275 from CartoDB/coverage
Add option to generate coverage metrics using istanbul module
2015-03-23 14:12:47 +01:00
Raul Ochoa
53d92fe70e Add option to generate coverage metrics using istanbul module 2015-03-23 14:05:29 +01:00
Raul Ochoa
6d32199c53 Remove get style tests 2015-03-23 12:42:39 +01:00
Raul Ochoa
25e4e3bd33 jshint 2015-03-23 12:40:24 +01:00
Raul Ochoa
0321884795 Re-generate npm-shrinkwrap.json to use windshaft/master.
A lot of tests from acceptance/server.js will fail
2015-03-23 12:29:42 +01:00
Raul Ochoa
5f6185dd51 Merge branch 'master' into 257-remove-old-api
Conflicts:
	lib/cartodb/cartodb_windshaft.js
	lib/cartodb/server_options.js
	package.json
2015-03-23 12:24:10 +01:00
Raul Ochoa
63ba75f703 Merge pull request #274 from CartoDB/url_rewrite
allow urls like /u/:user/
2015-03-20 12:21:41 +01:00
Raul Ochoa
9b4acf99d5 Adds example configuration to accept user param in /tiles/layergroup
That is used in testing. Tests should start moving to /api/v1/map.
2015-03-20 00:34:30 +01:00
Raul Ochoa
9ba53dc4cf Adds user param to params whitelist and uses localhost user for tests 2015-03-20 00:30:56 +01:00
javi
2f44dfe1da updated test config 2015-03-18 18:36:44 +01:00
javi
b891ae19f4 adding a bunch of test for layer group url 2015-03-18 18:15:26 +01:00
javi
00cf83dc45 try to fix test, take 3 2015-03-18 17:53:49 +01:00
javi
72294fbd25 refined tests 2015-03-18 17:26:33 +01:00
javi
5af09fc2bf small refactor in tests 2015-03-18 17:04:25 +01:00
javi
c1c6d493b7 allow urls like /u/:user/ 2015-03-18 15:54:05 +01:00
Raul Ochoa
a30ed5ce04 Merge pull request #271 from CartoDB/iri/https-change
Switching to HTTPS
2015-03-16 12:33:32 +01:00
Carla
7c35d5cd32 Switching to HTTPS
https://github.com/CartoDB/docs/issues/182
2015-03-16 12:26:28 +01:00
Raul Ochoa
2e67633ca8 Merge pull request #270 from CartoDB/jshint
Enable jshint
2015-03-16 00:48:15 +01:00
Raul Ochoa
87782b400d jshint to be tested by default 2015-03-16 00:38:29 +01:00
Raul Ochoa
b6d3785599 Fix Max cyclomatic complexity value 2015-03-16 00:36:38 +01:00
Raul Ochoa
645a2cd442 120 chars lines 2015-03-16 00:27:14 +01:00
Raul Ochoa
8c09dfd230 No capitalize step 2015-03-16 00:21:55 +01:00
Raul Ochoa
336491b54c Remove unused vars 2015-03-16 00:16:36 +01:00
Raul Ochoa
4365c1dbc2 Define mapnikXmlParams variable 2015-03-16 00:07:05 +01:00
Raul Ochoa
3c56c1fab3 Adds next param 2015-03-16 00:05:01 +01:00
Raul Ochoa
0a331cee37 do not redefine vars 2015-03-16 00:03:59 +01:00
Raul Ochoa
d7f5c40645 Triple === 2015-03-16 00:00:02 +01:00
Raul Ochoa
5df24e7f27 Remove Unreachable 'break' after 'return' 2015-03-15 23:58:38 +01:00
Raul Ochoa
406a1ffb0b no global replaceVars func 2015-03-15 23:58:12 +01:00
Raul Ochoa
438ecd5598 jshint: fix Function declarations should not be placed in blocks 2015-03-15 23:56:14 +01:00
Raul Ochoa
bd1c24ee1c jshint: Remove Confusing use of '!' 2015-03-15 23:52:46 +01:00
Raul Ochoa
e561f77d4d jshint: fix Dot notation 2015-03-15 23:49:32 +01:00
Raul Ochoa
d03a2c64a6 jshint: fix Missing semicolon 2015-03-15 23:46:59 +01:00
Raul Ochoa
fda8afdaf2 jshint: fix Bad line breaking 2015-03-15 23:44:45 +01:00
Raul Ochoa
e4da13189d Adds jshint
- dependency
 - makefile target
 - relax some rules
Work in progress
2015-03-15 23:37:03 +01:00
Raul Ochoa
f35d328dbf Update Routes.md 2015-03-12 12:58:46 +01:00
Raul Ochoa
d09998cce1 Extra notes about optional but deprecated endpoints
- /tiles/layergroup
- /tiles/template
2015-03-12 12:57:29 +01:00
Raul Ochoa
7a01d75cd8 Attributes route 2015-03-12 12:42:13 +01:00
Raul Ochoa
83e5d889a7 Some notes about routes 2015-03-12 12:38:06 +01:00
Raul Ochoa
f1eed600d5 Adds routes document (WIP) 2015-03-12 12:20:42 +01:00
Raul Ochoa
75a980ff1d Stubs next version 2015-03-11 17:15:44 +01:00
Raul Ochoa
9d9aafbcb3 Release 1.30.0 2015-03-11 17:14:41 +01:00
Raul Ochoa
8e9d9113d7 Upgrades windshaft to 0.40.0 2015-03-11 16:19:07 +01:00
Raul Ochoa
62661c633c Adds script/tool to go from lzma base64 encoded string to mapconfig 2015-03-11 15:16:24 +01:00
Raul Ochoa
38e35a6d61 Specific dependencies for building node-canvas 2015-03-11 14:25:36 +01:00
Raul Ochoa
0d5242d12b Add enabledFeatures configuration to the example configuration files
- Enables querytables from postgres as default
2015-03-09 14:42:47 +01:00
Raul Ochoa
edde869a68 Update CDB_QueryTables 2015-03-09 14:42:30 +01:00
Raul Ochoa
6d4eb23696 Improve readme
- Requirements bumped
 - Delete URLs documentation and point to docs directory.
2015-03-09 14:37:26 +01:00
Raul Ochoa
1979697551 Stubs next version 2015-03-09 14:08:38 +01:00
Raul Ochoa
661df294e1 Release 1.29.0 2015-03-09 14:07:47 +01:00
Raul Ochoa
39dc9a316d Merge pull request #268 from CartoDB/upgrade-windshaft
Upgrade windshaft
2015-03-09 14:03:31 +01:00
Raul Ochoa
cfe434c8ed Use published version 2015-03-09 13:52:47 +01:00
Raul Ochoa
53d7276136 Force bash 2015-03-08 11:36:31 +01:00
Raul Ochoa
e95167a049 Force bash 2015-03-08 11:36:17 +01:00
Raul Ochoa
e82131f4e8 Upgrades windshaft@0.39.0 2015-03-06 10:45:01 +01:00
Raul Ochoa
cbd44192c9 Use bash in Makefile targets 2015-02-26 17:00:50 +01:00
Raul Ochoa
f006d09f31 Update windshaft to version 0.38.2 2015-02-26 12:08:48 +01:00
Raul Ochoa
620160c44e Ignore npm-debug.log 2015-02-26 12:05:17 +01:00
Raul Ochoa
6ae2c2630a Upgrade windshaft to 0.38.1 2015-02-25 19:15:55 +01:00
Raul Ochoa
a287c84500 Upgrades windshaft to 0.38.0 2015-02-23 12:59:00 +01:00
Raul Ochoa
62e435fd9e Improve installation feedback as we did in Windshaft 2015-02-23 12:08:08 +01:00
Raul Ochoa
65702de64d Update to latest test/support/sql/CDB_QueryTables.sql 2015-02-23 12:06:47 +01:00
Raul Ochoa
ef2b45621b Merge branch 'master' into 257-remove-old-api
Conflicts:
	package.json
2015-02-20 15:24:13 +01:00
Raul Ochoa
1771313bea Stubs next version 2015-02-20 10:32:12 +01:00
Raul Ochoa
b0624582d9 Release 1.28.5 2015-02-20 10:30:27 +01:00
Raul Ochoa
10acdc4615 Upgrades windshaft to 0.37.5 2015-02-20 10:30:11 +01:00
Raul Ochoa
d96ed3511e Merge pull request #266 from namessanti/patch-1
Corrected DPI limits and jpeg quality
2015-02-19 19:26:02 +01:00
Santiago Giraldo Anduaga
e0dba85f67 Corrected DPI limits and jpeg quality
Corrected DPI limits and JPEG quality

@andrewxhill
2015-02-19 13:05:39 -05:00
Raul Ochoa
989e752959 Display master build status
it doesn't make sense to display errors from other branches/pr
2015-02-18 15:12:43 +01:00
Raul Ochoa
71efe2109c Merge branch 'master' into 257-remove-old-api
Conflicts:
	lib/cartodb/cartodb_windshaft.js
	package.json
2015-02-18 14:51:21 +01:00
Raul Ochoa
5da239a2eb Stubs next version 2015-02-18 11:55:35 +01:00
Raul Ochoa
5c2e5c0d05 Release 1.28.4 2015-02-18 11:54:40 +01:00
Raul Ochoa
27d6d636cf Upgrades to windshaft 0.37.4 2015-02-18 11:53:56 +01:00
Raul Ochoa
5db7f002e6 Stubs next version 2015-02-17 19:44:23 +01:00
Raul Ochoa
ac1f8f8497 Release 1.28.3 2015-02-17 19:43:17 +01:00
Raul Ochoa
ee6bd8c561 Merge pull request #265 from CartoDB/263-fix-403-errors-with-named-layers
Test for named layers and interactivity
2015-02-17 19:41:34 +01:00
Raul Ochoa
7f20e296a3 Upgrades windshaft to 0.37.3 and adds a test for named layers and interactivity
Closes #263
2015-02-17 19:28:05 +01:00
Raul Ochoa
2e577343d2 Update to latest CDB_QueryTables 2015-02-17 18:55:34 +01:00
Raul Ochoa
dc14248de2 Stubs next version 2015-02-17 16:17:32 +01:00
Raul Ochoa
9536669053 Release 1.28.2 2015-02-17 16:16:19 +01:00
Raul Ochoa
11db363bfb Upgrades windshaft to 0.37.2 2015-02-17 16:07:49 +01:00
Raul Ochoa
d2961430f3 Stubs next version 2015-02-17 12:20:51 +01:00
Raul Ochoa
2b7bd58fd5 Release 1.28.1 2015-02-17 12:20:07 +01:00
Raul Ochoa
055932d38e Upgrades windshaft to 0.37.1 2015-02-17 12:19:16 +01:00
Raul Ochoa
c65a29acf4 Stubs next version 2015-02-17 11:04:41 +01:00
Raul Ochoa
27cda49fd8 Release 1.28.0 2015-02-17 11:02:29 +01:00
Raul Ochoa
2d7b706507 Merge pull request #217 from strk/master-tolerant-configure
Have ./configure tolerate unknown options
2015-02-16 18:55:45 +01:00
Raul Ochoa
3ea3638fe9 Merge pull request #218 from strk/master-own-testuser
Use a windshaft-specific name for the test user
2015-02-16 18:55:34 +01:00
Raul Ochoa
1f9387bb68 Rely on published versions 2015-02-16 15:02:39 +01:00
Raul Ochoa
00542bbc57 Upgrades windshaft to 0.37.0 2015-02-16 14:57:03 +01:00
Raul Ochoa
5be8afbbeb Merge pull request #261 from CartoDB/private-cdb_tablemetadata
Authenticated query to retrieve last update
2015-02-16 14:29:10 +01:00
Raul Ochoa
9f7dcc2354 Update news 2015-02-16 14:21:56 +01:00
Raul Ochoa
21b3f44441 Merge branch 'master' into private-cdb_tablemetadata 2015-02-16 14:18:56 +01:00
Raul Ochoa
f295f847d1 Stubs next version 2015-02-16 12:15:08 +01:00
Raul Ochoa
0478905689 Release 1.27.0 2015-02-16 12:14:24 +01:00
Raul Ochoa
d311dd4245 Use PgConnection to set db auth
No need to use request context anymore
X-Cache-Channel will be set now even for private tables: fixes #253
2015-02-16 11:57:53 +01:00
Raul Ochoa
b25bb03cdf Merge branch 'master' into private-cdb_tablemetadata
Conflicts:
	lib/cartodb/server_options.js
	test/support/sql/windshaft.test.sql
2015-02-16 11:41:45 +01:00
Raul Ochoa
afa625e3d2 Merge pull request #260 from CartoDB/medusa-improvements
Medusa improvements
2015-02-16 11:34:23 +01:00
Raul Ochoa
e08b1ea1a0 Update news with changes in branch 2015-02-16 11:21:22 +01:00
Raul Ochoa
c6d328ee07 Upgrades to windshaft 0.36.0 2015-02-16 10:55:38 +01:00
Raul Ochoa
8d10d0f760 Remove draft 2015-02-16 10:54:53 +01:00
Raul Ochoa
a2a09979e4 Remove dump render cache stats 2015-02-11 18:47:11 +01:00
Raul Ochoa
597cb5286d No more before/after state change actions as there is no longer
a style change
2015-02-11 18:44:09 +01:00
Raul Ochoa
a0243e0bf7 Rely on windshaft's remove old api branch 2015-02-11 18:43:47 +01:00
Raul Ochoa
0f668aabf1 Rely on windshaft's master branch as everything for medusa is already there 2015-02-11 14:48:06 +01:00
Raul Ochoa
59dfd11e5b Remove geom_type retrieval 2015-02-10 16:57:43 +01:00
Raul Ochoa
b1b57d6f24 Regenerate npm-shrinkwrap.json 2015-02-10 11:41:22 +01:00
Raul Ochoa
636591ecbb Removes flush_cache endpoint 2015-02-10 00:13:50 +01:00
Raul Ochoa
a4eade31a2 Removes map_metadata endpoint 2015-02-10 00:08:08 +01:00
Raul Ochoa
ba0f394a48 Remove infowindow endpoint 2015-02-10 00:03:44 +01:00
Raul Ochoa
75c4153f9b No need to retrieve api key externally, QueryTablesApi takes care 2015-02-09 19:41:38 +01:00
Raul Ochoa
8e038b0323 Merge pull request #259 from CartoDB/empty-flush_cache-endpoint
Empty flush cache endpoint
2015-02-09 19:40:50 +01:00
Raul Ochoa
0a994c731c Merge branch 'master' into empty-flush_cache-endpoint 2015-02-09 19:05:25 +01:00
Raul Ochoa
13ae1b4067 Remove flush_cache tool as it doesn't make sense anymore 2015-02-09 19:04:38 +01:00
Raul Ochoa
742a9744ea Remove select permission for publicuser 2015-02-09 18:56:01 +01:00
Raul Ochoa
8ed864ad18 apt-get update before apt-get install 2015-02-09 18:49:58 +01:00
Raul Ochoa
87638168ff apt-get update before apt-get install 2015-02-09 18:39:24 +01:00
Raul Ochoa
8364da683a Merge branch 'master' into medusa-improvements 2015-02-09 18:39:14 +01:00
Raul Ochoa
6eec5822f0 Create CREATE EXTENSION plpythonu for tests 2015-02-09 18:36:47 +01:00
Raul Ochoa
d667dbcc2f Merge branch 'master' into private-cdb_tablemetadata 2015-02-09 18:36:19 +01:00
Raul Ochoa
40de1a8f86 Create CREATE EXTENSION plpythonu for tests 2015-02-09 18:34:28 +01:00
Raul Ochoa
b53c25e514 Merge pull request #258 from CartoDB/stop-testing-node-0.8
Don't test against node 0.8
2015-02-09 15:09:57 +01:00
Raul Ochoa
81919706ea Adds default image placeholder for http renderer to use as fallback 2015-02-09 15:08:36 +01:00
Raul Ochoa
d38fc16b57 Don't test against node 0.8 2015-02-09 14:49:01 +01:00
Raul Ochoa
90b22b2718 QueryTables and last updated_at retrieved with user
Move setDBAuth and setDBConn to PgConnection entity
 - It uses cartodb-redis to retrieve datasource configuration
Start using it in ServerOptions, TemplateMaps and QueryTablesApi
QueryTablesApi don't receive anymore the connection/credentials
 - It will always use an authenticated query to retrieve last update
 - That will allow to query affected private tables last update
2015-02-09 14:46:52 +01:00
Raul Ochoa
04af57cab9 Add some entries to cdb_tablemetadata for tables being used in tests 2015-02-09 14:38:59 +01:00
Raul Ochoa
d40b15454b Run some tests only if they are using the SQL API 2015-02-09 14:38:29 +01:00
Raul Ochoa
e1e925bd9e Run postgresql/sql-api dependant tests against two implementations
This time for real.
2015-02-09 14:33:17 +01:00
Raul Ochoa
151968ae13 Merge branch 'master' into private-cdb_tablemetadata 2015-02-06 18:42:18 +01:00
Raul Ochoa
547782eea5 Use windshaft's plain-renderer-plus-http-default-plus-per-layer-datasource
branch to pick latest developments for medusa
2015-02-06 12:03:17 +01:00
Raul Ochoa
6bd967e9fb Merge branch 'master' into medusa-improvements
Conflicts:
	lib/cartodb/server_options.js
2015-02-06 12:01:28 +01:00
Raul Ochoa
13f5fda1b8 Merge pull request #255 from CartoDB/empty-flush_cache-endpoint
Remove per table flush cache endpoint
2015-02-05 17:37:56 +01:00
Raul Ochoa
673bd4f3f2 Add querytables and cdb_tablemetadata for proper testing its integration 2015-02-05 17:21:38 +01:00
Raul Ochoa
d56affae2d Merge pull request #256 from CartoDB/private-cdb_tablemetadata
Remove no longer needed method from query_tables_api
2015-02-05 17:21:21 +01:00
Raul Ochoa
09527b6808 Remove no longer needed method from query_tables_api 2015-02-05 17:08:20 +01:00
Raul Ochoa
d065ace036 Remove per table flush cache endpoint 2015-02-05 17:05:50 +01:00
Raul Ochoa
2736b93c69 test to validate it's not possible to override authorization
with a crafted layergroup
2015-02-05 16:47:37 +01:00
Raul Ochoa
10b7ab307e Merge pull request #254 from CartoDB/add_cdbjs_layer
Added layer source info for cartodb.js
2015-02-05 15:23:53 +01:00
Andy Eschbacher
56c24a738a Added layer source info for cartodb.js 2015-02-04 16:23:34 -05:00
Raul Ochoa
fa8b27231c Removed unused/old template lock functionality 2015-02-04 19:36:16 +01:00
Raul Ochoa
c17af23a40 A non empty datasource from MapConfigNamedLayersAdapter.getLayers
means the affected tables can have private tables involved.
That implies QueryTablesApi will need the proper user to use
CDB_QueryTables. So we store it in a request context to use it in
the afterLayergroupCreate call.

Tiles for these layergroups will fail to add a X-Cache-Channel
header because it won't be possible to use the proper user within
those tiles. Ok, they will fail if they are not requested through
the same tiler instance because if they are they most likely will
reuse the in memory cache.

See https://github.com/CartoDB/Windshaft-cartodb/issues/253
2015-02-04 19:31:20 +01:00
Raul Ochoa
fbecc11aa5 Do not use the SQL API Emulator for testing named layers as it hides
an integration issue with cdb_querytables
2015-02-04 19:01:14 +01:00
Raul Ochoa
8cacc3bb9e Merge branch 'master' into 239-mapconfig-named-maps-extension 2015-02-04 18:59:07 +01:00
Raul Ochoa
a82af16347 Adds a template test with http layer 2015-02-04 18:57:46 +01:00
Raul Ochoa
5018d32af6 Add querytables and cdb_tablemetadata for proper testing its integration 2015-02-04 18:52:37 +01:00
Raul Ochoa
2c7bc6adde Datasource to give per-layer authentication in named layers
Make beforeLayergroupCreate to return a datasource with different
 authentication for the different layers.
 - Named layers will get access to private tables in case it's needed

Changes in MapConfigNamedLayersAdapter:
  - It will retrieve the dbAuth params only if named layers are present so
  there is no extra overhead for normal layers
  - Rename queue function signature from `callback` to `done` so it is easier
  to follow the code

Add several tests to validate `named` layers authentication
2015-02-04 11:30:36 +01:00
Raul Ochoa
58f9f5f7a8 Remove unused object, rename suite 2015-02-03 14:16:55 +01:00
Raul Ochoa
e4e633cf86 Fix next reference 2015-02-02 17:44:15 +01:00
Raul Ochoa
4ca5c5fa3c Merge pull request #252 from namessanti/api-test-doc
Static Maps API
2015-02-02 15:50:44 +01:00
Raul Ochoa
1bb0d8738e Add test case for layers with private tables 2015-02-02 14:38:26 +01:00
Raul Ochoa
4949616c4e Some acceptance tests, http_status = 403 should not happen in adapter,
needs improvement
2015-01-30 19:29:45 +01:00
Raul Ochoa
12c5d835c5 Fix integration tests as I messed with the filename 2015-01-30 19:11:54 +01:00
Raul Ochoa
87eaeb0074 Some integration tests for different cases in named layers type 2015-01-30 18:57:01 +01:00
Raul Ochoa
8b07156a2d Make templateMaps available in cartodb windshaft 2015-01-30 16:51:09 +01:00
Raul Ochoa
358b296750 Remove beforeEach and afterEach, in combination with suite they are
triggered for every single test even outside of the suite they were
invoked in.
2015-01-30 16:50:06 +01:00
Raul Ochoa
d0ef87b0cf Add a before layergroup creation action to allow first level named
maps layer type to be extended as other layers
2015-01-30 15:31:49 +01:00
Raul Ochoa
e28fe1fdc0 Initialize template maps in server options 2015-01-30 15:30:13 +01:00
Raul Ochoa
aecb07b008 Create redis pool in server options when not supplied 2015-01-30 15:28:55 +01:00
Raul Ochoa
5573dfda84 Add queue-async dependency 2015-01-30 15:26:27 +01:00
Raul Ochoa
7a22973258 Use windshaft's before-layergroup-step branch 2015-01-30 12:59:26 +01:00
Raul Ochoa
f099a69df3 Add limitations for named maps of the user account 2015-01-30 11:27:16 +01:00
Raul Ochoa
938b6579c0 MapConfig named maps extension specification (draft) 2015-01-30 11:17:29 +01:00
Raul Ochoa
e445d0de01 Stubs next version 2015-01-28 17:37:38 +01:00
Santiago Giraldo
6a39893e20 Brittany reviewed text and markdown, added basemap and limit information 2015-01-23 16:15:30 -05:00
Santiago Giraldo
2e84d18c3c Updated Maps API v2 2015-01-22 13:44:13 -05:00
Santiago Giraldo
b26fe87430 added-static-api-doc 2015-01-22 12:07:34 -05:00
Sandro Santilli
e9f5c60719 Use a windshaft-specific name for the test user
Avoids problems with running cartodb-sql-api tests after
windshaft-cartodb tests (as the test database is not cleaned up)
2014-10-08 10:31:32 +02:00
Sandro Santilli
9b06ac833a Have ./configure tolerate unknown options 2014-10-08 10:10:58 +02:00
275 changed files with 36499 additions and 8964 deletions

2
.gitignore vendored
View File

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

4
.jshintignore Normal file
View File

@@ -0,0 +1,4 @@
test/results/
test/monkey/
test/benchmark.js
test/support/

95
.jshintrc Normal file
View File

@@ -0,0 +1,95 @@
{
// // 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" : 6, // {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 predefined global variables
"predef": [
"-console", // disallows console, use debug
"beforeEach",
"afterEach",
"before",
"after",
"describe",
"it"
]
}

View File

@@ -1,9 +1,18 @@
sudo: false
addons:
postgresql: "9.3"
apt:
packages:
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
before_install:
- sudo apt-get install -y pkg-config libcairo2-dev libjpeg8-dev libgif-dev
- npm install -g npm@2
- createdb template_postgis
- createuser publicuser
- psql -c "CREATE EXTENSION postgis" template_postgis
env:
@@ -11,7 +20,6 @@ env:
language: node_js
node_js:
- "0.8"
- "0.10"
notifications:

11
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,11 @@
Contributing
---
The issue tracker is at [github.com/CartoDB/Windshaft-cartodb](https://github.com/CartoDB/Windshaft-cartodb).
We love pull requests from everyone, see [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/#contributing).
## Submitting Contributions
* You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://cartodb.com/contributing).

View File

@@ -1,12 +1,11 @@
1. Test (make clean all check), fix if broken before proceeding
2. Ensure proper version in package.json
3. Ensure NEWS section exists for the new version, review it, add release date
4. Drop npm-shrinkwrap.json
5. Run npm shrinkwrap to recreate npm-shrinkwrap.json
6. Commit package.json, npm-shrinwrap.json, NEWS
7. git tag -a Major.Minor.Patch # use NEWS section as content
8. Announce on cartodb@googlegroups.com
9. Stub NEWS/package for next version
4. Recreate npm-shrinkwrap.json with: `npm install --no-shrinkwrap && npm shrinkwrap`
5. Commit package.json, npm-shrinwrap.json, NEWS
6. git tag -a Major.Minor.Patch # use NEWS section as content
7. Announce on cartodb@googlegroups.com
8. Stub NEWS/package for next version
Versions:

53
INSTALL.md Normal file
View File

@@ -0,0 +1,53 @@
# Installing Windshaft-CartoDB #
## Requirements ##
Make sure that you have the requirements needed. These are
- Core
- Node.js >=0.8
- npm >=1.2.1 <2.0.0
- 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](https://github.com/CartoDB/Windshaft#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
- For cache control (optional)
- CartoDB 0.9.5+ (for `CDB_QueryTables`)
- Varnish (http://www.varnish-cache.org)
On Ubuntu 14.04 the dependencies can be installed with
```shell
sudo apt-get update
sudo apt-get install -y make g++ pkg-config git-core \
libgif-dev libjpeg-dev libcairo2-dev \
libhiredis-dev redis-server \
nodejs nodejs-legacy npm \
postgresql-9.3-postgis-2.1 postgresql-plpython-9.3 postgresql-server-dev-9.3
```
On Ubuntu 12.04 the [cartodb/cairo PPA](https://launchpad.net/~cartodb/+archive/ubuntu/cairo) may be useful.
## PostGIS setup ##
A `template_postgis` database is expected. One can be set up with
```shell
createdb --owner postgres --template template0 template_postgis
psql -d template_postgis -c 'CREATE EXTENSION postgis;'
```
## Build/install ##
To fetch and build all node-based dependencies, run:
```
npm install
```
Note that the ```npm install``` step will populate the node_modules/
directory with modules, some of which being compiled on demand. If you
happen to have startup errors you may need to force rebuilding those
modules. At any time just wipe out the node_modules/ directory and run
```npm install``` again.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014, Vizzuality
Copyright (c) 2015, CartoDB
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,7 +1,10 @@
srcdir=$(shell pwd)
SHELL=/bin/bash
pre-install:
@$(SHELL) ./scripts/check-node-canvas.sh
all:
npm install
@$(SHELL) ./scripts/install.sh
clean:
rm -rf node_modules/*
@@ -15,21 +18,36 @@ config.status--test:
config/environments/test.js: config.status--test
./config.status--test
check-local: config/environments/test.js
./run_tests.sh ${RUNTESTFLAGS} \
test/unit/cartodb/*.js \
test/acceptance/*.js
TEST_SUITE := $(shell find test/{acceptance,integration,unit} -name "*.js")
TEST_SUITE_UNIT := $(shell find test/unit -name "*.js")
TEST_SUITE_INTEGRATION := $(shell find test/integration -name "*.js")
TEST_SUITE_ACCEPTANCE := $(shell find test/acceptance -name "*.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
test: config/environments/test.js
@echo "***tests***"
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} $(TEST_SUITE)
check-full: check-local check-submodules
test-unit: config/environments/test.js
@echo "***tests***"
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} $(TEST_SUITE_UNIT)
check: check-local
test-integration: config/environments/test.js
@echo "***tests***"
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} $(TEST_SUITE_INTEGRATION)
test-acceptance: config/environments/test.js
@echo "***tests***"
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} $(TEST_SUITE_ACCEPTANCE)
jshint:
@echo "***jshint***"
@./node_modules/.bin/jshint lib/ test/ app.js
test-all: jshint test
coverage:
@RUNTESTFLAGS=--with-coverage make test
check: test
.PHONY: pre-install test jshint coverage

1092
NEWS.md

File diff suppressed because it is too large Load Diff

117
README.md
View File

@@ -1,39 +1,21 @@
Windshaft-CartoDB
==================
[![Build Status](http://travis-ci.org/CartoDB/Windshaft-cartodb.png)]
(http://travis-ci.org/CartoDB/Windshaft-cartodb)
[![Build Status](https://travis-ci.org/CartoDB/Windshaft-cartodb.svg?branch=master)](https://travis-ci.org/CartoDB/Windshaft-cartodb)
This is the CartoDB map tiler. It extends Windshaft with some extra
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-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]
- 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)
Install
-------
See [INSTALL.md](INSTALL.md) for detailed installation instructions.
Configure
---------
@@ -44,23 +26,15 @@ see ```./configure --help``` to see available options.
Look at lib/cartodb/server_options.js for more on config
Build/install
-------------
Upgrading
---------
To fetch and build all node-based dependencies, run:
Checkout your commit/branch. If you need to reinstall dependencies (you can check [NEWS](NEWS.md)) do the following:
```
git clone
npm install
rm -rf node_modules; npm install
```
Note that the ```npm install``` step will populate the node_modules/
directory with modules, some of which being compiled on demand. If you
happen to have startup errors you may need to force rebuilding those
modules. At any time just wipe out the node_modules/ directory and run
```npm install``` again.
Run
---
@@ -75,59 +49,34 @@ 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
[CartoDB's Map Gallery](http://cartodb.com/gallery/) showcases several examples of visualisations built on top of this.
Args:
Contributing
---
* 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)
See [CONTRIBUTING.md](CONTRIBUTING.md).
### Developing with a custom windshaft version
**INFOWINDOW**
If you plan or want to use a custom / not released yet version of windshaft (or any other dependency) the best option is
to use `npm link`. You can read more about it at [npm-link: Symlink a package folder](https://docs.npmjs.com/cli/link).
[GET] subdomain.cartodb.com/tiles/:table_name/infowindow
**Quick start**:
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.
```shell
~/windshaft-directory $ npm install
~/windshaft-directory $ npm link
~/windshaft-cartodb-directory $ npm link windshaft
```

145
app.js
View File

@@ -1,56 +1,72 @@
/*
* Windshaft-CartoDB
* ===============
*
* ./app.js [environment]
*
* environments: [development, production]
*/
var path = require('path'),
fs = require('fs'),
RedisPool = require('redis-mpool')
;
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;
// sanity check
if (ENV != 'development' && ENV != 'production' && ENV != 'staging' ){
console.error("\nnode app.js [environment]");
console.error("environments: development, production, staging\n");
process.exit(1);
}
var http = require('http');
var https = require('https');
var path = require('path');
var fs = require('fs');
var _ = require('underscore');
var ENVIRONMENT;
if ( process.argv[2] ) {
ENVIRONMENT = process.argv[2];
} else if ( process.env.NODE_ENV ) {
ENVIRONMENT = process.env.NODE_ENV;
} else {
ENVIRONMENT = 'development';
}
// jshint undef:false
var log = console.log.bind(console);
var logError = console.error.bind(console);
// jshint undef:true
var availableEnvironments = {
production: true,
staging: true,
development: true
};
// sanity check
if (!availableEnvironments[ENVIRONMENT]){
logError('node app.js [environment]');
logError('environments: %s', Object.keys(availableEnvironments).join(', '));
process.exit(1);
}
process.env.NODE_ENV = ENVIRONMENT;
// set environment specific variables
global.environment = require(__dirname + '/config/environments/' + ENV);
global.environment.api_hostname = require('os').hostname().split('.')[0];
global.environment = require('./config/environments/' + ENVIRONMENT);
global.log4js = require('log4js');
log4js_config = {
var log4js_config = {
appenders: [],
replaceConsole:true
replaceConsole: true
};
if (global.environment.uv_threadpool_size) {
process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size;
}
// set global HTTP and HTTPS agent default configurations
// ref https://nodejs.org/api/http.html#http_new_agent_options
var agentOptions = _.defaults(global.environment.httpAgent || {}, {
keepAlive: false,
keepAliveMsecs: 1000,
maxSockets: Infinity,
maxFreeSockets: 256
});
http.globalAgent = new http.Agent(agentOptions);
https.globalAgent = new https.Agent(agentOptions);
if ( global.environment.log_filename ) {
var logdir = path.dirname(global.environment.log_filename);
// See cwd inlog4js.configure call below
logdir = path.resolve(__dirname, logdir);
if ( ! fs.existsSync(logdir) ) {
console.error("Log filename directory does not exist: " + logdir);
logError("Log filename directory does not exist: " + logdir);
process.exit(1);
}
console.log("Logs will be written to " + global.environment.log_filename);
log("Logs will be written to " + global.environment.log_filename);
log4js_config.appenders.push(
{ type: "file", filename: global.environment.log_filename }
);
@@ -60,69 +76,50 @@ if ( global.environment.log_filename ) {
);
}
if ( global.environment.rollbar ) {
log4js_config.appenders.push({
type: __dirname + "/lib/cartodb/log4js_rollbar.js",
options: global.environment.rollbar
});
}
global.log4js.configure(log4js_config, { cwd: __dirname });
global.logger = global.log4js.getLogger();
log4js.configure(log4js_config, { cwd: __dirname });
global.logger = log4js.getLogger();
var redisOpts = _.extend(global.environment.redis, { name: 'windshaft' }),
redisPool = new RedisPool(redisOpts);
global.environment.api_hostname = require('os').hostname().split('.')[0];
// 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'),
serverOptions = require('./lib/cartodb/server_options')(redisPool);
var cartodbWindshaft = require('./lib/cartodb/server');
var serverOptions = require('./lib/cartodb/server_options');
ws = CartodbWindshaft(serverOptions);
if (global.statsClient) {
redisPool.on('status', function(status) {
var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
global.statsClient.gauge(keyPrefix + 'count', status.count);
global.statsClient.gauge(keyPrefix + 'unused', status.unused);
global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
});
}
var server = cartodbWindshaft(serverOptions);
// Maximum number of connections for one process
// 128 is a good number if you have up to 1024 filedescriptors
// 4 is good if you have max 32 filedescriptors
// 1 is good if you have max 16 filedescriptors
ws.maxConnections = global.environment.maxConnections || 128;
var backlog = global.environment.maxConnections || 128;
ws.listen(global.environment.port, global.environment.host);
var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, backlog);
var version = require("./package").version;
ws.on('listening', function() {
console.log("Windshaft tileserver " + version + " started on "
+ global.environment.host + ':' + global.environment.port
+ " (" + ENV + ")");
listener.on('listening', function() {
log(
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
);
});
// 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();
});
setInterval(function() {
var memoryUsage = process.memoryUsage();
Object.keys(memoryUsage).forEach(function(k) {
global.statsClient.gauge('windshaft.memory.' + k, memoryUsage[k]);
});
}, 5000);
process.on('SIGHUP', function() {
global.log4js.clearAndShutdownAppenders(function() {
global.log4js.configure(log4js_config);
global.logger = log4js.getLogger();
console.log('Log files reloaded');
global.logger = global.log4js.getLogger();
log('Log files reloaded');
});
});
process.on('uncaughtException', function(err) {
logger.error('Uncaught exception: ' + err.stack);
global.logger.error('Uncaught exception: ' + err.stack);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -2,6 +2,9 @@ var config = {
environment: 'development'
,port: 8181
,host: '127.0.0.1'
// Size of the threadpool which can be used to run user code and get notified in the loop thread
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
@@ -10,17 +13,15 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// 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
@@ -32,7 +33,7 @@ var config = {
// to be able to navigate the map without a reload ?
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
@@ -86,17 +87,126 @@ 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 backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
// See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Metatile is the number of tiles-per-side that are going
// 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,
// tilelive-mapnik uses an internal cache to store tiles/grids
// generated when using metatile. This options allow to tune
// the behaviour for that internal cache.
metatileCache: {
// Time an object must stay in the cache until is removed
ttl: 0,
// Whether an object must be removed after the first hit
// Usually you want to use `true` here when ttl>0.
deleteOnHit: false
},
// Override metatile behaviour depending on the format
formatMetatile: {
png: 2,
'grid.json': 1
},
// 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
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
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
// if provided the http renderer will use it instead of throw an error
fallbackImage: {
type: 'fs', // 'fs' and 'url' supported
src: __dirname + '/../../assets/default-placeholder.png'
}
},
torque: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/tmp/cdb-tiler-dev/millstone-dev'
@@ -125,27 +235,16 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
},
unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161
noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading
}
,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
// For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options
,httpAgent: {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 25,
maxFreeSockets: 256
}
,varnish: {
host: 'localhost',
@@ -156,8 +255,17 @@ 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
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
// Settings for the health check available at /health
@@ -168,6 +276,18 @@ 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,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};
module.exports = config;

View File

@@ -2,6 +2,9 @@ var config = {
environment: 'production'
,port: 8181
,host: '127.0.0.1'
// Size of the threadpool which can be used to run user code and get notified in the loop thread
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
@@ -10,17 +13,15 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// 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
@@ -33,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
@@ -80,17 +81,126 @@ 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 backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
// See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Metatile is the number of tiles-per-side that are going
// 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,
// tilelive-mapnik uses an internal cache to store tiles/grids
// generated when using metatile. This options allow to tune
// the behaviour for that internal cache.
metatileCache: {
// Time an object must stay in the cache until is removed
ttl: 0,
// Whether an object must be removed after the first hit
// Usually you want to use `true` here when ttl>0.
deleteOnHit: false
},
// Override metatile behaviour depending on the format
formatMetatile: {
png: 2,
'grid.json': 1
},
// 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
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
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
// if provided the http renderer will use it instead of throw an error
fallbackImage: {
type: 'fs', // 'fs' and 'url' supported
src: __dirname + '/../../assets/default-placeholder.png'
}
},
torque: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/home/ubuntu/tile_assets/'
@@ -119,27 +229,16 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
},
unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161
noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading
}
,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
// For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options
,httpAgent: {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 25,
maxFreeSockets: 256
}
,varnish: {
host: 'localhost',
@@ -150,8 +249,17 @@ 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
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:false
,serverMetadata: {
@@ -160,15 +268,6 @@ var config = {
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
// Settings for the health check available at /health
,health: {
enabled: true,
@@ -177,6 +276,17 @@ 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,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: false
}
};
module.exports = config;

View File

@@ -2,6 +2,9 @@ var config = {
environment: 'production'
,port: 8181
,host: '127.0.0.1'
// Size of the threadpool which can be used to run user code and get notified in the loop thread
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
@@ -10,17 +13,15 @@ var config = {
// Base URLs for the APIs
//
// See http://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// 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
@@ -33,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
@@ -80,17 +81,126 @@ 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 backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
// See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Metatile is the number of tiles-per-side that are going
// 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,
// tilelive-mapnik uses an internal cache to store tiles/grids
// generated when using metatile. This options allow to tune
// the behaviour for that internal cache.
metatileCache: {
// Time an object must stay in the cache until is removed
ttl: 0,
// Whether an object must be removed after the first hit
// Usually you want to use `true` here when ttl>0.
deleteOnHit: false
},
// Override metatile behaviour depending on the format
formatMetatile: {
png: 2,
'grid.json': 1
},
// 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
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
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
// if provided the http renderer will use it instead of throw an error
fallbackImage: {
type: 'fs', // 'fs' and 'url' supported
src: __dirname + '/../../assets/default-placeholder.png'
}
},
torque: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: false,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/home/ubuntu/tile_assets/'
@@ -119,27 +229,16 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
},
unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161
noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading
}
,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
// For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options
,httpAgent: {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 25,
maxFreeSockets: 256
}
,varnish: {
host: 'localhost',
@@ -150,8 +249,17 @@ 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
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
,serverMetadata: {
@@ -160,15 +268,6 @@ var config = {
https: 'cartocdn.global.ssl.fastly.net'
}
}
// Optional rollbar support
,rollbar: {
token: 'secret',
// See http://github.com/rollbar/node_rollbar#configuration-reference
options: {
endpoint: 'https://api.rollbar.com/api/1/',
handler: 'inline'
}
}
// Settings for the health check available at /health
,health: {
enabled: false,
@@ -177,6 +276,17 @@ 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,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};
module.exports = config;

View File

@@ -2,6 +2,9 @@ var config = {
environment: 'test'
,port: 8888
,host: '127.0.0.1'
// Size of the threadpool which can be used to run user code and get notified in the loop thread
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
@@ -10,17 +13,15 @@ var config = {
// Base URLs for the APIs
//
// See https://github.com/CartoDB/Windshaft-cartodb/wiki/Unified-Map-API
//
//
// 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
@@ -33,7 +34,7 @@ var config = {
// Defaults to 7200 (2 hours)
,mapConfigTTL: 7200
// idle socket timeout, in milliseconds
,socket_timeout: 600000
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
@@ -50,7 +51,7 @@ var config = {
,postgres: {
// Parameters to pass to datasource plugin of mapnik
// See http://github.com/mapnik/mapnik/wiki/PostGIS
user: "testpublicuser",
user: "test_windshaft_publicuser",
password: "public",
host: '127.0.0.1',
port: 5432,
@@ -80,17 +81,127 @@ 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 backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
// See https://github.com/CartoDB/Windshaft/blob/master/lib/windshaft/renderers/renderer_factory.js
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Metatile is the number of tiles-per-side that are going
// 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,
// tilelive-mapnik uses an internal cache to store tiles/grids
// generated when using metatile. This options allow to tune
// the behaviour for that internal cache.
metatileCache: {
// Time an object must stay in the cache until is removed
ttl: 0,
// Whether an object must be removed after the first hit
// Usually you want to use `true` here when ttl>0.
deleteOnHit: false
},
// Override metatile behaviour depending on the format
formatMetatile: {
png: 2,
'grid.json': 1
},
// 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
},
geojson: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
},
// 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
}
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
proxy: undefined, // the url for a proxy server
whitelist: [ // the whitelist of urlTemplates that can be used
'http://{s}.example.com/{z}/{x}/{y}.png'
]
'.*', // 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'
],
// image to use as placeholder when urlTemplate is not in the whitelist
// if provided the http renderer will use it instead of throw an error
fallbackImage: {
type: 'fs', // 'fs' and 'url' supported
src: __dirname + '/../../assets/default-placeholder.png'
}
},
torque: {
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
}
}
// anything analyses related
,analysis: {
// batch configuration
batch: {
// Inline execution avoid the use of SQL API as batch endpoint
// When set to true it will run all analysis queries in series, with a direct connection to the DB
// This might be useful for:
// - testing
// - running an standalone server without any dependency on external services
inlineExecution: true,
// where the SQL API is running, it will use a custom Host header to specify the username.
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
}
,millstone: {
// Needs to be writable by server user
cache_basedir: '/tmp/cdb-tiler-test/millstone'
@@ -119,29 +230,16 @@ var config = {
},
emitter: {
statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd
}
},
unwatchOnRelease: false, // Send unwatch on release, see http://github.com/CartoDB/Windshaft-cartodb/issues/161
noReadyCheck: true // Check `no_ready_check` at https://github.com/mranney/node_redis/tree/v0.12.1#overloading
}
,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
// For more details about this options check https://nodejs.org/api/http.html#http_new_agent_options
,httpAgent: {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 25,
maxFreeSockets: 256
}
,varnish: {
host: '',
@@ -152,8 +250,17 @@ 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
// X-Tiler-Profile header containing elapsed timing for various
// steps taken for producing the response.
,useProfiler:true
// Settings for the health check available at /health
@@ -164,6 +271,17 @@ 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,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
}
};
module.exports = config;

15
configure vendored
View File

@@ -20,7 +20,6 @@
ENVDIR=config/environments
PGPORT=
SQLAPI_PORT=
MAPNIK_VERSION=
ENVIRONMENT=development
@@ -32,7 +31,6 @@ usage() {
echo "Configuration:"
echo " --help display this help and exit"
echo " --with-pgport=NUM access PostgreSQL server on TCP port NUM [$PGPORT]"
echo " --with-sqlapi-port=NUM access SQL-API server on TCP port NUM [$SQLAPI_PORT]"
echo " --with-mapnik-version=STRING set mapnik version string [$MAPNIK_VERSION]"
echo " --environment=STRING set output environment name [$ENVIRONMENT]"
}
@@ -46,9 +44,6 @@ while test -n "$1"; do
--with-pgport=*)
PGPORT=`echo "$1" | cut -d= -f2`
;;
--with-sqlapi-port=*)
SQLAPI_PORT=`echo "$1" | cut -d= -f2`
;;
--with-mapnik-version=*)
MAPNIK_VERSION=`echo "$1" | cut -d= -f2`
;;
@@ -56,9 +51,8 @@ while test -n "$1"; do
ENVIRONMENT=`echo "$1" | cut -d= -f2`
;;
*)
echo "Unknown option '$1'" >&2
usage >&2
exit 1
echo "Unused option '$1'" >&2
;;
esac
shift
done
@@ -68,12 +62,8 @@ ENVEX=./${ENVDIR}/${ENVIRONMENT}.js.example
if [ -z "$PGPORT" ]; then
PGPORT=`node -e "console.log(require('${ENVEX}').postgres.port)"`
fi
if [ -z "$SQLAPI_PORT" ]; then
SQLAPI_PORT=`node -e "console.log(require('${ENVEX}').sqlapi.port)"`
fi
echo "PGPORT: $PGPORT"
echo "SQLAPI_PORT: $SQLAPI_PORT"
echo "MAPNIK_VERSION: $MAPNIK_VERSION"
echo "ENVIRONMENT: $ENVIRONMENT"
@@ -83,7 +73,6 @@ echo "Writing $o"
# See http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/
sed -n "1h;1!H;\${;g;s/\(,postgres: {[^}]*port: *'\?\)[^',]*\('\?,\)/\1$PGPORT\2/;p;}" < "${ENVEX}" \
| sed "s/mapnik_version:.*/mapnik_version: '$MAPNIK_VERSION'/" \
| sed -n "1h;1!H;\${;g;s/\(,sqlapi: {[^}]*port: *'\?\)[^',]*\('\?,\)/\1$SQLAPI_PORT\2/;p;}" \
> "$o"
STATUSFILE=config.status--${ENVIRONMENT}

View File

@@ -1,111 +0,0 @@
# Kind of maps
Windshaft-CartoDB supports the following types of maps:
- [Temporary maps](#temporary-maps) (created by anyone)
- [Detached maps](#detached-maps)
- [Inline maps](#inline-maps) (legacy)
- [Persistent maps](#peristent-maps) (created by CartDB user)
- [Template maps](#template-maps)
- [Table maps](#table-maps) (legacy, deprecated)
## Temporary maps
Temporary maps have no owners and are anonymous in nature.
There are two kinds of temporary maps:
- Detached maps (aka MultiLayer-API)
- Inline maps
### Detached maps
Detached maps are maps that are configured with a request
obtaining a temporary token and then used by referencing
the obtained token. The token expires automatically when unused.
Anyone can create detached maps, but users will need read access
to the data source of the map layers.
The configuration format is a [MapConfig]
(http://github.com/CartoDB/Windshaft/wiki/MapConfig-specification) document.
The HTTP endpoints for creating the map and using it are described [here]
(http://github.com/CartoDB/Windshaft-cartodb/wiki/MultiLayer-API)
*TODO* cleanup the referenced document
### Inline maps
Inline maps are maps that only exist for a single request,
being the request for a specific map resource (tile).
Inline maps are always bound to a table, and can only be
obtained by those having read access to the that table.
Additionally, users need to have access to any datasource
specified as part of the configuration.
Inline maps only support PNG and UTF8GRID tiles.
The configuration consist in a set of parameters, to be
specified in the query string of the tile request:
* sql - the query to run as datasource, can be an array
* style - the CartoCSS style for the datasource, can be an array
* style_version - version of the CartoCSS style, can be an array
* interactivity - only for fetching UTF8GRID,
If the style is not provided, style of the associated table is
used; if the sql is not provided, all records of the associated
table are used as the datasource; the two possibilities result
in a mix between _inline_ maps and [Table maps][].
*TODO* specify (or link) api endpoints
## Persistent maps
Persistent maps can only be created by a CartoDB user who has full
responsibility over editing and deleting them. There are two
kind of persistent maps:
- Template maps
- Table maps (legacy, deprecated)
### Templated maps
Templated maps are templated [MapConfig]
(http://github.com/CartoDB/Windshaft/wiki/MapConfig-specification) documents
associated with an authorization certificate.
The authorization certificate determines who can instanciate the
template and use the resulting map. Authorized users of the instanciated
maps will have the same database access privilege of the template owner.
The HTTP endpoints for creating and using templated maps are described [here]
(http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps).
*TODO* cleanup the referenced document
### Table maps
Table maps are maps associated with a table.
Configuration of such maps is limited to the CartoCSS style.
* style - the CartoCSS style for the datasource, can be an array
* style_version - version of the CartoCSS style, can be an array
You can only fetch PNG or UTF8GRID tiles from these maps.
Access method is the same as the one for [Inline maps](#inline-maps)
# Endpoints description
- **/api/maps/** (same interface than https://github.com/CartoDB/Windshaft/wiki/Multilayer-API)
- **/api/maps/named** (same interface than https://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps)
NOTE: in case Multilayer-API does not contain this info yet, the
endpoint for fetching attributes is this:
- **/api/maps/:map_id/:layer_index/attributes/:feature_id**
- would return { c: 1, d: 2 }

View File

@@ -1,674 +1,19 @@
## Maps API
# Maps API
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account, and you can apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
You can create two types of maps with the Maps API:
- **Anonymous maps**
Maps that can be created using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example]({{ '/cartodb-platform/cartodb-js.html' | prepend: site.baseurl }}).
- **Anonymous Maps**
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example](/cartodb-platform/cartodb-js/getting-started/).
- **Named maps**
Maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.
- **Named Maps**
There are also maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.
## Quickstart
## Documentation
### Anonymous maps
Here is an example of how to create an anonymous map with JavaScript:
```javascript
var mapconfig = {
"version": "1.0.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
$.ajax({
crossOrigin: true,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'http://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'
console.log(templateUrl);
}
})
```
### Named maps
Let's create a named map using some private tables in a CartoDB account.
The following map config sets up a map of European countries that have a white fill color:
```javascript
{
"version": "0.0.1",
"name": "test",
"auth": {
"method": "open"
},
"layergroup": {
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
}
```
The map config needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type ``man curl`` in bash. Using `curl`, and storing the config from above in a file `mapconfig.json`, the call would look like:
<div class="code-title notitle code-request"></div>
```bash
curl 'https://{account}.cartodb.com/api/v1/map/named?api_key=APIKEY' -H 'Content-Type: application/json' -d @mapconfig.json
```
To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response.
<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'
```
The response will return JSON with properties for the `layergroupid`, `cdn_url`, and the timestamp (`last_updated`) of the last data modification.
Here is an example response:
```javascript
{
"cdn_url": {
"http": "ashbu.cartocdn.com",
"https": "cartocdn-ashbu.global.ssl.fastly.net"
},
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z"
}
```
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
```
## General Concepts
The following concepts are the same for every endpoint in the API except when it's noted explicitly.
### Auth
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data, an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a named map).
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`.
### Errors
Errors are reported using standard HTTP codes and extended information encoded in JSON with this format:
```javascript
{
"errors": [
"access forbidden to table TABLE"
]
}
```
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
### CORS support
All the endpoints which might be accessed using a web browser add CORS headers and allow OPTIONS method.
## Anonymous Maps
Anonymous maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec)
### Instantiate
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map
```
#### Params
```javascript
{
"version": "1.0.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e",
"interactivity": ["cartodb_id", "iso3"]
}
}]
}
```
Should be a [Mapconfig](https://github.com/CartoDB/Windshaft/blob/0.19.1/doc/MapConfig-1.1.0.md).
#### Response
The response includes:
- **layergroupid**
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
```
- **updated_at**
The ISO date of the last time the data involved in the query was updated.
- **metadata** *(optional)*
Includes information about the layers. Some layers may not have metadata.
- **cdn_url**
URLs to fetch the data using the best CDN for your zone.
#### Example
<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
```
<div class="code-title">RESPONSE</div>
```javascript
{
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
}
```
The tiles can be accessed using:
```bash
http://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
```
For attributes defined in `attributes` section:
```bash
http://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id
```
Which returns JSON with the attributes defined, like:
```javascript
{ c: 1, d: 2 }
```
Notice UTF Grid and attributes endpoints need an integer parameter, ``layer``. That number is the 0-based index of the layer inside the mapconfig. So in this case 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example mapconfig. If a second layer was available it could be returned with 1, a third layer with 2, etc.
### Create JSONP
The JSONP endpoint is provided in order to allow web browsers access which don't support CORS.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map?callback=method
```
#### Params
- **config**
Encoded JSON with the params for creating named maps (the variables defined in the template).
- **lmza**
This attribute contains the same as config but LZMA compressed. It cannot be used at the same time as `config`.
- **callback**
JSON callback name.
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl "https://documentation.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
```
<div class="code-title">RESPONSE</div>
```javascript
callback({
layergroupid: "d9034c133262dfb90285cea26c5c7ad7:0",
cdn_url: {
"http": "http://cdb.com",
"https": "https://cdb.com"
},
last_updated: "1970-01-01T00:00:00.000Z"
})
```
### Remove
Anonymous maps cannot be removed by an API call. They will expire after about five minutes but sometimes longer. If an anonymous map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time returns to the map an attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.
## Named Maps
Named maps are essentially the same as anonymous maps except the mapconfig is stored on the server and the map is given a unique name. Two other big differences are that you can create named maps from private data, and that users without an API Key can see them even though they are from that private data.
The main two differences compared to anonymous maps are:
- **auth layer**
This allows you to control who is able to see the map based on a token auth
- **templates**
Since the mapconfig is static it can contain some variables so the client can modify the map's appearance using those variables
Template maps are persistent with no preset expiration. They can only be created or deleted by a CartoDB user with a valid API_KEY (see [auth section](http://docs.cartodb.com/cartodb-platform/maps-api.html#auth)).
### Create
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map/named
```
#### Params
- **api_key** is required
<div class="code-title">template.json</div>
```javascript
{
"version": "0.0.1",
"name": "template_name",
"auth": {
"method": "token",
"valid_tokens": [
"auth_token1",
"auth_token2"
]
},
"placeholders": {
"color": {
"type": "css_color",
"default": "red"
},
"cartodb_id": {
"type": "number",
"default": 1
}
},
"layergroup": {
"version": "1.0.1",
"layers": [
{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}
]
}
}
```
##### Arguments
- **name**: There can be at most _one_ template with the same name for any user. Valid names start with a letter, and only contain letters, numbers, or underscores (_).
- **auth**:
- **method** `"token"` or `"open"` (the default if no `"method"` is given).
- **valid_tokens** when `"method"` is set to `"token"`, the values listed here allow you to instantiate the named map.
- **placeholders**: Variables not listed here are not substituted. Variable not provided at instantiation time trigger an error. A default is required for optional variables. Type specification is used for quoting, to avoid injections see template format section below.
- **layergroup**: the layer list definition. This is the MapConfig explained in anonymous maps. See [MapConfig documentation](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.1.0.md) for more info.
#### Template Format
A templated `layergroup` allows the use of placeholders in the "cartocss" and "sql" elements of the "option" object in any "layer" of a `layergroup` configuration
Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced.
##### Example
```javascript
<%= my_color %>
```
The set of supported placeholders for a template will need to be explicitly defined with a specific type and default value for each.
#### Placeholder Types
The placeholder type will determine the kind of escaping for the associated value. Supported types are:
- **sql_literal** internal single-quotes will be sql-escaped
- **sql_ident** internal double-quotes will be sql-escaped
- **number** can only contain numerical representation
- **css_color** can only contain color names or hex-values
Placeholder default values will be used whenever new values are not provided as options at the time of creation on the client. They can also be used to test the template by creating a default version with now options provided.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined losely.
<div class="code-title code-request with-result">REQUEST</div>
```html
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"template_id":"name",
}
```
### Instantiate
Instantiating a map allows you to get the information needed to fetch tiles. That temporal map is an anonymous map.
#### Definition
<div class="code-title notitle code-request"></div>
```html
POST /api/v1/map/named/:template_name
```
#### Param
- **auth_token** optional, but required when `"method"` is set to `"token"`
```javascript
// params.json
{
"color": "#ff0000",
"cartodb_id": 3
}
```
The fields you pass as `params.json` depend on the variables allowed by the named map. If there are variables missing it will raise an error (HTTP 400).
- **auth_token** *optional* if the named map needs auth
#### Example
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/:template_name`.
Valid credentials will be needed if required by the template.
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.json \
'https://documentation.cartodb.com/api/v1/map/named/@template_name?auth_token=AUTH_TOKEN'
```
<div class="code-title">Response</div>
```javascript
{
"layergroupid": "docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated": "2013-11-14T11:20:15.000Z"
}
```
<div class="code-title">Error</div>
```javascript
{
"error": "Some error string here"
}
```
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see anonymous map section). However, you'll need to show the `auth_token`, if required by the template.
### Using JSONP
There is also a special endpoint to be able to initialize a map using JSONP (for old browsers).
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/:template_name/jsonp
```
#### Params
- **auth_token** optional, but required when `"method"` is set to `"token"`
- **config** Encoded JSON with the params for creating named maps (the variables defined in the template)
- **lmza** This attribute contains the same as config but LZMA compressed. It cannot be used at the same time than `config`.
- **callback:** JSON callback name
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl 'https://documentation.cartodb.com/api/v1/map/named/:template_name/jsonp?auth_token=AUTH_TOKEN&callback=callback&config=template_params_json'
```
<div class="code-title">RESPONSE</div>
```javascript
callback({
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
})
```
This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables).
```javascript
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
The response is in this format:
```javascript
callback({
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
})
```
### Update
#### Definition
<div class="code-title notitle code-request"></div>
```bash
PUT /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Response
Same as updating a map.
#### Other Info
Updating a named map removes all the named map instances so they need to be initialized again.
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"template_id": "@template_name"
}
```
If any template has the same name, it will be updated.
If a template with the same name does NOT exist, a 400 HTTP response is generated with an error in this format:
```javascript
{
"error": "error string here"
}
```
### Delete
Delete the specified template map from the server and disables any previously initialized versions of the map.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
DELETE /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request">REQUEST</div>
```bash
curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title">RESPONSE</div>
```javascript
{
"error": "Some error string here"
}
```
On success, a 204 (No Content) response would be issued. Otherwise a 4xx response with with an error will be returned.
### Listing Available Templates
This allows you to get a list of all available templates.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY'
```
<div class="code-title with-result">RESPONSE</div>
```javascript
{
"template_ids": ["@template_name1","@template_name2"]
}
```
<div class="code-title">ERROR</div>
```javascript
{
"error": "Some error string here"
}
```
### Getting a Specific Template
This gets the definition of a template.
#### Definition
<div class="code-title notitle code-request"></div>
```bash
GET /api/v1/map/named/:template_name
```
#### Params
- **api_key** is required
#### Example
<div class="code-title code-request with-result">REQUEST</div>
```bash
curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY'
```
<div class="code-title with-result">RESPONSE</div>
```javascript
{
"template": {...} // see template.json above
}
```
<div class="code-title">ERROR</div>
```javascript
{
"error": "Some error string here"
}
```
### Use with CartoDB.js
Named maps can be used with CartoDB.js by specifying a named map in a layer source as follows. Named maps are treated almost the same as other layer source types in most other ways.
```js
var layerSource = {
user_name: '{your_user_name}',
type: 'namedmap',
named_map: {
name: '{template_name}',
layers: [{
layer_name: "layer1",
interactivity: "column1, column2, ..."
}]
}
}
cartodb.createLayer('map_dom_id',layerSource)
.addTo(map_object);
```
See [CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js.html) methods [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetparamskey-value) and [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js.html#layersetauthtokenauthtoken).
* [Quickstart](quickstart.md)
* [General Concepts](general_concepts.md)
* [Anonymous Maps](anonymous_maps.md)
* [Named Maps](named_maps.md)
* [Static Maps API](static_maps_api.md)

View File

@@ -0,0 +1,56 @@
# 1. Purpose
This specification describes an extension for
[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version.
# 2. Changes over specification
This extension introduces a new layer type so it's possible to use a Named Map by its name as a layer.
## 2.1 Named layers definition
```javascript
{
// REQUIRED
// string, `named` is the only supported value
type: "named",
// REQUIRED
// object, set `named` map layers configuration
options: {
// REQUIRED
// string, the name for the Named Map to use
name: "world_borders",
// OPTIONAL
// object, the replacement values for the Named Map's template placeholders
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details
config: {
"color": "#000"
},
// OPTIONAL
// string array, the authorized tokens in case the Named Map has auth method set to `token`
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details
auth_tokens: [
"token1",
"token2"
]
}
}
```
## 2.2 Limitations
1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition.
2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very
same user account.
# History
## 1.0.0
- Initial version

View File

@@ -1,4 +1,4 @@
The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/Vizzuality/Windshaft/wiki/Multilayer-API) in a few ways.
The Windshaft-CartoDB MultiLayer API extends the [Windshaft MultiLayer API](https://github.com/CartoDB/Windshaft/blob/master/doc/Multilayer-API.md) in a few ways.
## Last modification timestamp embedded in the token
@@ -17,8 +17,8 @@ Windshaft-CartoDB adds the following attributes in the response object
```json
{
http: 'http://cdn_url.com/'
https: 'https://secure.cdn_url.com/'
"http": "http://cdn_url.com/",
"https": "https://secure.cdn_url.com/"
}
```

114
docs/Routes.md Normal file
View File

@@ -0,0 +1,114 @@
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)/: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 (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
<br/>Notes: By :widgetName per :layer widget [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/:token/:layer/widget/:widgetName/search {:user(f),:token(f),:layer(f),:widgetName(f)} (1)`
<br/>Notes: By :widgetName per :layer widget search [0]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: Map instantiation [0]
1. `POST (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: Map instantiation [0]
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. `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. `OPTIONS (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup) {:user(f)} (1)`
<br/>Notes: CORS [0]
1. `GET (?:/api/v1/map/named|/user/:user/api/v1/map/named|/tiles/template)/:template_id/:layer/:z/:x/:y.(:format) {:user(f),:template_id(f),:layer(f),:z(f),:x(f),:y(f),:0(f),:format(f)} (1)`
<br/>Notes: Per :layer fixed URL named map tiles [1]
1. `GET (?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)/static/named/:template_id/:width/:height.:format {:user(f),:template_id(f),:width(f),:height(f),:format(f)} (1)`
<br/>Notes: Static map for named maps [1]
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. `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. `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. `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]
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. `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. `GET /health {} (1)`
<br/>Notes: Health check
1. `GET / {} (1)`
<br/>Notes: Welcome message
1. `GET /version {} (1)`
<br/>Notes: Return relevant module versions: mapnik, grainstore, etc
## 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/server.js b/lib/cartodb/server.js
index 5f62850..bca377d 100644
--- a/lib/cartodb/server.js
+++ b/lib/cartodb/server.js
@@ -215,6 +215,20 @@ module.exports = function(serverOptions) {
* END Routing
******************************************************************************************************************/
+ var format = require('util').format;
+ var routesNotes = app._router.stack
+ .filter(function(handler) { return !!handler.route; })
+ .map(function(handler) {
+ return format("\n1. `%s %s {%s} (1)`\n<br/>Notes: [DEPRECATED]? ",
+ Object.keys(handler.route.methods)[0].toUpperCase(),
+ handler.route.path,
+ handler.keys.map(function(k) {
+ return format(':%s(%s)', k.name, k.optional ? 't' : 'f');
+ }).join(',')
+ );
+ });
+ console.log(routesNotes.join('\n'));
+
return app;
};
```

View File

@@ -1,272 +0,0 @@
Template maps are layergroup configurations that rather than being
fully defined contain variables that can be set to produce a different
layergroup configurations (instantiation).
Template maps are persistent, can only be created and deleted by the
CartoDB user showing a valid API_KEY.
# Template format
A templated layergroup would allow using placeholders
in the "cartocss" and "sql" elements in the "option"
field of any "layer" of a layergroup configuration
(see https://github.com/CartoDB/Windshaft/wiki/MapConfig-specification).
Valid placeholder names start with a letter and can only
contain letters, numbers or underscores. They have to be
written between ``<%= `` and `` %>`` strings in order to be
replaced. Example: ``<%= my_color %>``.
The set of supported placeholders for a template will need to be
explicitly defined specifying type and default value for each.
**placeholder types**
Placeholder type will determine the kind of escaping for the
associated value. Supported types are:
* sql_literal (internal single-quotes will be sql-escaped)
* sql_ident (internal double-quotes will be sql-escaped)
* number (can only contain numerical representation)
* css_color (can only contain color names or hex-values)
* ... (add more as need arises)
Placeholder default value will be used when not provided at
instantiation time and could be used to test validity of the
template by creating a default instance.
```js
// template.json
{
version: "0.0.1",
// there can be at most 1 template with the same name for any user
// valid names start with a letter and only contains letter, numbers
// or underscores
name: "template_name",
// embedded authorization certificate
auth: {
method: "token", // or "open" (the default if no "method" is given)
// only (required and non empty) for "token" method
valid_tokens: ["auth_token1","auth_token2"]
},
// Variables not listed here are not substituted
// Variable not provided at instantiation time trigger an error
// A default is required for optional variables
// Type specification is used for quoting, to avoid injections
placeholders: {
color: {
type:"css_color",
default:"red"
},
cartodb_id: {
type:"number",
default: 1
}
},
layergroup: {
// see https://github.com/CartoDB/Windshaft/wiki/MapConfig-specification
"version": "1.0.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}]
}
}
```
# Creating a templated map
You can create a template map with a single call (for simplicity).
You'd use a POST sending JSON data:
```sh
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://docs.cartodb.com/tiles/template?api_key=APIKEY'
```
The response would be like this:
```js
{
"template_id":"@template_name"
}
```
If a template with the same name exists in the user storage,
a 400 response is generated.
Errors are in this form:
```js
{
"error":"Some error string here"
}
```
# Updating an existing template
You can update a template map with a PUT:
```sh
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://docs.cartodb.com/tiles/template/:template_name?api_key=APIKEY'
```
A template with the same name will be updated, if any.
The response would be like this:
```js
{
"template_id":"@template_name"
}
```
If a template with the same name does NOT exist,
a 400 HTTP response is generated with an error in this format:
```js
{
"error":"Some error string here"
}
```
# Listing available templates
You can get a list of available templates with a GET to ``/template``.
A valid api_key is required.
```sh
curl -X GET 'https://docs.cartodb.com/tiles/template?api_key=APIKEY'
```
The response would be like this:
```js
{
"template_ids": ["@template_name1","@template_name2"]
}
```
Or, on error:
```js
{
"error":"Some error string here"
}
```
# Getting a specific template
You can get the definition of a template with a
GET to ``/template/:template_name``.
A valid api_key is required.
Example:
```sh
curl -X GET 'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
The response would be like this:
```js
{
"template": {...} // see template.json above
}
```
Or, on error:
```js
{
"error":"Some error string here"
}
```
# Instantiating a template map
You can instantiate a template map passing all required parameters with
a POST to ``/template/:template_name``.
Valid credentials will be needed, if required by the template.
```js
// params.js
{
color: '#ff0000',
cartodb_id: 3
}
```
```sh
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.js \
'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
The response would be like this:
```js
{
"layergroupid":"docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated":"2013-11-14T11:20:15.000Z"
}
```
or, on error:
```js
{
"error":"Some error string here"
}
```
You can then use the ``layergroupid`` for fetching tiles and grids as you do
normally ( see https://github.com/CartoDB/Windshaft/wiki/Multilayer-API).
But you'll still have to show the ``auth_token``, if required by the template.
### using JSONP
There is also a special endpoint to be able to instanciate using JSONP (for old browsers)
```
curl 'https://docs.cartodb.com/tiles/template/@template_name/jsonp?auth_token=AUTH_TOKEN&callback=function_name&config=template_params_json'
```
it takes the ``callback`` function (required), ``auth_token`` in case the template needs auth and ``config`` which is the variabñes for the template (in case it has variables). For example config may be created (using javascript)
```
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
the response it's in this format:
```
jQuery17205720721024554223_1390996319118(
{
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
}
)
```
# Deleting a template map
You can delete a templated map with a DELETE to ``/template/:template_name``:
```sh
curl -X DELETE 'https://docs.cartodb.com/tiles/template/@template_name?auth_token=AUTH_TOKEN'
```
On success, a 204 (No Content) response would be issued.
Otherwise a 4xx response with this format:
```js
{
"error":"Some error string here"
}
```

194
docs/anonymous_maps.md Normal file
View File

@@ -0,0 +1,194 @@
# Anonymous Maps
Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec)
## Instantiate
#### Definition
```html
POST /api/v1/map
```
#### Params
```javascript
{
"version": "1.3.0",
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e",
"interactivity": ["cartodb_id", "iso3"]
}
}]
}
```
See [MapConfig File Formats](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for details.
#### Response
The response includes:
Attributes | Description
--- | ---
layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png`
updated_at | The ISO date of the last time the data involved in the query was updated.
metadata | Includes information about the layers.
cdn_url | URLs to fetch the data using the best CDN for your zone.
### Example
#### Call
```bash
curl 'https://{username}.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
```
#### Response
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
},
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
}
```
### Retrieve resources from the layergroup
When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection.
#### Mapnik tiles
These tiles will get just the Mapnik layers. To get individual layers, see the following section.
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```
#### Individual layers
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually in different formats depending on the layer type.
Individual layers can be accessed using that 0-based index. For UTF grid tiles:
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json
```
In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig.
If the MapConfig had a Torque layer at index 1 it could be possible to request it with:
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
```
#### Attributes defined in `attributes` section
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
```
Which returns JSON with the attributes defined, like:
```javascript
{ "c": 1, "d": 2 }
```
#### Blending and layer selection
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
```
Note: currently format is limited to `png`.
`layer_filter` can be used to select some layers to be rendered together. `layer_filter` supports two formats:
- `all` alias
Using `all` as `layer_filter` will blend all layers in the layergroup
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
```
- Filter by layer index
A list of comma separated layer indexes can be used to just render a subset of layers. For example `0,3,4` will filter and blend layers with indexes 0, 3, and 4.
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
```
Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- Once a Mapnik layer is selected, all Mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all Mapnik layers if you want to select at least one so you will get a consistent
behavior in the future.
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
consistent behavior in the future.
## Create JSONP
The JSONP endpoint is provided in order to allow web browsers access which don't support CORS.
#### Definition
```bash
GET /api/v1/map?callback=method
```
#### Params
Param | Description
--- | ---
config | Encoded JSON with the params for creating Named Maps (the variables defined in the template).
lmza | This attribute contains the same as config but LZMA compressed. It cannot be used at the same time as `config`.
callback | JSON callback name.
### Example
#### Call
```bash
curl "https://{username}.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D"
```
#### Response
```javascript
callback({
layergroupid: "d9034c133262dfb90285cea26c5c7ad7:0",
cdn_url: {
"http": "http://cdb.com",
"https": "https://cdb.com"
},
last_updated: "1970-01-01T00:00:00.000Z"
})
```
## Remove
Anonymous Maps cannot be removed by an API call. They will expire after about five minutes, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.

27
docs/general_concepts.md Normal file
View File

@@ -0,0 +1,27 @@
# General Concepts
The following concepts are the same for every endpoint in the API except when it's noted explicitly.
## Auth
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`.
## Errors
Errors are reported using standard HTTP codes and extended information encoded in JSON with this format:
```javascript
{
"errors": [
"access forbidden to table TABLE"
]
}
```
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
## CORS support
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.

View File

@@ -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

531
docs/named_maps.md Normal file
View File

@@ -0,0 +1,531 @@
# Named Maps
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
The Named Map workflow consists of uploading a MapConfig file to CartoDB servers, to select data from your CartoDB user database by using SQL, and specifying the CartoCSS for your map.
The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps.
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
The main differences, compared to Anonymous Maps, is that Named Maps include:
- **auth token**
This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection.
- **template map**
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CartoDB user with a valid API KEY (See [auth argument](#arguments)).
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/).
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options).
## Create
#### Definition
```html
POST /api/v1/map/named
```
#### Params
Params | Description
--- | ---
api_key | is required
MapConfig | a [Named Map MapConfig](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
#### template.json
The `name` argument defines how to name this "template_name".json. Note that there are some requirements for how to name a Named Map template. See the [`name`](#arguments) argument description for details.
```javascript
{
"version": "0.0.1",
"name": "template_name",
"auth": {
"method": "token",
"valid_tokens": [
"auth_token1",
"auth_token2"
]
},
"placeholders": {
"color": {
"type": "css_color",
"default": "red"
},
"cartodb_id": {
"type": "number",
"default": 1
}
},
"layergroup": {
"version": "1.0.1",
"layers": [
{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: <%= color %>; }",
"sql": "select * from european_countries_e WHERE cartodb_id = <%= cartodb_id %>"
}
}
]
},
"view": {
"zoom": 4,
"center": {
"lng": 0,
"lat": 0
},
"bounds": {
"west": -45,
"south": -45,
"east": 45,
"north": 45
}
}
}
```
#### Arguments
Params | Description
--- | ---
name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_.
auth |
--- | ---
&#124;_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
&#124;_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.cartodb.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS.
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for more information.
view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box.
--- | ---
&#124;_ zoom | The zoom level to use
&#124;_ center |
--- | ---
&#124;_ &#124;_ lng | The longitude to use for the center
&#124;_ &#124;_ lat | The latitude to use for the center
&#124;_ bounds |
--- | ---
&#124;_ &#124;_ west | LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
&#124;_ &#124;_ south | LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
&#124;_ &#124;_ east | UpperCorner longitude for the bounding box, in decimal degrees (aka most eastern)
&#124;_ &#124;_ north | UpperCorner latitude for the bounding box, in decimal degrees (aka most northern)
### Placeholder Format
Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergoup configurations](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#layergroup-configurations).
Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API.
#### Example
```javascript
<%= my_color %>
```
The set of supported placeholders for a template need to be explicitly defined with a specific type, and default value, for each placeholder.
### Placeholder Types
The placeholder type will determine the kind of escaping for the associated value. Supported types are:
Types | Description
--- | ---
sql_literal | internal single-quotes will be sql-escaped
sql_ident | internal double-quotes will be sql-escaped
number | can only contain numerical representation
css_color | can only contain color names or hex-values
Placeholder default values will be used whenever new values are not provided as options, at the time of creation on the client. They can also be used to test the template by creating a default version with new options provided.
When using templates, be very careful about your selections as they can give broad access to your data if they are defined loosely.
#### Call
This is the call for creating the Named Map. It is sending the template.json file to the service, and the server responds with the template id.
```bash
curl -X POST \
-H 'Content-Type: application/json' \
-d @template.json \
'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
```
#### Response
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
```javascript
{
"template_id":"name"
}
```
## Instantiate
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CartoDB.js `createLayer()` function. The result is an Anonymous Map.
#### Definition
```html
POST /api/v1/map/named/{template_name}
```
#### Param
Param | Description
--- | ---
auth_token | `"token"` or `"open"` (`"open"` is the default if not specified. Use `"token"` to password-protect your map)
```javascript
// params.json, this is required if the Named Map allows variables (if placeholders were defined in the template.json by the user)
{
"color": "#ff0000",
"cartodb_id": 3
}
```
The fields you pass as `params.json` depend on the variables allowed by the Named Map. If there are variables missing, it will raise an error (HTTP 400).
**Note:** It is required that you include a `params.json` file to instantiate a Named Map that contains variables, even if you have no fields to pass and the JSON is empty. (This is specific to when a Named Map allows variables (if placeholders were defined in the template.json by the user).
#### Example
You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`.
Valid auth token will be needed, if required by the template.
#### Call
```bash
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.json \
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
```
#### Response
```javascript
{
"layergroupid": "docs@fd2861af@c01a54877c62831bb51720263f91fb33:123456788",
"last_updated": "2013-11-14T11:20:15.000Z"
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/)).
## Update
#### Definition
```bash
PUT /api/v1/map/named/{template_name}
```
#### Params
Param | Description
--- | ---
api_key | is required
#### Response
Same as updating a map.
### Other Information
Updating a Named Map removes all the Named Map instances, so they need to be initialized again.
### Example
#### Call
```bash
curl -X PUT \
-H 'Content-Type: application/json' \
-d @template.json \
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
```javascript
{
"template_id": "@template_name"
}
```
If any template has the same name, it will be updated.
If a template with the same name does NOT exist, a 400 HTTP response is generated with an error in this format:
```javascript
{
"errors" : ["error string here"]
}
```
## Delete
Deletes the specified template map from the server, and disables any previously initialized versions of the map.
#### Definition
```bash
DELETE /api/v1/map/named/{template_name}
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X DELETE 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
```javascript
{
"errors" : ["Some error string here"]
}
```
On success, a 204 (No Content) response will be issued. Otherwise a 4xx response with an error will be returned.
## Listing Available Templates
This allows you to get a list of all available templates.
#### Definition
```bash
GET /api/v1/map/named/
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
```
#### Response
```javascript
{
"template_ids": ["@template_name1","@template_name2"]
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
## Get Template Definition
This gets the definition of a requested template.
#### Definition
```bash
GET /api/v1/map/named/{template_name}
```
#### Params
Param | Description
--- | ---
api_key | is required
### Example
#### Call
```bash
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
```javascript
{
"template": {...} // see [template.json](#templatejson)
}
```
#### Error
```javascript
{
"errors" : ["Some error string here"]
}
```
## JSONP for Named Maps
If using a [JSONP](https://en.wikipedia.org/wiki/JSONP) (for old browsers) request, there is a special endpoint used to initialize and create a Named Map.
#### Definition
```bash
GET /api/v1/map/named/{template_name}/jsonp
```
#### Params
Params | Description
--- | ---
auth_token | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
params | Encoded JSON with the params (variables) needed for the Named Map
lmza | You can use an LZMA compressed file instead of a params JSON file
callback | JSON callback name
#### Call
```bash
curl 'https://{username}.cartodb.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
```
#### Response
```javascript
callback({
"layergroupid":"c01a54877c62831bb51720263f91fb33:0",
"last_updated":"1970-01-01T00:00:00.000Z"
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
}
})
```
This takes the `callback` function (required), `auth_token` if the template needs auth, and `config` which is the variable for the template (in cases where it has variables).
```javascript
url += "config=" + encodeURIComponent(
JSON.stringify({ color: 'red' });
```
The response is:
```javascript
callback({
layergroupid: "dev@744bd0ed9b047f953fae673d56a47b4d:1390844463021.1401",
last_updated: "2014-01-27T17:41:03.021Z"
})
```
## CartoDB.js for Named Maps
You can use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
```javascript
{
user_name: '{username}', // Required
type: 'namedmap', // Required
named_map: {
name: '{name_of_map}', // Required, the 'name' of the Named Map that you have created
// Optional
layers: [{
layer_name: "sublayer0", // Optional
interactivity: "column1, column2, ..." // Optional
},
{
layer_name: "sublayer1",
interactivity: "column1, column2, ..."
},
...
],
// Optional
params: {
color: "hex_value",
num: 2
}
}
}
```
**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them.
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js/) has methods for accessing your Named Maps.
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
**Note:** The CartoDB.js `layer.setParams()` function is not supported when using Named Maps for Torque.
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
#### Examples of Named Maps created with CartoDB.js
- [Named Map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
- [Named Map with interactivity](http://bl.ocks.org/ohasselblad/d1a45b8ff5e7bd90cd68)
- [Toggling sublayers in a Named Map](http://bl.ocks.org/ohasselblad/c1a0f4913610eec53cd3)
## Fetching XYZ Tiles for Named Maps
Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik Retina tiles) for your Named Map.
### Fetch XYZ Tiles Directly with a URL
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
To call a template_id in a URL:
`/{template_id}/{layer}/{z}/{x}/{y}.{format}`
For example, a complete URL might appear as:
"https://{username}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png"
The placeholders indicate the following:
- [`template_id`](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response) is the response of your Named Map.
- layers can be a number (referring to the # layer of your map), all layers of your map, or a list of layers.
- To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png"
- To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png"
- To show all layers, enter the value `all` for the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png"
- To show a [list of layers](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png"
### Get Mapnik Retina Tiles
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response-1) to initialize the map.
Instantiate the map by using your `layergroupid` in the token placeholder:
`{token}/{z}/{x}/{y}@{scale_factor}?{x}.{format}`

100
docs/quickstart.md Normal file
View File

@@ -0,0 +1,100 @@
# Quickstart
## Anonymous Maps
Here is an example of how to create an Anonymous Map with JavaScript:
```javascript
var mapconfig = {
"version": "1.3.1",
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
$.ajax({
crossOrigin: true,
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'https://{username}.cartodb.com/api/v1/map',
data: JSON.stringify(mapconfig),
success: function(data) {
var templateUrl = 'https://{username}.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
console.log(templateUrl);
}
})
```
## Named Maps
Let's create a Named Map using some private tables in a CartoDB account.
The following map config sets up a map of European countries that have a white fill color:
```javascript
{
"version": "0.0.1",
"name": "test",
"auth": {
"method": "open"
},
"layergroup": {
"layers": [{
"type": "mapnik",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer { polygon-fill: #FFF; }",
"sql": "select * from european_countries_e"
}
}]
}
}
```
The MapConfig needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
#### Call
```bash
curl 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
```
To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response.
#### Call
```bash
curl -X POST 'https://{username}.cartodb.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
```
The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`.
Note: all `layers` in `metadata` will always have a `type` string and a `meta` dictionary with the key/value pairs.
#### Response
```javascript
{
"layergroupid": "c01a54877c62831bb51720263f91fb33:0",
"last_updated": "1970-01-01T00:00:00.000Z",
"metadata": {
"layers": [
{
"type": "mapnik",
"meta": {}
}
]
}
}
```
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
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```

221
docs/static_maps_api.md Normal file
View File

@@ -0,0 +1,221 @@
# Static Maps API
The Static Maps API can be initiated using both Named and Anonymous Maps using the 'layergroupid' token. The API can be used to create static images of parts of maps and thumbnails for use in web design, graphic design, print, field work, and many other applications that require standard image formats.
## Maps API endpoints
Begin by instantiating either a Named or Anonymous Map using the `layergroupid token` as demonstrated in the Maps API documentation above. The `layergroupid` token calls to the map and allows for parameters in the definition to generate static images.
### Zoom + center
#### Definition
```bash
GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format}
```
#### Params
Param | Description
--- | ---
token | the layergroupid token from the map instantiation
z | the zoom level of the map
lat | the latitude for the center of the map
format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
### Bounding Box
#### Definition
```bash
GET /api/v1/map/static/bbox/{token}/{bbox}/{width}/{height}.{format}`
```
#### Params
Param | Description
--- | ---
token | the layergroupid token from the map instantiation
bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for:
--- | ---
| LowerCorner longitude, in decimal degrees (aka most western)
| LowerCorner latitude, in decimal degrees (aka most southern)
| UpperCorner longitude, in decimal degrees (aka most eastern)
| UpperCorner latitude, in decimal degrees (aka most northern)
width | the width in pixels for the output image
height | the height in pixels for the output image
format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
Note: you can see this endpoint as
```bash
GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format}`
```
### Named Map
#### Definition
```bash
GET /api/v1/map/static/named/{name}/{width}/{height}.{format}
```
#### Params
Param | Description
--- | ---
name | the name of the Named Map
width | the width in pixels for the output image
height | the height in pixels for the output image
format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
#### Layers
The Static Maps API allows for multiple layers of incorporation into the `MapConfig` to allow for maximum versatility in creating a static map. The examples below were used to generate the static image example in the next section, and appear in the specific order designated.
**Basemaps**
```javascript
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
}
```
By manipulating the `"urlTemplate"` custom basemaps can be used in generating static images. Supported map types for the Static Maps API are:
```javascript
'http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
'http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
```
**Mapnik**
```javascript
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
```
**CartoDB**
As described in the [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
}
```
Additionally, static images from Torque maps and other map layers can be used together to generate highly customizable and versatile static maps.
### Caching
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
### Limits
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
* Image resolution by default is set to 72 DPI
* JPEG quality by default is 85%
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
## Examples
After instantiating a map from a CartoDB account:
#### Call
```bash
GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png
```
#### Response
<p class="wrap-border"><img src="https://raw.githubusercontent.com/namessanti/Pictures/master/static_api.png" alt="static-api"/></p>
### MapConfig
For this map, the multiple layers, order, and stylings are defined by the MapConfig.
```javascript
{
"version": "1.3.0",
"layers": [
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
]
}
},
{
"type": "mapnik",
"options": {
"sql": "select null::geometry the_geom_webmercator",
"cartocss": "#layer {\n\tpolygon-fill: #FF3300;\n\tpolygon-opacity: 0;\n\tline-color: #333;\n\tline-width: 0;\n\tline-opacity: 0;\n}",
"cartocss_version": "2.2.0"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from park",
"cartocss": "/** simple visualization */\n\n#park{\n polygon-fill: #229A00;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from residential_zoning_2009",
"cartocss": "/** simple visualization */\n\n#residential_zoning_2009{\n polygon-fill: #c7eae5;\n polygon-opacity: 1;\n line-color: #FFF;\n line-width: 0.2;\n line-opacity: 0.5;\n}",
"cartocss_version": "2.1.1"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from nycha_developments_july2011",
"cartocss": "/** simple visualization */\n\n#nycha_developments_july2011{\n polygon-fill: #ef3b2c;\n polygon-opacity: 0.7;\n line-color: #FFF;\n line-width: 0;\n line-opacity: 1;\n}",
"cartocss_version": "2.1.1"
}
}
]
}
```

141
lib/cartodb/api/auth_api.js Normal file
View File

@@ -0,0 +1,141 @@
var assert = require('assert');
var step = require('step');
/**
*
* @param {PgConnection} pgConnection
* @param metadataBackend
* @param {MapStore} mapStore
* @param {TemplateMaps} templateMaps
* @constructor
* @type {AuthApi}
*/
function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) {
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.mapStore = mapStore;
this.templateMaps = templateMaps;
}
module.exports = AuthApi;
// Check if a request is authorized by a signer
//
// @param req express request object
// @param callback function(err, signed_by) signed_by will be
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
AuthApi.prototype.authorizedBySigner = function(req, callback) {
if ( ! req.params.token || ! req.params.signer ) {
return callback(null, false); // no signer requested
}
var self = this;
var layergroup_id = req.params.token;
var auth_token = req.params.auth_token;
this.mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
return callback(err);
}
var authorized = self.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
return callback(null, authorized);
});
};
// Check if a request is authorized by api_key
//
// @param user
// @param req express request object
// @param callback function(err, authorized)
// NOTE: authorized is expected to be 0 or 1 (integer)
//
AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
var givenKey = req.query.api_key || req.query.map_key;
if ( ! givenKey && req.body ) {
// check also in request body
givenKey = req.body.api_key || req.body.map_key;
}
if ( ! givenKey ) {
return callback(null, 0); // no api key, no authorization...
}
var self = this;
step(
function () {
self.metadataBackend.getUserMapKey(user, this);
},
function checkApiKey(err, val){
assert.ifError(err);
return val && givenKey === val;
},
function finish(err, authorized) {
callback(err, authorized);
}
);
};
/**
* Check access authorization
*
* @param req - standard req object. Importantly contains table and host information
* @param callback function(err, allowed) is access allowed not?
*/
AuthApi.prototype.authorize = function(req, callback) {
var self = this;
var user = req.context.user;
step(
function () {
self.authorizedByAPIKey(user, req, this);
},
function checkApiKey(err, authorized){
if (req.profiler) {
req.profiler.done('authorizedByAPIKey');
}
assert.ifError(err);
// if not authorized by api_key, continue
if (!authorized) {
// not authorized by api_key, check if authorized by signer
return self.authorizedBySigner(req, this);
}
// authorized by api key, login as the given username and stop
self.pgConnection.setDBAuth(user, req.params, function(err) {
callback(err, true); // authorized (or error)
});
},
function checkSignAuthorized(err, authorized) {
if (err) {
return callback(err);
}
if ( ! authorized ) {
// request not authorized by signer.
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
//
if ( ! req.params.signer ) {
return callback(null, true); // authorized so far
}
// if signer name was given, return no authorization
return callback(null, false);
}
self.pgConnection.setDBAuth(user, req.params, function(err) {
if (req.profiler) {
req.profiler.done('setDBAuth');
}
callback(err, true); // authorized (or error)
});
}
);
};

View File

@@ -0,0 +1,46 @@
function OverviewsMetadataApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = OverviewsMetadataApi;
// TODO: share this with QueryTablesApi? ... or maintain independence?
var affectedTableRegexCache = {
bbox: /!bbox!/g,
scale_denominator: /!scale_denominator!/g,
pixel_width: /!pixel_width!/g,
pixel_height: /!pixel_height!/g
};
function prepareSql(sql) {
return sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
.replace(affectedTableRegexCache.scale_denominator, '0')
.replace(affectedTableRegexCache.pixel_width, '1')
.replace(affectedTableRegexCache.pixel_height, '1')
;
}
OverviewsMetadataApi.prototype.getOverviewsMetadata = function (username, sql, callback) {
// FIXME: Currently using internal function _cdb_schema_name
// CDB_Overviews should provide the schema information directly.
var query = 'SELECT *, _cdb_schema_name(base_table)' +
' FROM CDB_Overviews(CDB_QueryTablesText($windshaft$' + prepareSql(sql) + '$windshaft$))';
this.pgQueryRunner.run(username, query, function handleOverviewsRows(err, rows) {
if (err){
callback(err);
return;
}
var metadata = rows.reduce(function(metadata, row){
var table = row.base_table;
var schema = row._cdb_schema_name;
if ( !metadata[table] ) {
metadata[table] = {};
}
metadata[table][row.z] = { table: row.overview_table };
metadata[table].schema = schema;
return metadata;
}, {});
return callback(null, metadata);
});
};

View File

@@ -1,122 +0,0 @@
var sqlApi = require('../sql/sql_api'),
PSQL = require('cartodb-psql');
function QueryTablesApi() {
}
var affectedTableRegexCache = {
bbox: /!bbox!/g,
scale_denominator: /!scale_denominator!/g,
pixel_width: /!pixel_width!/g,
pixel_height: /!pixel_height!/g
};
module.exports = QueryTablesApi;
QueryTablesApi.prototype.getLastUpdatedTime = function (username, api_key, tableNames, callback) {
var sql = 'SELECT EXTRACT(EPOCH FROM max(updated_at)) as max FROM CDB_TableMetadata m WHERE m.tabname = any (ARRAY['+
tableNames.map(function(t) { return "'" + t + "'::regclass"; }).join(',') +
'])';
// call sql api
sqlApi.query(username, api_key, sql, function(err, rows){
if (err){
var msg = err.message ? err.message : err;
callback(new Error('could not find last updated timestamp: ' + msg));
return;
}
// when the table has not updated_at means it hasn't been changed so a default last_updated is set
var last_updated = 0;
if(rows.length !== 0) {
last_updated = rows[0].max || 0;
}
callback(null, last_updated*1000);
});
};
QueryTablesApi.prototype.getAffectedTablesInQuery = function (username, options, sql, callback) {
var query = 'SELECT CDB_QueryTables($windshaft$' + prepareSql(sql) + '$windshaft$)';
runQuery(username, options, query, handleAffectedTablesInQueryRows, callback);
};
function handleAffectedTablesInQueryRows(err, rows, callback) {
if (err){
var msg = err.message ? err.message : err;
callback(new Error('could not fetch source tables: ' + msg));
return;
}
var qtables = rows[0].cdb_querytables;
var tableNames = qtables.split(/^\{(.*)\}$/)[1];
tableNames = tableNames ? tableNames.split(',') : [];
callback(null, tableNames);
}
QueryTablesApi.prototype.getAffectedTablesAndLastUpdatedTime = function (username, options, sql, callback) {
var query = [
'WITH querytables AS (',
'SELECT * FROM CDB_QueryTables($windshaft$' + prepareSql(sql) + '$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[])'
].join(' ');
runQuery(username, options, query, handleAffectedTablesAndLastUpdatedTimeRows, callback);
};
function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) {
if (err || rows.length === 0) {
var msg = err.message ? err.message : err;
callback(new Error('could not fetch affected tables and last updated time: ' + msg));
return;
}
var result = rows[0];
var tableNames = result.tablenames.split(/^\{(.*)\}$/)[1];
tableNames = tableNames ? tableNames.split(',') : [];
var lastUpdatedTime = result.max || 0;
callback(null, {
affectedTables: tableNames,
lastUpdatedTime: lastUpdatedTime * 1000
});
}
function runQuery(username, options, query, queryHandler, callback) {
if (shouldQueryPostgresDirectly()) {
var psql = new PSQL(options);
psql.query(query, function(err, resultSet) {
resultSet = resultSet || {};
var rows = resultSet.rows || [];
queryHandler(err, rows, callback);
});
} else {
sqlApi.query(username, options.api_key, query, function(err, rows) {
queryHandler(err, rows, callback);
});
}
}
function prepareSql(sql) {
return sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
.replace(affectedTableRegexCache.scale_denominator, '0')
.replace(affectedTableRegexCache.pixel_width, '1')
.replace(affectedTableRegexCache.pixel_height, '1')
;
}
function shouldQueryPostgresDirectly() {
return global.environment
&& global.environment.enabledFeatures
&& global.environment.enabledFeatures.cdbQueryTablesFromPostgres;
}

View File

@@ -0,0 +1,47 @@
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, tables, callback) {
var estimatedExtentSQLs = tables.map(function(table) {
return "ST_EstimatedExtent('" + table.schema_name + "', '" + table.table_name + "', '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, function handleBoundsResult (err, rows) {
if (err) {
var msg = err.message ? err.message : err;
return callback(new Error('could not fetch source tables: ' + msg));
}
var result = null;
if (rows.length > 0) {
result = {
bounds: rows[0]
};
}
callback(null, result);
});
};

View File

@@ -0,0 +1,28 @@
/**
*
* @param metadataBackend
* @param options
* @constructor
* @type {UserLimitsApi}
*/
function UserLimitsApi(metadataBackend, options) {
this.metadataBackend = metadataBackend;
this.options = options || {};
this.options.limits = this.options.limits || {};
}
module.exports = UserLimitsApi;
UserLimitsApi.prototype.getRenderLimits = function (username, callback) {
var self = this;
this.metadataBackend.getTilerRenderLimit(username, function handleTilerLimits(err, renderLimit) {
if (err) {
return callback(err);
}
return callback(null, {
cacheOnTimeout: self.options.limits.cacheOnTimeout || false,
render: renderLimit || self.options.limits.render || 0
});
});
};

View File

@@ -0,0 +1,49 @@
var PSQL = require('cartodb-psql');
function AnalysisStatusBackend() {
}
module.exports = AnalysisStatusBackend;
AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
var nodeId = params.nodeId;
var statusQuery = 'SELECT node_id, status, updated_at FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\'';
var pg = new PSQL(dbParamsFromReqParams(params));
pg.query(statusQuery, function(err, result) {
if (err) {
return callback(err, result);
}
result = result || {};
var rows = result.rows || [];
return callback(null, rows[0] || {
node_id: nodeId,
status: 'unknown'
});
}, true); // use read-only transaction
};
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -0,0 +1,19 @@
var camshaft = require('camshaft');
function AnalysisBackend(options) {
var batchConfig = options.batch || {};
batchConfig.endpoint = batchConfig.endpoint || 'http://127.0.0.1:8080/api/v1/sql/job';
batchConfig.inlineExecution = batchConfig.inlineExecution || false;
batchConfig.hostHeaderTemplate = batchConfig.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
this.batchConfig = batchConfig;
}
module.exports = AnalysisBackend;
AnalysisBackend.prototype.create = function(analysisConfiguration, analysisDefinition, callback) {
analysisConfiguration.batch.endpoint = this.batchConfig.endpoint;
analysisConfiguration.batch.inlineExecution = this.batchConfig.inlineExecution;
analysisConfiguration.batch.hostHeaderTemplate = this.batchConfig.hostHeaderTemplate;
camshaft.create(analysisConfiguration, analysisDefinition, callback);
};

View File

@@ -0,0 +1,280 @@
var assert = require('assert');
var _ = require('underscore');
var PSQL = require('cartodb-psql');
var camshaft = require('camshaft');
var step = require('step');
var Timer = require('../stats/timer');
var BBoxFilter = require('../models/filter/bbox');
var DataviewFactory = require('../models/dataview/factory');
function DataviewBackend(analysisBackend) {
this.analysisBackend = analysisBackend;
}
module.exports = DataviewBackend;
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
query = bboxFilter.sql(query);
}
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins'),
function castNumbers(overrides, val, k) {
overrides[k] = Number.isFinite(+val) ? +val : val;
return overrides;
},
{ownFilter: ownFilter}
);
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
dataview.getResult(pg, overrideParams, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
}
);
};
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
// TODO load this from configuration
endpoint: 'http://127.0.0.1:8080/api/v1/sql/job',
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
query = bboxFilter.sql(query);
}
var userQuery = params.q;
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
dataview.search(pg, userQuery, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
}
);
};
function getAnalysisDefinition(mapConfigAnalyses, sourceId) {
mapConfigAnalyses = mapConfigAnalyses || [];
for (var i = 0; i < mapConfigAnalyses.length; i++) {
var analysisGraph = new camshaft.reference.AnalysisGraph(mapConfigAnalyses[i]);
var nodes = analysisGraph.getNodesWithId();
if (nodes.hasOwnProperty(sourceId)) {
return mapConfigAnalyses[i];
}
}
throw new Error('There is no associated analysis for the dataview source id');
}
function getDataviewDefinition(mapConfig, dataviewName) {
var dataviews = mapConfig.dataviews || {};
return dataviews[dataviewName];
}
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -0,0 +1,136 @@
var assert = require('assert');
var step = require('step');
var PSQL = require('cartodb-psql');
var _ = require('underscore');
function PgConnection(metadataBackend) {
this.metadataBackend = metadataBackend;
}
module.exports = PgConnection;
// Set db authentication parameters to those of the given username
//
// @param username the cartodb username, mapped to a database username
// via CartodbRedis metadata records
//
// @param params the parameters to set auth options into
// added params are: "dbuser" and "dbpassword"
//
// @param callback function(err)
//
PgConnection.prototype.setDBAuth = function(username, params, callback) {
var self = this;
var user_params = {};
var auth_user = global.environment.postgres_auth_user;
var auth_pass = global.environment.postgres_auth_pass;
step(
function getId() {
self.metadataBackend.getUserId(username, this);
},
function(err, user_id) {
assert.ifError(err);
user_params.user_id = user_id;
var dbuser = _.template(auth_user, user_params);
_.extend(params, {dbuser:dbuser});
// skip looking up user_password if postgres_auth_pass
// doesn't contain the "user_password" label
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) {
return null;
}
self.metadataBackend.getUserDBPass(username, this);
},
function(err, user_password) {
assert.ifError(err);
user_params.user_password = user_password;
if ( auth_pass ) {
var dbpass = _.template(auth_pass, user_params);
_.extend(params, {dbpassword:dbpass});
}
return true;
},
function finish(err) {
callback(err);
}
);
};
// Set db connection parameters to those for the given username
//
// @param dbowner cartodb username of database owner,
// mapped to a database username
// via CartodbRedis metadata records
//
// @param params the parameters to set connection options into
// added params are: "dbname", "dbhost"
//
// @param callback function(err)
//
PgConnection.prototype.setDBConn = function(dbowner, params, callback) {
var self = this;
// Add default database connection parameters
// if none given
_.defaults(params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
step(
function getConnectionParams() {
self.metadataBackend.getUserDBConnectionParams(dbowner, this);
},
function extendParams(err, dbParams){
assert.ifError(err);
// we don't want null values or overwrite a non public user
if (params.dbuser !== 'publicuser' || !dbParams.dbuser) {
delete dbParams.dbuser;
}
if ( dbParams ) {
_.extend(params, dbParams);
}
return null;
},
function finish(err) {
callback(err);
}
);
};
/**
* Returns a `cartodb-psql` object for a given username.
* @param {String} username
* @param {Function} callback function({Error}, {PSQL})
*/
PgConnection.prototype.getConnection = function(username, callback) {
var self = this;
var params = {};
require('debug')('cachechan')("getConn1");
step(
function setAuth() {
self.setDBAuth(username, params, this);
},
function setConn(err) {
assert.ifError(err);
self.setDBConn(username, params, this);
},
function openConnection(err) {
assert.ifError(err);
return callback(err, new PSQL({
user: params.dbuser,
pass: params.dbpass,
host: params.dbhost,
port: params.dbport,
dbname: params.dbname
}));
}
);
};

View File

@@ -0,0 +1,46 @@
var assert = require('assert');
var PSQL = require('cartodb-psql');
var step = require('step');
function PgQueryRunner(pgConnection) {
this.pgConnection = pgConnection;
}
module.exports = PgQueryRunner;
/**
* Runs `query` with `username`'s PostgreSQL role, callback receives error and rows array.
*
* @param {String} username
* @param {String} query
* @param {Function} callback function({Error}, {Array}) second argument is guaranteed to be an array
*/
PgQueryRunner.prototype.run = function(username, query, callback) {
var self = this;
var params = {};
step(
function setAuth() {
self.pgConnection.setDBAuth(username, params, this);
},
function setConn(err) {
assert.ifError(err);
self.pgConnection.setDBConn(username, params, this);
},
function executeQuery(err) {
assert.ifError(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 || {};
return callback(err, resultSet.rows || []);
});
}
);
};

View File

@@ -1,7 +1,9 @@
var crypto = require('crypto'),
Step = require('step'),
_ = require('underscore'),
dot = require('dot');
var assert = require('assert');
var crypto = require('crypto');
var debug = require('debug')('windshaft:templates');
var step = require('step');
var _ = require('underscore');
var dot = require('dot');
var EventEmitter = require('events').EventEmitter;
@@ -19,9 +21,11 @@ var util = require('util');
// @param opts TemplateMap options. Supported elements:
// 'max_user_templates' limit on the number of per-user
//
//
//
function TemplateMaps(redis_pool, opts) {
if (!(this instanceof TemplateMaps)) return new TemplateMaps();
if (!(this instanceof TemplateMaps)) {
return new TemplateMaps();
}
EventEmitter.call(this);
@@ -37,18 +41,13 @@ function TemplateMaps(redis_pool, opts) {
//
// Map templates are owned by a user that specifies access permissions
// for their instances.
//
//
// We have the following datastores:
//
// 1. User templates: set of per-user map templates
// User templates (HASH:tpl_id->tpl_val)
this.key_usr_tpl = dot.template("map_tpl|{{=it.owner}}");
// User template locks (HASH:tpl_id->ctime)
this.key_usr_tpl_lck = dot.template("map_tpl|{{=it.owner}}|locks");
this.lock_ttl = this.opts['lock_ttl'] || 5000;
}
util.inherits(TemplateMaps, EventEmitter);
@@ -61,7 +60,7 @@ var o = TemplateMaps.prototype;
//--------------- PRIVATE METHODS --------------------------------
o._userTemplateLimit = function() {
return this.opts['max_user_templates'] || 0;
return this.opts.max_user_templates || 0;
};
/**
@@ -76,33 +75,37 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
var that = this;
var db = that.db_signatures;
Step(
step(
function getRedisClient() {
that.redis_pool.acquire(db, this);
},
function executeQuery(err, data) {
if ( err ) throw err;
assert.ifError(err);
redisClient = data;
redisArgs.push(this);
redisClient[redisFunc.toUpperCase()].apply(redisClient, redisArgs);
},
function releaseRedisClient(err, data) {
if ( ! _.isUndefined(redisClient) ) that.redis_pool.release(db, redisClient);
if ( ! _.isUndefined(redisClient) ) {
that.redis_pool.release(db, redisClient);
}
callback(err, data);
}
);
};
var _reValidIdentifier = /^[a-zA-Z][0-9a-zA-Z_]*$/;
var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i;
// jshint maxcomplexity:15
o._checkInvalidTemplate = function(template) {
if ( template.version != '0.0.1' ) {
if ( template.version !== '0.0.1' ) {
return new Error("Unsupported template version " + template.version);
}
var tplname = template.name;
if ( ! tplname ) {
return new Error("Missing template name");
}
if ( ! tplname.match(_reValidIdentifier) ) {
if ( ! tplname.match(_reValidNameIdentifier) ) {
return new Error("Invalid characters in template name '" + tplname + "'");
}
@@ -117,7 +120,7 @@ o._checkInvalidTemplate = function(template) {
for (var i = 0, len = placeholderKeys.length; i < len; i++) {
var placeholderKey = placeholderKeys[i];
if (!placeholderKey.match(_reValidIdentifier)) {
if (!placeholderKey.match(_reValidPlaceholderIdentifier)) {
return new Error("Invalid characters in placeholder name '" + placeholderKey + "'");
}
if ( ! placeholders[placeholderKey].hasOwnProperty('default') ) {
@@ -134,14 +137,15 @@ o._checkInvalidTemplate = function(template) {
case 'open':
break;
case 'token':
if ( ! _.isArray(auth.valid_tokens) )
if ( ! _.isArray(auth.valid_tokens) ) {
return new Error("Invalid 'token' authentication: missing valid_tokens");
if ( ! auth.valid_tokens.length )
}
if ( ! auth.valid_tokens.length ) {
return new Error("Invalid 'token' authentication: no valid_tokens");
}
break;
default:
return new Error("Unsupported authentication method: " + auth.method);
break;
}
return false;
@@ -193,7 +197,7 @@ function templateDefaults(template) {
// @param template layergroup template, see
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err, tpl_id)
// @param callback function(err, tpl_id)
// Return template identifier (only valid for given user)
//
o.addTemplate = function(owner, template, callback) {
@@ -210,7 +214,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;
@@ -218,19 +222,17 @@ o.addTemplate = function(owner, template, callback) {
self._redisCmd('HLEN', [ userTemplatesKey ], this);
},
function installTemplateIfDoesNotExist(err, numberOfTemplates) {
if ( err ) {
throw err;
}
assert.ifError(err);
if ( limit && numberOfTemplates >= limit ) {
throw new Error("User '" + owner + "' reached limit on number of templates " +
"("+ numberOfTemplates + "/" + limit + ")");
var limitReachedError = new Error("User '" + owner + "' reached limit on number of templates (" +
numberOfTemplates + "/" + limit + ")");
limitReachedError.http_status = 409;
throw limitReachedError;
}
self._redisCmd('HSETNX', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
},
function validateInstallation(err, wasSet) {
if ( err ) {
throw err;
}
assert.ifError(err);
if ( ! wasSet ) {
throw new Error("Template '" + templateName + "' of user '" + owner + "' already exists");
}
@@ -258,14 +260,12 @@ 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);
},
function handleDeletion(err, deleted) {
if (err) {
throw err;
}
assert.ifError(err);
if (!deleted) {
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
}
@@ -296,7 +296,7 @@ o.delTemplate = function(owner, tpl_id, callback) {
// http://github.com/CartoDB/Windshaft-cartodb/wiki/Template-maps#template-format
//
// @param callback function(err)
//
//
o.updTemplate = function(owner, tpl_id, template, callback) {
var self = this;
@@ -310,37 +310,38 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
var templateName = template.name;
if ( tpl_id != templateName ) {
if ( tpl_id !== templateName ) {
return callback(new Error("Cannot update name of a map template ('" + tpl_id + "' != '" + templateName + "')"));
}
var userTemplatesKey = this.key_usr_tpl({ owner:owner });
Step(
var previousTemplate = null;
step(
function getExistingTemplate() {
self._redisCmd('HGET', [ userTemplatesKey, tpl_id ], this);
},
function updateTemplate(err, currentTemplate) {
if (err) {
throw err;
}
if (!currentTemplate) {
function updateTemplate(err, _currentTemplate) {
assert.ifError(err);
if (!_currentTemplate) {
throw new Error("Template '" + tpl_id + "' of user '" + owner + "' does not exist");
}
previousTemplate = _currentTemplate;
self._redisCmd('HSET', [ userTemplatesKey, templateName, JSON.stringify(template) ], this);
},
function handleTemplateUpdate(err, didSetNewField) {
if (err) {
throw err;
}
assert.ifError(err);
if (didSetNewField) {
console.warn('New template created on update operation');
debug('New template created on update operation');
}
return true;
},
function finish(err) {
if (!err) {
self.emit('update', owner, templateName, template);
if (self.fingerPrint(JSON.parse(previousTemplate)) !== self.fingerPrint(template)) {
self.emit('update', owner, templateName, template);
}
}
callback(err, template);
@@ -354,7 +355,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
//
// @param callback function(err, tpl_id_list)
// Returns a list of template identifiers
//
//
o.listTemplates = function(owner, callback) {
this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
};
@@ -371,12 +372,12 @@ 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);
},
function parseTemplate(err, tpl_val) {
if ( err ) throw err;
assert.ifError(err);
return JSON.parse(tpl_val);
},
function finish(err, tpl) {
@@ -421,7 +422,7 @@ o.isAuthorized = function(template, authTokens) {
// Only the ones found in the template's placeholders object
// will be used, with missing ones taking default values.
//
// @returns a layergroup configuration
// @returns a layergroup configuration
//
// @throws Error on malformed template or parameter
//
@@ -429,14 +430,14 @@ var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
_reCSSColorName = /^[a-zA-Z]+$/,
_reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
_replaceVars = function(str, params) {
//return _.template(str, params); // lazy way, possibly dangerous
function _replaceVars (str, params) {
//return _.template(str, params); // lazy way, possibly dangerous
// Construct regular expressions for each param
Object.keys(params).forEach(function(k) {
str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
});
return str;
};
}
o.instance = function(template, params) {
var all_params = {};
var phold = template.placeholders || {};
@@ -455,16 +456,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 {
@@ -478,8 +477,16 @@ o.instance = function(template, params) {
var layergroup = JSON.parse(JSON.stringify(template.layergroup));
for (var i=0; i<layergroup.layers.length; ++i) {
var lyropt = layergroup.layers[i].options;
if ( lyropt.cartocss ) lyropt.cartocss = _replaceVars(lyropt.cartocss, all_params);
if ( lyropt.sql) lyropt.sql = _replaceVars(lyropt.sql, all_params);
if ( params.styles && params.styles[i] ) {
// dynamic styling for this layer
lyropt.cartocss = params.styles[i];
} else if ( lyropt.cartocss ) {
lyropt.cartocss = _replaceVars(lyropt.cartocss, all_params);
}
if ( lyropt.sql) {
lyropt.sql = _replaceVars(lyropt.sql, all_params);
}
// Anything else ?
}
@@ -499,3 +506,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;
};

16
lib/cartodb/cache/backend/fastly.js vendored Normal file
View File

@@ -0,0 +1,16 @@
var FastlyPurge = require('fastly-purge');
function FastlyCacheBackend(apiKey, serviceId) {
this.serviceId = serviceId;
this.fastlyPurge = new FastlyPurge(apiKey, { softPurge: false });
}
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);
};

View File

@@ -28,5 +28,3 @@ VarnishHttpCacheBackend.prototype.invalidate = function(cacheObject, callback) {
}
);
};
module.exports = VarnishHttpCacheBackend;

View File

@@ -0,0 +1,24 @@
var LruCache = require('lru-cache');
function LayergroupAffectedTables() {
// dbname + layergroupId -> affected tables cache
this.cache = new LruCache({ max: 2000 });
}
module.exports = LayergroupAffectedTables;
LayergroupAffectedTables.prototype.hasAffectedTables = function(dbName, layergroupId) {
return this.cache.has(createKey(dbName, layergroupId));
};
LayergroupAffectedTables.prototype.set = function(dbName, layergroupId, affectedTables) {
this.cache.set(createKey(dbName, layergroupId), affectedTables);
};
LayergroupAffectedTables.prototype.get = function(dbName, layergroupId) {
return this.cache.get(createKey(dbName, layergroupId));
};
function createKey(dbName, layergroupId) {
return dbName + ':' + layergroupId;
}

View File

@@ -0,0 +1,96 @@
var _ = require('underscore');
var dot = require('dot');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
var templateName = require('../backends/template_maps').templateName;
var queue = require('queue-async');
var LruCache = require("lru-cache");
function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, overviewsAdapter,
turboCartoAdapter) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter();
this.overviewsAdapter = overviewsAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.providerCache = new LruCache({ max: 2000 });
}
module.exports = NamedMapProviderCache;
NamedMapProviderCache.prototype.get = function(user, templateId, config, authToken, params, callback) {
var namedMapKey = createNamedMapKey(user, templateId);
var namedMapProviders = this.providerCache.get(namedMapKey) || {};
var providerKey = createProviderKey(config, authToken, params);
if (!namedMapProviders.hasOwnProperty(providerKey)) {
namedMapProviders[providerKey] = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.namedLayersAdapter,
this.overviewsAdapter,
this.turboCartoAdapter,
this.analysisMapConfigAdapter,
user,
templateId,
config,
authToken,
params
);
this.providerCache.set(namedMapKey, namedMapProviders);
// early exit, if provider did not exist we just return it
return callback(null, namedMapProviders[providerKey]);
}
var namedMapProvider = namedMapProviders[providerKey];
var self = this;
queue(2)
.defer(namedMapProvider.getTemplate.bind(namedMapProvider))
.defer(this.templateMaps.getTemplate.bind(this.templateMaps), user, templateId)
.awaitAll(function templatesQueueDone(err, results) {
if (err) {
return callback(err);
}
// We want to reset provider its template has changed
// Ideally this should be done in a passive mode where this cache gets notified of template changes
var uniqueFingerprints = _.uniq(results.map(self.templateMaps.fingerPrint)).length;
if (uniqueFingerprints > 1) {
namedMapProvider.reset();
}
return callback(null, namedMapProvider);
});
};
NamedMapProviderCache.prototype.invalidate = function(user, templateId) {
this.providerCache.del(createNamedMapKey(user, templateId));
};
function createNamedMapKey(user, templateId) {
return user + ':' + templateName(templateId);
}
var providerKey = '{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}';
var providerKeyTpl = dot.template(providerKey);
function createProviderKey(config, authToken, params) {
var tplValues = _.defaults({}, params, {
authToken: authToken || '',
configHash: NamedMapMapConfigProvider.configHash(config),
layer: '',
format: '',
scale_factor: 1
});
return providerKeyTpl(tplValues);
}

View File

@@ -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;
@@ -14,13 +16,38 @@ module.exports = SurrogateKeysCache;
* @param cacheObject should respond to `key() -> String` method
*/
SurrogateKeysCache.prototype.tag = function(response, cacheObject) {
response.header('Surrogate-Key', cacheObject.key());
var newKey = cacheObject.key();
response.set('Surrogate-Key', appendSurrogateKey(
response.get('Surrogate-Key'),
Array.isArray(newKey) ? cacheObject.key().join(' ') : newKey
));
};
function appendSurrogateKey(currentKey, newKey) {
if (!!currentKey) {
newKey = currentKey + ' ' + newKey;
}
return newKey;
}
/**
* @param cacheObject should respond to `key() -> String` method
* @param {Function} callback
*/
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);
});
};

View File

@@ -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
}

View File

@@ -1,262 +0,0 @@
var _ = require('underscore')
, Step = require('step')
, Windshaft = require('windshaft')
, TemplateMaps = require('./template_maps.js')
, Cache = require('./cache_validator')
, os = require('os')
, HealthCheck = require('./monitoring/health_check')
;
if ( ! process.env['PGAPPNAME'] )
process.env['PGAPPNAME']='cartodb_tiler';
var CartodbWindshaft = function(serverOptions) {
// Perform keyword substitution in statsd
// See https://github.com/CartoDB/Windshaft-cartodb/issues/153
if ( global.environment.statsd ) {
if ( global.environment.statsd.prefix ) {
var host_token = os.hostname().split('.').reverse().join('.');
global.environment.statsd.prefix = global.environment.statsd.prefix.replace(/:host/, host_token);
}
}
var redisPool = serverOptions.redis.pool
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:cartodb'}));
var cartoData = require('cartodb-redis')({pool: redisPool});
if(serverOptions.cache_enabled) {
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
serverOptions.afterStateChange = function(req, data, callback) {
Cache.invalidate_db(req.params.dbname, req.params.table);
callback(null, data);
}
}
serverOptions.beforeStateChange = function(req, callback) {
var err = null;
if ( ! req.params.hasOwnProperty('_authorizedByApiKey') ) {
err = new Error("map state cannot be changed by unauthenticated request!");
}
callback(err, req);
};
// 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 templateMapsOpts = {
max_user_templates: global.environment.maxUserTemplates
};
var templateMaps = new TemplateMaps(redisPool, templateMapsOpts);
serverOptions.templateMaps = templateMaps;
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
VarnishHttpCacheBackend = require('./cache/backend/varnish_http'),
varnishHttpCacheBackend = new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port),
surrogateKeysCache = new SurrogateKeysCache(varnishHttpCacheBackend);
if (serverOptions.varnish_purge_enabled) {
function invalidateNamedMap(owner, templateName) {
surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
if (err) {
console.warn('Cache: surrogate key invalidation failed');
}
});
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
});
}
// boot
var ws = new Windshaft.Server(serverOptions);
// Override getVersion to include cartodb-specific versions
var wsversion = ws.getVersion;
ws.getVersion = function() {
var version = wsversion();
version.windshaft_cartodb = require('../../package.json').version;
return version;
};
var ws_sendResponse = ws.sendResponse;
// GET routes for which we don't want to request any caching.
// POST/PUT/DELETE requests are never cached anyway.
var noCacheGETRoutes = [
'/',
// See https://github.com/CartoDB/Windshaft-cartodb/issues/176
serverOptions.base_url_mapconfig,
template_baseurl + '/:template_id/jsonp'
];
ws.sendResponse = function(res, args) {
var that = this;
var thatArgs = arguments;
var statusCode;
if ( res._windshaftStatusCode ) {
// Added by our override of sendError
statusCode = res._windshaftStatusCode;
} else {
if ( args.length > 2 ) statusCode = args[2];
else {
statusCode = args[1] || 200;
}
}
var req = res.req;
Step (
function addCacheChannel() {
if ( ! req ) {
// having no associated request can happen when
// using fake response objects for testing layergroup
// creation
return false;
}
if ( ! req.params ) {
// service requests (/version, /)
// have no need for an X-Cache-Channel
return false;
}
if ( statusCode != 200 ) {
// We do not want to cache
// unsuccessful responses
return false;
}
if ( _.contains(noCacheGETRoutes, req.route.path) ) {
//console.log("Skipping cache channel in route:\n" + req.route.path);
return false;
}
//console.log("Adding cache channel to route\n" + req.route.path + " not matching any in:\n" + mapCreateRoutes.join("\n"));
serverOptions.addCacheChannel(that, req, this);
},
function sendResponse(err, added) {
if ( err ) console.log(err + err.stack);
ws_sendResponse.apply(that, thatArgs);
return null;
},
function finish(err) {
if ( err ) console.log(err + err.stack);
}
);
};
var ws_sendError = ws.sendError;
ws.sendError = function() {
var res = arguments[0];
var statusCode = arguments[2];
res._windshaftStatusCode = statusCode;
ws_sendError.apply(this, arguments);
};
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
var TemplateMapsController = require('./controllers/template_maps'),
templateMapsController = new TemplateMapsController(
ws, serverOptions, templateMaps, cartoData, template_baseurl, surrogateKeysCache, NamedMapsCacheEntry
);
templateMapsController.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.
* TODO: Move?
*/
ws.del(serverOptions.base_url + '/flush_cache', function(req, res){
if ( req.profiler && req.profiler.statsd_client ) {
req.profiler.start('windshaft-cartodb.flush_cache');
}
ws.doCORS(res);
Step(
function flushCache(){
serverOptions.flushCache(req, serverOptions.cache_enabled ? Cache : null, this);
},
function sendResponse(err, data){
if (err){
ws.sendError(res, {error: err.message}, 500, 'DELETE CACHE', err);
//ws.sendResponse(res, [500]);
} else {
ws.sendResponse(res, [{status: 'ok'}, 200]);
}
}
);
});
var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive);
ws.get('/health', function(req, res) {
var healthConfig = global.environment.health || {};
if (!!healthConfig.enabled) {
var startTime = Date.now();
healthCheck.check(healthConfig, function(err, result) {
var ok = !err;
var response = {
enabled: true,
ok: ok,
elapsed: Date.now() - startTime,
result: result
};
if (err) {
response.err = err.message;
}
res.send(response, ok ? 200 : 503);
});
} else {
res.send({enabled: false, ok: true}, 200);
}
});
return ws;
};
module.exports = CartodbWindshaft;

View File

@@ -0,0 +1,266 @@
var assert = require('assert');
var _ = require('underscore');
var step = require('step');
var debug = require('debug')('windshaft:cartodb');
var LZMA = require('lzma').LZMA;
var lzmaWorker = new LZMA();
// Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [
'config',
'map_key',
'api_key',
'auth_token',
'callback',
// widgets & filters
'filters', // json
'own_filter', // 0, 1
'bbox', // w,s,e,n
'bins', // number
'start', // number
'end', // number
'column_type', // string
// widgets search
'q'
];
function BaseController(authApi, pgConnection) {
this.authApi = authApi;
this.pgConnection = pgConnection;
}
module.exports = BaseController;
// jshint maxcomplexity:9
/**
* Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis
* @param req - standard express request obj. Should have host & table
* @param callback
*/
BaseController.prototype.req2params = function(req, callback){
var self = this;
if ( 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;
});
// Decompress
lzmaWorker.decompress(
lzma,
function(result) {
if (req.profiler) {
req.profiler.done('lzma');
}
try {
delete req.query.lzma;
_.extend(req.query, JSON.parse(result));
self.req2params(req, callback);
} catch (err) {
req.profiler.done('req2params');
callback(new Error('Error parsing lzma as JSON: ' + err));
}
}
);
return;
}
req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST);
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
var user = req.context.user;
if ( req.params.token ) {
// Token might match the following patterns:
// - {user}@{tpl_id}@{token}:{cache_buster}
var tksplit = req.params.token.split(':');
req.params.token = tksplit[0];
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 db of user "' + user + '"'
);
err.http_status = 403;
req.profiler.done('req2params');
callback(err);
return;
}
if ( tksplit.length > 1 ) {
/*var template_hash = */tksplit.shift(); // unused
}
req.params.token = tksplit.shift();
}
}
// bring all query values onto req.params object
_.extend(req.params, req.query);
if (req.profiler) {
req.profiler.done('req2params.setup');
}
step(
function getPrivacy(){
self.authApi.authorize(req, this);
},
function validateAuthorization(err, authorized) {
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;
throw err;
}
return null;
},
function getDatabase(err){
assert.ifError(err);
self.pgConnection.setDBConn(user, req.params, this);
},
function finishSetup(err) {
if ( err ) {
req.profiler.done('req2params');
return callback(err, req);
}
// Add default database connection parameters
// if none given
_.defaults(req.params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
req.profiler.done('req2params');
callback(null, req);
}
);
};
// jshint maxcomplexity:6
// jshint maxcomplexity:9
BaseController.prototype.send = function(req, res, body, status, headers) {
if (req.params.dbhost) {
res.set('X-Served-By-DB-Host', req.params.dbhost);
}
if (req.profiler) {
res.set('X-Tiler-Profiler', req.profiler.toJSONString());
}
if (headers) {
res.set(headers);
}
res.status(status);
if (!Buffer.isBuffer(body) && typeof body === 'object') {
if (req.query && req.query.callback) {
res.jsonp(body);
} else {
res.json(body);
}
} else {
res.send(body);
}
if (req.profiler) {
try {
// May throw due to dns, see
// See http://github.com/CartoDB/Windshaft/issues/166
req.profiler.sendStats();
} catch (err) {
debug("error sending profiling stats: " + err);
}
}
};
// jshint maxcomplexity:6
BaseController.prototype.sendError = function(req, res, err, label) {
var allErrors = Array.isArray(err) ? err : [err];
label = label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {
statusCode = 200;
}
var errorResponseBody = { errors: allErrors.map(errorMessage) };
this.send(req, res, errorResponseBody, statusCode);
};
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
.replace(/Connection string: '[^']*'\n\s/im, '')
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
module.exports.errorMessage = errorMessage;
function findStatusCode(err) {
var statusCode;
if ( err.http_status ) {
statusCode = err.http_status;
} else {
statusCode = statusFromErrorMessage('' + err);
}
return statusCode;
}
module.exports.findStatusCode = findStatusCode;
function statusFromErrorMessage(errMsg) {
// Find an appropriate statusCode based on message
// jshint maxcomplexity:7
var statusCode = 400;
if ( -1 !== errMsg.indexOf('permission denied') ) {
statusCode = 403;
}
else if ( -1 !== errMsg.indexOf('authentication failed') ) {
statusCode = 403;
}
else if (errMsg.match(/Postgis Plugin.*[\s|\n].*column.*does not exist/)) {
statusCode = 400;
}
else if ( -1 !== errMsg.indexOf('does not exist') ) {
if ( -1 !== errMsg.indexOf(' role ') ) {
statusCode = 403; // role 'xxx' does not exist
} else if ( errMsg.match(/function .* does not exist/) ) {
statusCode = 400; // invalid SQL (SQL function does not exist)
} else {
statusCode = 404;
}
}
return statusCode;
}

View File

@@ -0,0 +1,7 @@
module.exports = {
Layergroup: require('./layergroup'),
Map: require('./map'),
NamedMaps: require('./named_maps'),
NamedMapsAdmin: require('./named_maps_admin'),
ServerInfo: require('./server_info')
};

View File

@@ -0,0 +1,489 @@
var assert = require('assert');
var step = require('step');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var DataviewBackend = require('../backends/dataview');
var AnalysisStatusBackend = require('../backends/analysis-status');
var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider');
var QueryTables = require('cartodb-query-tables');
/**
* @param {AuthApi} authApi
* @param {PgConnection} pgConnection
* @param {MapStore} mapStore
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @param {WidgetBackend} widgetBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
this.widgetBackend = widgetBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTables = layergroupAffectedTables;
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
}
util.inherits(LayergroupController, BaseController);
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y@:scale_factor?x.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.layer.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/attributes/:fid', cors(), userMiddleware,
this.attributes.bind(this));
app.get(app.base_url_mapconfig +
'/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(), userMiddleware,
this.center.bind(this));
app.get(app.base_url_mapconfig +
'/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(), userMiddleware,
this.bbox.bind(this));
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName', cors(), userMiddleware,
this.widget.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName/search', cors(), userMiddleware,
this.widgetSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName', cors(), userMiddleware,
this.dataview.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName/search', cors(), userMiddleware,
this.dataviewSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
this.analysisNodeStatus.bind(this));
};
LayergroupController.prototype.analysisNodeStatus = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveNodeStatus(err) {
assert.ifError(err);
self.analysisStatusBackend.getNodeStatus(req.params, this);
},
function finish(err, nodeStatus, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET NODE STATUS');
} else {
self.sendResponse(req, res, nodeStatus, 200, {
'Cache-Control': 'public,max-age=5',
'Last-Modified': new Date().toUTCString()
});
}
}
);
};
LayergroupController.prototype.dataview = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveDataview(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.dataviewBackend.getDataview(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, dataview, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW');
} else {
self.sendResponse(req, res, dataview, 200);
}
}
);
};
LayergroupController.prototype.dataviewSearch = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function searchDataview(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.dataviewBackend.search(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW SEARCH');
} else {
self.sendResponse(req, res, searchResult, 200);
}
}
);
};
LayergroupController.prototype.widget = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.getWidget(mapConfigProvider, req.params, this);
},
function finish(err, widget, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, widget, 200);
}
}
);
};
LayergroupController.prototype.widgetSearch = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.search(mapConfigProvider, req.params, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, searchResult, 200);
}
}
);
};
LayergroupController.prototype.attributes = function(req, res) {
var self = this;
req.profiler.start('windshaft.maplayer_attribute');
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveFeatureAttributes(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, req.params, false, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET ATTRIBUTES');
} else {
self.sendResponse(req, res, tile, 200);
}
}
);
};
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
LayergroupController.prototype.tile = function(req, res) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
LayergroupController.prototype.layer = function(req, res, next) {
if (req.params.token === 'static') {
return next();
}
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res);
};
LayergroupController.prototype.tileOrLayer = function (req, res) {
var self = this;
step(
function mapController$prepareParams() {
self.req2params(req, this);
},
function mapController$getTileOrGrid(err) {
assert.ifError(err);
self.tileBackend.getTile(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
req.params, this
);
},
function mapController$finalize(err, tile, headers, stats) {
req.profiler.add(stats);
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
}
);
};
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
var supportedFormats = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true
};
var formatStat = 'invalid';
if (req.params.format) {
var format = req.params.format.replace('.', '_');
if (supportedFormats[format]) {
formatStat = format;
}
}
if (err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
// Rewrite mapnik parsing errors to start with layer number
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
if (matches) {
errMsg = 'style'+matches[2]+': ' + matches[1];
}
err.message = errMsg;
this.sendError(req, res, err, 'TILE RENDER');
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
this.sendResponse(req, res, tile, 200, headers);
global.statsClient.increment('windshaft.tiles.success');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
}
};
LayergroupController.prototype.bbox = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
});
};
LayergroupController.prototype.center = function(req, res) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
});
};
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.layer = 'all';
req.params.format = 'png';
var self = this;
step(
function reqParams() {
self.req2params(req, this);
},
function getImage(err) {
assert.ifError(err);
if (center) {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
format, width, height, zoom /* bounds */, this);
}
},
function handleImage(err, image, headers, stats) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'STATIC_MAP');
} else {
res.set('Content-Type', headers['Content-Type'] || 'image/' + format);
self.sendResponse(req, res, image, 200);
}
}
);
};
LayergroupController.prototype.sendResponse = function(req, res, body, status, headers) {
var self = this;
res.set('Cache-Control', 'public,max-age=31536000');
// Set Last-Modified header
var lastUpdated;
if (req.params.cache_buster) {
// Assuming cache_buster is a timestamp
lastUpdated = new Date(parseInt(req.params.cache_buster));
} else {
lastUpdated = new Date();
}
res.set('Last-Modified', lastUpdated.toUTCString());
var dbName = req.params.dbname;
step(
function getAffectedTables() {
self.getAffectedTables(req.context.user, dbName, req.params.token, this);
},
function sendResponse(err, affectedTables) {
req.profiler.done('affectedTables');
if (err) {
global.logger.warn('ERROR generating cache channel: ' + err);
}
if (!!affectedTables) {
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
self.surrogateKeysCache.tag(res, affectedTables);
}
self.send(req, res, body, status, headers);
}
);
};
LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) {
if (this.layergroupAffectedTables.hasAffectedTables(dbName, layergroupId)) {
return callback(null, this.layergroupAffectedTables.get(dbName, layergroupId));
}
var self = this;
step(
function extractSQL() {
step(
function loadFromStore() {
self.mapStore.load(layergroupId, this);
},
function getSQL(err, mapConfig) {
assert.ifError(err);
var queries = mapConfig.getLayers()
.map(function(lyr) {
return lyr.options.sql;
})
.filter(function(sql) {
return !!sql;
});
return queries.length ? queries.join(';') : null;
},
this
);
},
function findAffectedTables(err, sql) {
assert.ifError(err);
if ( ! sql ) {
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
step(
function getConnection() {
self.pgConnection.getConnection(user, this);
},
function getAffectedTables(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
this
);
},
function buildCacheChannel(err, tables) {
assert.ifError(err);
self.layergroupAffectedTables.set(dbName, layergroupId, tables);
return tables;
},
callback
);
};

View File

@@ -0,0 +1,462 @@
var _ = require('underscore');
var assert = require('assert');
var step = require('step');
var windshaft = require('windshaft');
var QueryTables = require('cartodb-query-tables');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var MapConfig = windshaft.model.MapConfig;
var Datasource = windshaft.model.Datasource;
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider');
/**
* @param {AuthApi} authApi
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @param {MapBackend} mapBackend
* @param metadataBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigOverviewsAdapter} overviewsAdapter
* @param {TurboCartoAdapter} turboCartoAdapter
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables,
overviewsAdapter, turboCartoAdapter, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
this.metadataBackend = metadataBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTables = layergroupAffectedTables;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend);
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.overviewsAdapter = overviewsAdapter;
}
util.inherits(MapController, BaseController);
module.exports = MapController;
MapController.prototype.register = function(app) {
app.get(app.base_url_mapconfig, cors(), userMiddleware, this.createGet.bind(this));
app.post(app.base_url_mapconfig, cors(), userMiddleware, this.createPost.bind(this));
app.get(app.base_url_templated + '/:template_id/jsonp', cors(), userMiddleware, this.jsonp.bind(this));
app.post(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.instantiate.bind(this));
app.options(app.base_url_mapconfig, cors('Content-Type'));
};
MapController.prototype.createGet = function(req, res){
req.profiler.start('windshaft.createmap_get');
this.create(req, res, function createGet$prepareConfig(err, req) {
assert.ifError(err);
if ( ! req.params.config ) {
throw new Error('layergroup GET needs a "config" parameter');
}
return JSON.parse(req.params.config);
});
};
MapController.prototype.createPost = function(req, res) {
req.profiler.start('windshaft.createmap_post');
this.create(req, res, function createPost$prepareConfig(err, req) {
assert.ifError(err);
if (!req.is('application/json')) {
throw new Error('layergroup POST data must be of type application/json');
}
return req.body;
});
};
MapController.prototype.instantiate = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_post');
}
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
if (!req.is('application/json')) {
return callback(new Error('Template POST data must be of type application/json'));
}
return callback(null, req.body);
});
};
MapController.prototype.jsonp = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
var err = null;
if ( req.query.callback === undefined || req.query.callback.length === 0) {
err = new Error('callback parameter should be present and be a function name');
}
var templateParams = {};
if (req.query.config) {
try {
templateParams = JSON.parse(req.query.config);
} catch(e) {
err = new Error('Invalid config parameter, should be a valid JSON');
}
}
return callback(err, templateParams);
});
};
MapController.prototype.create = function(req, res, prepareConfigFn) {
var self = this;
var mapConfig;
var analysesResults = [];
step(
function setupParams(){
self.req2params(req, this);
},
prepareConfigFn,
function prepareAnalysisLayers(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
user: req.params.dbuser,
pass: req.params.dbpassword
},
batch: {
username: req.context.user,
apiKey: req.params.api_key
}
};
var filters = {};
if (req.params.filters) {
try {
filters = JSON.parse(req.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
},
function beforeLayergroupCreate(err, requestMapConfig, _analysesResults) {
assert.ifError(err);
var next = this;
analysesResults = _analysesResults;
self.namedLayersAdapter.getLayers(req.context.user, requestMapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
}
);
},
function addOverviewsInformation(err, requestMapConfig, datasource) {
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers, function(err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
});
},
function parseTurboCarto(err, requestMapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
});
},
function createLayergroup(err, requestMapConfig, datasource) {
assert.ifError(err);
mapConfig = new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource());
self.mapBackend.createLayergroup(
mapConfig, req.params,
new CreateLayergroupMapConfigProvider(mapConfig, req.context.user, self.userLimitsApi, req.params),
this
);
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, analysesResults, layergroup, this);
},
function finish(err, layergroup) {
if (err) {
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
} else {
addWidgetsUrl(req.context.user, layergroup);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.send(req, res, layergroup, 200);
}
}
);
};
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
var self = this;
var cdbuser = req.context.user;
var mapConfigProvider;
var mapConfig;
step(
function setupParams(){
self.req2params(req, this);
},
function getTemplateParams() {
prepareParamsFn(this);
},
function getTemplate(err, templateParams) {
assert.ifError(err);
mapConfigProvider = new NamedMapMapConfigProvider(
self.templateMaps,
self.pgConnection,
self.metadataBackend,
self.userLimitsApi,
self.namedLayersAdapter,
self.overviewsAdapter,
self.turboCartoAdapter,
self.analysisMapConfigAdapter,
cdbuser,
req.params.template_id,
templateParams,
req.query.auth_token,
req.params
);
mapConfigProvider.getMapConfig(this);
},
function createLayergroup(err, mapConfig_, rendererParams) {
assert.ifError(err);
mapConfig = mapConfig_;
self.mapBackend.createLayergroup(
mapConfig, rendererParams,
new CreateLayergroupMapConfigProvider(mapConfig, cdbuser, self.userLimitsApi, rendererParams),
this
);
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, [], layergroup, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
self.sendError(req, res, err, 'NAMED MAP LAYERGROUP');
} else {
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
addWidgetsUrl(cdbuser, layergroup);
addDataviewsUrls(cdbuser, layergroup, mapConfig.obj());
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
self.send(req, res, layergroup, 200);
}
}
);
};
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, analysesResults, layergroup, callback) {
var self = this;
var username = req.context.user;
var tasksleft = 2; // redis key and affectedTables
var errors = [];
var done = function(err) {
if ( err ) {
errors.push('' + err);
}
if ( ! --tasksleft ) {
err = errors.length ? new Error(errors.join('\n')) : null;
callback(err, layergroup);
}
};
// include in layergroup response the variables in serverMedata
// those variables are useful to send to the client information
// about how to reach this server or information about it
_.extend(layergroup, global.environment.serverMetadata);
// Don't wait for the mapview count increment to
// take place before proceeding. Error will be logged
// asynchronously
this.metadataBackend.incMapviewCount(username, mapconfig.obj().stat_tag, function(err) {
if (req.profiler) {
req.profiler.done('incMapviewCount');
}
if ( err ) {
global.logger.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
}
done();
});
var sql = mapconfig.getLayers().map(function(layer) {
return layer.options.sql;
}).join(';');
var dbName = req.params.dbname;
var layergroupId = layergroup.layergroupid;
step(
function getPgConnection() {
self.pgConnection.getConnection(username, this);
},
function getAffectedTablesAndLastUpdatedTime(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
function handleAffectedTablesAndLastUpdatedTime(err, result) {
if (req.profiler) {
req.profiler.done('queryTablesAndLastUpdated');
}
assert.ifError(err);
// feed affected tables cache so it can be reused from, for instance, layergroup controller
self.layergroupAffectedTables.set(dbName, layergroupId, result);
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + result.getLastUpdatedAt();
layergroup.last_updated = new Date(result.getLastUpdatedAt()).toISOString();
// TODO this should take into account several URL patterns
addWidgetsUrl(username, layergroup);
addDataviewsUrls(username, layergroup, mapconfig.obj());
addAnalysesMetadata(username, layergroup, analysesResults, true);
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
res.set('Last-Modified', (new Date()).toUTCString());
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables && result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
}
}
return null;
},
function finish(err) {
done(err);
}
);
};
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
analysesResults.forEach(function(analysis) {
var nodes = analysis.getSortedNodes();
layergroup.metadata.analyses.push({
nodes: nodes.reduce(function(nodesIdMap, node) {
if (node.params.id) {
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
nodesIdMap[node.params.id] = {
status: node.getStatus(),
url: getUrls(username, nodeResource)
};
if (includeQuery) {
nodesIdMap[node.params.id].query = node.getQuery();
}
}
return nodesIdMap;
}, {})
});
});
}
function addDataviewsUrls(username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
Object.keys(dataviews).forEach(function(dataviewName) {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: getUrls(username, resource)
};
});
}
function addWidgetsUrl(username, layergroup) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (layer.widgets) {
Object.keys(layer.widgets).forEach(function(widgetName) {
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
layer.widgets[widgetName].url = getUrls(username, resource);
});
}
return layer;
});
}
}
function getUrls(username, resource) {
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url;
if (cdnUrl) {
return {
http: 'http://' + cdnUrl.http + '/' + username + '/api/v1/map/' + resource,
https: 'https://' + cdnUrl.https + '/' + username + '/api/v1/map/' + resource
};
} else {
var port = global.environment.port;
return {
http: 'http://' + username + '.' + 'localhost.lan:' + port + '/api/v1/map/' + resource
};
}
}

View File

@@ -0,0 +1,277 @@
var step = require('step');
var assert = require('assert');
var _ = require('underscore');
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend,
surrogateKeysCache, tablesExtentApi, metadataBackend) {
BaseController.call(this, authApi, pgConnection);
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
this.metadataBackend = metadataBackend;
}
util.inherits(NamedMapsController, BaseController);
module.exports = NamedMapsController;
NamedMapsController.prototype.register = function(app) {
app.get(app.base_url_templated +
'/:template_id/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.tile.bind(this));
app.get(app.base_url_mapconfig +
'/static/named/:template_id/:width/:height.:format', cors(), userMiddleware,
this.staticMap.bind(this));
};
NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName()));
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
var self = this;
step(
function getAffectedTablesAndLastUpdatedTime() {
namedMapProvider.getAffectedTablesAndLastUpdatedTime(this);
},
function sendResponse(err, result) {
req.profiler.done('affectedTables');
if (err) {
global.logger.log('ERROR generating cache channel: ' + err);
}
if (!result || !!result.tables) {
// we increase cache control as we can invalidate it
res.set('Cache-Control', 'public,max-age=31536000');
var lastModifiedDate;
if (Number.isFinite(result.lastUpdatedTime)) {
lastModifiedDate = new Date(result.getLastUpdatedAt());
} else {
lastModifiedDate = new Date();
}
res.set('Last-Modified', lastModifiedDate.toUTCString());
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
}
}
self.send(req, res, resource, 200);
}
);
};
NamedMapsController.prototype.tile = function(req, res) {
var self = this;
var cdbUser = req.context.user;
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
this
);
},
function getTile(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.tileBackend.getTile(namedMapProvider, req.params, this);
},
function handleImage(err, tile, headers, stats) {
if (req.profiler) {
req.profiler.add(stats);
}
if (err) {
self.sendError(req, res, err, 'NAMED_MAP_TILE');
} else {
self.sendResponse(req, res, tile, headers, namedMapProvider);
}
}
);
};
NamedMapsController.prototype.staticMap = function(req, res) {
var self = this;
var cdbUser = req.context.user;
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.format = 'png';
req.params.layer = 'all';
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
this
);
},
function prepareImageOptions(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.getStaticImageOptions(cdbUser, namedMapProvider, this);
},
function getImage(err, imageOpts) {
assert.ifError(err);
var width = +req.params.width;
var height = +req.params.height;
if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) {
self.previewBackend.getImage(
namedMapProvider, format, width, height, imageOpts.zoom, imageOpts.center, this);
} else {
self.previewBackend.getImage(
namedMapProvider, format, width, height, imageOpts.bounds, this);
}
},
function incrementMapViews(err, image, headers, stats) {
assert.ifError(err);
var next = this;
namedMapProvider.getMapConfig(function(mapConfigErr, mapConfig) {
self.metadataBackend.incMapviewCount(cdbUser, mapConfig.obj().stat_tag, function(sErr) {
if (err) {
global.logger.log("ERROR: failed to increment mapview count for user '%s': %s", cdbUser, sErr);
}
next(err, image, headers, stats);
});
});
},
function handleImage(err, image, headers, stats) {
if (req.profiler) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
}
if (err) {
self.sendError(req, res, err, 'STATIC_VIZ_MAP');
} else {
self.sendResponse(req, res, image, headers, namedMapProvider);
}
}
);
};
var DEFAULT_ZOOM_CENTER = {
zoom: 1,
center: {
lng: 0,
lat: 0
}
};
NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, namedMapProvider, callback) {
var self = this;
step(
function getTemplate() {
namedMapProvider.getTemplate(this);
},
function handleTemplateView(err, template) {
assert.ifError(err);
if (template.view) {
var zoomCenter = templateZoomCenter(template.view);
if (zoomCenter) {
return zoomCenter;
}
var bounds = templateBounds(template.view);
if (bounds) {
return bounds;
}
}
return false;
},
function estimateBoundsIfNoImageOpts(err, imageOpts) {
if (imageOpts) {
return imageOpts;
}
var next = this;
namedMapProvider.getAffectedTablesAndLastUpdatedTime(function(err, affectedTablesAndLastUpdate) {
if (err) {
return next(null);
}
var affectedTables = affectedTablesAndLastUpdate.tables || [];
if (affectedTables.length === 0) {
return next(null);
}
self.tablesExtentApi.getBounds(cdbUser, affectedTables, function(err, result) {
return next(null, result);
});
});
},
function returnCallback(err, imageOpts) {
return callback(err, imageOpts || DEFAULT_ZOOM_CENTER);
}
);
};
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 Number.isFinite(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;
}

View File

@@ -0,0 +1,203 @@
var step = require('step');
var assert = require('assert');
var templateName = require('../backends/template_maps').templateName;
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
/**
* @param {AuthApi} authApi
* @param {PgConnection} pgConnection
* @param {TemplateMaps} templateMaps
* @constructor
*/
function NamedMapsAdminController(authApi, pgConnection, templateMaps) {
BaseController.call(this, authApi, pgConnection);
this.authApi = authApi;
this.templateMaps = templateMaps;
}
util.inherits(NamedMapsAdminController, BaseController);
module.exports = NamedMapsAdminController;
NamedMapsAdminController.prototype.register = function(app) {
app.post(app.base_url_templated, cors(), userMiddleware, this.create.bind(this));
app.put(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.update.bind(this));
app.get(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.retrieve.bind(this));
app.delete(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.destroy.bind(this));
app.get(app.base_url_templated, cors(), userMiddleware, this.list.bind(this));
app.options(app.base_url_templated + '/:template_id', cors('Content-Type'));
};
NamedMapsAdminController.prototype.create = function(req, res) {
var self = this;
var cdbuser = req.context.user;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, 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, req, res, 'POST TEMPLATE')
);
};
NamedMapsAdminController.prototype.update = function(req, res) {
var self = this;
var cdbuser = req.context.user;
var template;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, 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, req, res, 'PUT TEMPLATE')
);
};
NamedMapsAdminController.prototype.retrieve = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.get_template');
}
var cdbuser = req.context.user;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function getTemplate(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) {
assert.ifError(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, req, res, 'GET TEMPLATE')
);
};
NamedMapsAdminController.prototype.destroy = function(req, res) {
var self = this;
if (req.profiler) {
req.profiler.start('windshaft-cartodb.delete_template');
}
var cdbuser = req.context.user;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function deleteTemplate(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*/){
assert.ifError(err);
return '';
},
finishFn(self, req, res, 'DELETE TEMPLATE', 204)
);
};
NamedMapsAdminController.prototype.list = function(req, res) {
var self = this;
if ( req.profiler ) {
req.profiler.start('windshaft-cartodb.get_template_list');
}
var cdbuser = req.context.user;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, 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, req, res, 'GET TEMPLATE LIST')
);
};
function finishFn(controller, req, res, description, status) {
return function finish(err, response){
if (err) {
controller.sendError(req, res, err, description);
} else {
controller.send(req, res, response, status || 200);
}
};
}
function ifUnauthenticated(authenticated, description) {
if (!authenticated) {
var err = new Error(description);
err.http_status = 403;
throw err;
}
}
function ifInvalidContentType(req, description) {
if (!req.is('application/json')) {
throw new Error(description);
}
}

View File

@@ -0,0 +1,56 @@
var windshaft = require('windshaft');
var HealthCheck = require('../monitoring/health_check');
var WELCOME_MSG = "This is the CartoDB Maps API, " +
"see the documentation at http://docs.cartodb.com/cartodb-platform/maps-api.html";
var versions = {
windshaft: windshaft.version,
grainstore: windshaft.grainstore.version(),
node_mapnik: windshaft.mapnik.version,
mapnik: windshaft.mapnik.versions.mapnik,
windshaft_cartodb: require('../../../package.json').version
};
function ServerInfoController() {
this.healthConfig = global.environment.health || {};
this.healthCheck = new HealthCheck(global.environment.disabled_file);
}
module.exports = ServerInfoController;
ServerInfoController.prototype.register = function(app) {
app.get('/health', this.health.bind(this));
app.get('/', this.welcome.bind(this));
app.get('/version', this.version.bind(this));
};
ServerInfoController.prototype.welcome = function(req, res) {
res.status(200).send(WELCOME_MSG);
};
ServerInfoController.prototype.version = function(req, res) {
res.status(200).send(versions);
};
ServerInfoController.prototype.health = function(req, res) {
if (!!this.healthConfig.enabled) {
var startTime = Date.now();
this.healthCheck.check(function(err) {
var ok = !err;
var response = {
enabled: true,
ok: ok,
elapsed: Date.now() - startTime
};
if (err) {
response.err = err.message;
}
res.status(ok ? 200 : 503).send(response);
});
} else {
res.status(200).send({enabled: false, ok: true});
}
};

View File

@@ -1,478 +0,0 @@
var Step = require('step');
var _ = require('underscore');
function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache,
NamedMapsCacheEntry) {
this.app = app;
this.serverOptions = serverOptions;
this.templateMaps = templateMaps;
this.metadataBackend = metadataBackend;
this.templateBaseUrl = templateBaseUrl;
this.surrogateKeysCache = surrogateKeysCache;
this.NamedMapsCacheEntry = NamedMapsCacheEntry;
}
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.serverOptions.setDBAuth(cdbuser, params, this);
},
function setConn(err) {
if ( err ) throw err;
self.serverOptions.setDBConn(cdbuser, params, this);
},
function finish(err) {
callback(err);
}
);
};

View File

@@ -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;

View File

@@ -0,0 +1,11 @@
module.exports = function cors(extraHeaders) {
return function(req, res, next) {
var baseHeaders = "X-Requested-With, X-Prototype-Version, X-CSRF-Token";
if(extraHeaders) {
baseHeaders += ", " + extraHeaders;
}
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Headers", baseHeaders);
next();
};
};

View File

@@ -0,0 +1,7 @@
var CdbRequest = require('../models/cdb_request');
var cdbRequest = new CdbRequest();
module.exports = function userMiddleware(req, res, next) {
req.context.user = cdbRequest.userByReq(req);
next();
};

View File

@@ -0,0 +1,220 @@
var queue = require('queue-async');
var debug = require('debug')('windshaft:analysis');
var camshaft = require('camshaft');
var dot = require('dot');
dot.templateSettings.strip = false;
function AnalysisMapConfigAdapter(analysisBackend) {
this.analysisBackend = analysisBackend;
}
module.exports = AnalysisMapConfigAdapter;
var SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function skipColumns(columnNames) {
return columnNames
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}
var layerQueryTemplate = dot.template([
'SELECT {{=it._columns}}',
'FROM ({{=it._query}}) _cdb_analysis_query'
].join('\n'));
function layerQuery(query, columnNames) {
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(columnNames));
return layerQueryTemplate({ _query: query, _columns: _columns.join(', ') });
}
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
var analyses = requestMapConfig.analyses || [];
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
var definition = analysisDefinition;
Object.keys(dataviewsFiltersBySourceId).forEach(function(sourceId) {
definition = analysisGraph.getDefinitionWith(sourceId, {filters: dataviewsFiltersBySourceId[sourceId] });
});
return definition;
});
return requestMapConfig;
}
function shouldAdaptLayers(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) &&
Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0;
}
var DATAVIEW_TYPE_2_FILTER_TYPE = {
aggregation: 'category',
histogram: 'range'
};
function getFilter(dataview, params) {
var type = dataview.type;
return {
type: DATAVIEW_TYPE_2_FILTER_TYPE[type],
column: dataview.options.column,
params: params
};
}
AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration, requestMapConfig, filters, callback) {
// jshint maxcomplexity:7
var self = this;
filters = filters || {};
if (!shouldAdaptLayers(requestMapConfig)) {
return callback(null, requestMapConfig);
}
var dataviewsFilters = filters.dataviews || {};
debug(dataviewsFilters);
var dataviews = requestMapConfig.dataviews || {};
var errors = getDataviewsErrors(dataviews);
if (errors.length > 0) {
return callback(errors);
}
var dataviewsFiltersBySourceId = Object.keys(dataviewsFilters).reduce(function(bySourceId, dataviewName) {
var dataview = dataviews[dataviewName];
if (dataview) {
var sourceId = dataview.source.id;
if (!bySourceId.hasOwnProperty(sourceId)) {
bySourceId[sourceId] = {};
}
bySourceId[sourceId][dataviewName] = getFilter(dataview, dataviewsFilters[dataviewName]);
}
return bySourceId;
}, {});
debug(dataviewsFiltersBySourceId);
debug('mapconfig input', JSON.stringify(requestMapConfig, null, 4));
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
function createAnalysis(analysisDefinition, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, done);
}
var analysesQueue = queue(requestMapConfig.analyses.length);
requestMapConfig.analyses.forEach(function(analysis) {
analysesQueue.defer(createAnalysis, analysis);
});
analysesQueue.awaitAll(function(err, analysesResults) {
if (err) {
return callback(err);
}
var sourceId2Node = analysesResults.reduce(function(sourceId2Query, analysis) {
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Query[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Query[node.params.id] = node;
}
});
return sourceId2Query;
}, {});
var missingNodesErrors = [];
requestMapConfig.layers = requestMapConfig.layers.map(function(layer, layerIndex) {
if (getLayerSourceId(layer)) {
var layerSourceId = getLayerSourceId(layer);
var layerNode = sourceId2Node[layerSourceId];
if (layerNode) {
layer.options.sql = layerQuery(layerNode.getQuery(), layerNode.getColumns());
var layerDataviews = getLayerDataviews(layer, dataviews);
layer.options.columns = layerDataviews.reduce(function(columns, dataview) {
return columns.concat(getDataviewColumns(dataview));
}, []);
} else {
missingNodesErrors.push(
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
);
}
}
return layer;
});
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
if (missingNodesErrors.length > 0) {
return callback(missingNodesErrors);
}
return callback(null, requestMapConfig, analysesResults);
});
};
function getLayerSourceId(layer) {
return layer.options.source && layer.options.source.id;
}
function getDataviewSourceId(dataview) {
return dataview.source && dataview.source.id;
}
function getLayerDataviews(layer, dataviews) {
var layerDataviews = [];
var layerSourceId = getLayerSourceId(layer);
if (layerSourceId) {
var dataviewsList = getDataviewsList(dataviews);
dataviewsList.forEach(function(dataview) {
if (getDataviewSourceId(dataview) === layerSourceId) {
layerDataviews.push(dataview);
}
});
}
return layerDataviews;
}
function getDataviewColumns(dataview) {
var columns = [];
var options = dataview.options;
['column', 'aggregationColumn'].forEach(function(opt) {
if (options.hasOwnProperty(opt)) {
columns.push(options[opt]);
}
});
return columns;
}
function getDataviewsList(dataviews) {
return Object.keys(dataviews).map(function(dataviewKey) { return dataviews[dataviewKey]; });
}
function getDataviewsErrors(dataviews) {
var errors = [];
Object.keys(dataviews).forEach(function(dataviewName) {
var dataview = dataviews[dataviewName];
if (!dataview.hasOwnProperty('source') || !dataview.source.id) {
errors.push(new Error('Dataview "' + dataviewName + '" is missing `source.id` attribute'));
}
if (!dataview.type) {
errors.push(new Error('Dataview "' + dataviewName + '" is missing `type` attribute'));
}
});
return errors;
}

View File

@@ -0,0 +1,25 @@
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 ) {
global.logger.error("Pattern '%s' does not match hostname '%s'", this.RE_USER_FROM_HOST, host);
return;
}
if ( mat.length !== 2 ) {
global.logger.error("Pattern '%s' gave unexpected matches against '%s': %s", this.RE_USER_FROM_HOST, host, mat);
return;
}
return mat[1];
};

View File

@@ -0,0 +1,278 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:aggregation');
var dot = require('dot');
dot.templateSettings.strip = false;
var summaryQueryTpl = dot.template([
'summary AS (',
' SELECT',
' count(1) AS count,',
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
')'
].join('\n'));
var rankedCategoriesQueryTpl = dot.template([
'categories AS(',
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
' FROM ({{=it._query}}) _cdb_aggregation_all',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
var categoriesSummaryQueryTpl = dot.template([
'categories_summary AS(',
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
var rankedAggregationQueryTpl = dot.template([
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank < {{=it._limit}}',
'UNION ALL',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
var aggregationQueryTpl = dot.template([
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
' nulls_count, min_val, max_val, count, categories_count',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
var CATEGORIES_LIMIT = 6;
var VALID_OPERATIONS = {
count: [],
sum: ['aggregationColumn']
};
var TYPE = 'aggregation';
/**
{
type: 'aggregation',
options: {
column: 'name',
aggregation: 'count' // it could be, e.g., sum if column is numeric
}
}
*/
function Aggregation(query, options) {
if (!_.isString(options.column)) {
throw new Error('Aggregation expects `column` in widget options');
}
if (!_.isString(options.aggregation)) {
throw new Error('Aggregation expects `aggregation` operation in widget options');
}
if (!VALID_OPERATIONS[options.aggregation]) {
throw new Error("Aggregation does not support '" + options.aggregation + "' operation");
}
var requiredOptions = VALID_OPERATIONS[options.aggregation];
var missingOptions = _.difference(requiredOptions, Object.keys(options));
if (missingOptions.length > 0) {
throw new Error(
"Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',')
);
}
BaseWidget.apply(this);
this.query = query;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
}
Aggregation.prototype = new BaseWidget();
Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.query;
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
aggregationQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_limit: CATEGORIES_LIMIT
})
].join('\n');
} else {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
})
].join('\n');
}
debug(aggregationSql);
return callback(null, aggregationSql);
};
var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})');
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl({
_aggregationFn: this.aggregation,
_aggregationColumn: this.aggregationColumn || 1
});
};
Aggregation.prototype.format = function(result) {
var categories = [];
var count = 0;
var nulls = 0;
var minValue = 0;
var maxValue = 0;
var categoriesCount = 0;
if (result.rows.length) {
var firstRow = result.rows[0];
count = firstRow.count;
nulls = firstRow.nulls_count;
minValue = firstRow.min_val;
maxValue = firstRow.max_val;
categoriesCount = firstRow.categories_count;
result.rows.forEach(function(row) {
categories.push(_.omit(row, 'count', 'nulls_count', 'min_val', 'max_val', 'categories_count'));
});
}
return {
count: count,
nulls: nulls,
min: minValue,
max: maxValue,
categoriesCount: categoriesCount,
categories: categories
};
};
var filterCategoriesQueryTpl = dot.template([
'SELECT {{=it._column}} AS category, {{=it._value}} AS value',
'FROM ({{=it._query}}) _cdb_aggregation_search',
'WHERE CAST({{=it._column}} as text) ILIKE {{=it._userQuery}}',
'GROUP BY {{=it._column}}'
].join('\n'));
var searchQueryTpl = dot.template([
'WITH',
'search_unfiltered AS (',
' {{=it._searchUnfiltered}}',
'),',
'search_filtered AS (',
' {{=it._searchFiltered}}',
'),',
'search_union AS (',
' SELECT * FROM search_unfiltered',
' UNION ALL',
' SELECT * FROM search_filtered',
')',
'SELECT category, sum(value) AS value',
'FROM search_union',
'GROUP BY category',
'ORDER BY value desc'
].join('\n'));
Aggregation.prototype.search = function(psql, userQuery, callback) {
var self = this;
var _userQuery = psql.escapeLiteral('%' + userQuery + '%');
// TODO unfiltered will be wrong as filters are already applied at this point
var query = searchQueryTpl({
_searchUnfiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: '0',
_userQuery: _userQuery
}),
_searchFiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: 'count(1)',
_userQuery: _userQuery
})
});
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
}
return callback(null, {type: self.getType(), categories: result.rows });
}, true); // use read-only transaction
};
Aggregation.prototype.getType = function() {
return TYPE;
};
Aggregation.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_aggregation: this.aggregation
});
};

View File

@@ -0,0 +1,26 @@
function BaseDataview() {}
module.exports = BaseDataview;
BaseDataview.prototype.getResult = function(psql, override, callback) {
var self = this;
this.sql(psql, override, function(err, query) {
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
}
result = self.format(result, override);
result.type = self.getType();
return callback(null, result);
}, true); // use read-only transaction
});
};
BaseDataview.prototype.search = function(psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
};

View File

@@ -0,0 +1,18 @@
var dataviews = require('./');
var DataviewFactory = {
dataviews: Object.keys(dataviews).reduce(function(allDataviews, dataviewClassName) {
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
return allDataviews;
}, {}),
getDataview: function(query, dataviewDefinition) {
var type = dataviewDefinition.type;
if (!this.dataviews[type]) {
throw new Error('Invalid dataview type: "' + type + '"');
}
return new this.dataviews[type](query, dataviewDefinition.options);
}
};
module.exports = DataviewFactory;

View File

@@ -0,0 +1,104 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:formula');
var dot = require('dot');
dot.templateSettings.strip = false;
var formulaQueryTpl = dot.template([
'SELECT',
'{{=it._operation}}({{=it._column}}) AS result,',
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
'FROM ({{=it._query}}) _cdb_formula'
].join('\n'));
var VALID_OPERATIONS = {
count: true,
avg: true,
sum: true,
min: true,
max: true
};
var TYPE = 'formula';
/**
{
type: 'formula',
options: {
column: 'name',
operation: 'count' // count, sum, avg
}
}
*/
function Formula(query, options) {
if (!_.isString(options.operation)) {
throw new Error('Formula expects `operation` in widget options');
}
if (!VALID_OPERATIONS[options.operation]) {
throw new Error("Formula does not support '" + options.operation + "' operation");
}
if (options.operation !== 'count' && !_.isString(options.column)) {
throw new Error('Formula expects `column` in widget options');
}
BaseWidget.apply(this);
this.query = query;
this.column = options.column || '1';
this.operation = options.operation;
}
Formula.prototype = new BaseWidget();
Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.query;
var formulaSql = formulaQueryTpl({
_query: _query,
_operation: this.operation,
_column: this.column
});
debug(formulaSql);
return callback(null, formulaSql);
};
Formula.prototype.format = function(result) {
var formattedResult = {
operation: this.operation,
result: 0,
nulls: 0
};
if (result.rows.length) {
formattedResult.operation = this.operation;
formattedResult.result = result.rows[0].result;
formattedResult.nulls = result.rows[0].nulls_count;
}
return formattedResult;
};
Formula.prototype.getType = function() {
return TYPE;
};
Formula.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_operation: this.operation
});
};

View File

@@ -0,0 +1,297 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:dataview:histogram');
var dot = require('dot');
dot.templateSettings.strip = false;
var columnTypeQueryTpl = dot.template(
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;
var basicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join(' \n'));
var overrideBasicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join('\n'));
var iqrQueryTpl = dot.template([
'iqrange AS (',
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
' FROM (',
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
' ) AS quartile',
' FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
' WHERE quartile = 1 or quartile = 3',
' GROUP BY quartile',
' ) _cdb_iqr',
')'
].join('\n'));
var binsQueryTpl = dot.template([
'bins AS (',
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
' THEN 1',
' ELSE GREATEST(',
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
' LEAST(',
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
' {{=it._maxBins}}',
' )',
' )',
' END AS bins_number',
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
' LIMIT 1',
')'
].join('\n'));
var overrideBinsQueryTpl = dot.template([
'bins AS (',
' SELECT {{=it._bins}} AS bins_number',
')'
].join('\n'));
var nullsQueryTpl = dot.template([
'nulls AS (',
' SELECT',
' count(*) AS nulls_count',
' FROM ({{=it._query}}) _cdb_histogram_nulls',
' WHERE {{=it._column}} IS NULL',
')'
].join('\n'));
var histogramQueryTpl = dot.template([
'SELECT',
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
' bins_number,',
' nulls_count,',
' avg_val,',
' CASE WHEN min_val = max_val',
' THEN 0',
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
' END AS bin,',
' min({{=it._column}})::numeric AS min,',
' max({{=it._column}})::numeric AS max,',
' avg({{=it._column}})::numeric AS avg,',
' count(*) AS freq',
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
'WHERE {{=it._column}} IS NOT NULL',
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
'ORDER BY bin'
].join('\n'));
var TYPE = 'histogram';
/**
{
type: 'histogram',
options: {
column: 'name',
bins: 10 // OPTIONAL
}
}
*/
function Histogram(query, options) {
if (!_.isString(options.column)) {
throw new Error('Histogram expects `column` in widget options');
}
this.query = query;
this.column = options.column;
this.bins = options.bins;
this._columnType = null;
}
Histogram.prototype = new BaseWidget();
Histogram.prototype.constructor = Histogram;
module.exports = Histogram;
var DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
Histogram.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var self = this;
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.query
});
if (this._columnType === null) {
psql.query(columnTypeQuery, function(err, result) {
// assume numeric, will fail later
self._columnType = 'numeric';
if (!err && !!result.rows[0]) {
var pgType = result.rows[0].pg_typeof;
if (DATE_OIDS.hasOwnProperty(pgType)) {
self._columnType = 'date';
}
}
self.sql(psql, override, callback);
}, true); // use read-only transaction
return null;
}
if (this._columnType === 'date') {
_column = columnCastTpl({column: _column});
}
var _query = this.query;
var basicsQuery, binsQuery;
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
debug('overriding with %j', override);
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
});
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
basicsQuery = basicsQueryTpl({
_query: _query,
_column: _column
});
if (override && _.has(override, 'bins')) {
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
binsQuery = [
iqrQueryTpl({
_query: _query,
_column: _column
}),
binsQueryTpl({
_query: _query,
_minBins: BIN_MIN_NUMBER,
_maxBins: BIN_MAX_NUMBER
})
].join(',\n');
}
}
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
histogramQueryTpl({
_query: _query,
_column: _column
})
].join('\n');
debug(histogramSql);
return callback(null, histogramSql);
};
Histogram.prototype.format = function(result, override) {
override = override || {};
var buckets = [];
var binsCount = getBinsCount(override);
var width = getWidth(override);
var binsStart = getBinStart(override);
var nulls = 0;
var avg;
if (result.rows.length) {
var firstRow = result.rows[0];
binsCount = firstRow.bins_number;
width = firstRow.bin_width || width;
avg = firstRow.avg_val;
nulls = firstRow.nulls_count;
binsStart = override.hasOwnProperty('start') ? override.start : firstRow.min;
buckets = result.rows.map(function(row) {
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
});
}
return {
bin_width: width,
bins_count: binsCount,
bins_start: binsStart,
nulls: nulls,
avg: avg,
bins: buckets
};
};
function getBinStart(override) {
return override.start || 0;
}
function getBinsCount(override) {
return override.bins || 0;
}
function getWidth(override) {
var width = 0;
var binsCount = override.bins;
if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
width = (override.end - override.start) / binsCount;
}
return width;
}
Histogram.prototype.getType = function() {
return TYPE;
};
Histogram.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_column: this.column,
_query: this.query
});
};

View File

@@ -0,0 +1,6 @@
module.exports = {
Aggregation: require('./aggregation'),
Formula: require('./formula'),
Histogram: require('./histogram'),
List: require('./list')
};

View File

@@ -0,0 +1,66 @@
var dot = require('dot');
dot.templateSettings.strip = false;
var BaseWidget = require('./base');
var TYPE = 'list';
var listSqlTpl = dot.template('select {{=it._columns}} from ({{=it._query}}) as _cdb_list');
/**
{
type: 'list',
options: {
columns: ['name', 'description']
}
}
*/
function List(query, options) {
options = options || {};
if (!Array.isArray(options.columns)) {
throw new Error('List expects `columns` array in widget options');
}
BaseWidget.apply(this);
this.query = query;
this.columns = options.columns;
}
List.prototype = new BaseWidget();
List.prototype.constructor = List;
module.exports = List;
List.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
}
var listSql = listSqlTpl({
_query: this.query,
_columns: this.columns.join(', ')
});
return callback(null, listSql);
};
List.prototype.format = function(result) {
return {
rows: result.rows
};
};
List.prototype.getType = function() {
return TYPE;
};
List.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_columns: this.columns.join(', ')
});
};

View File

@@ -0,0 +1,122 @@
var debug = require('debug')('windshaft:filter:bbox');
var dot = require('dot');
dot.templateSettings.strip = false;
var filterQueryTpl = dot.template([
'SELECT * FROM ({{=it._sql}}) _cdb_bbox_filter',
'WHERE {{=it._filters}}'
].join('\n'));
var bboxFilterTpl = dot.template(
'{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})'
);
var LATITUDE_MAX_VALUE = 85.0511287798066;
var LONGITUDE_LOWER_BOUND = -180;
var LONGITUDE_UPPER_BOUND = 180;
var LONGITUDE_RANGE = LONGITUDE_UPPER_BOUND - LONGITUDE_LOWER_BOUND;
/**
Definition
{
"type”: "bbox",
"options": {
"column": "the_geom_webmercator",
"srid": 3857
}
}
Params
{
“bbox”: "west,south,east,north"
}
*/
function BBox(filterDefinition, filterParams) {
var bbox = filterParams.bbox;
if (!bbox) {
throw new Error('BBox filter expects to have a bbox param');
}
var bboxElements = bbox.split(',').map(function(e) { return +e; });
validateBboxElements(bboxElements);
this.column = filterDefinition.column || 'the_geom_webmercator';
this.srid = filterDefinition.srid || 3857;
// Latitudes must be within max extent
var south = Math.max(bboxElements[1], -LATITUDE_MAX_VALUE);
var north = Math.min(bboxElements[3], LATITUDE_MAX_VALUE);
// Longitudes crossing 180º need another approach
var adjustedLongitudeRange = adjustLongitudeRange([bboxElements[0], bboxElements[2]]);
var west = adjustedLongitudeRange[0];
var east = adjustedLongitudeRange[1];
this.bboxes = getBoundingBoxes(west, south, east, north);
}
function getBoundingBoxes(west, south, east, north) {
var bboxes = [];
if (east - west >= 360) {
bboxes.push([-180, south, 180, north]);
} else if (west >= -180 && east <= 180) {
bboxes.push([west, south, east, north]);
} else {
bboxes.push([west, south, 180, north]);
bboxes.push([-180, south, east % 180, north]);
}
return bboxes;
}
function validateBboxElements(bboxElements) {
var isNumericBbox = bboxElements
.map(function(n) { return Number.isFinite(n); })
.reduce(function(allFinite, isFinite) {
if (!allFinite) {
return false;
}
return isFinite;
}, true);
if (bboxElements.length !== 4 || !isNumericBbox) {
throw new Error('Invalid bbox filter, expected format="west,south,east,north"');
}
}
function adjustLongitudeRange(we) {
var west = we[0];
west -= LONGITUDE_LOWER_BOUND;
west = west - (LONGITUDE_RANGE * Math.floor(west / LONGITUDE_RANGE)) + LONGITUDE_LOWER_BOUND;
var longitudeRange = Math.min(we[1] - we[0], 360);
return [west, west + longitudeRange];
}
module.exports = BBox;
module.exports.adjustLongitudeRange = adjustLongitudeRange;
module.exports.LATITUDE_MAX_VALUE = LATITUDE_MAX_VALUE;
module.exports.LONGITUDE_MAX_VALUE = LONGITUDE_UPPER_BOUND;
BBox.prototype.sql = function(rawSql) {
var bboxSql = filterQueryTpl({
_sql: rawSql,
_filters: this.bboxes.map(function(bbox) {
return bboxFilterTpl({
_column: this.column,
_bbox: bbox.join(','),
_srid: this.srid
});
}.bind(this)).join(' OR ')
});
debug(bboxSql);
return bboxSql;
};

View File

@@ -0,0 +1,29 @@
/**
* @param {String} token might match the following pattern: {user}@{tpl_id}@{token}:{cache_buster}
*/
function parse(token) {
var signer, cacheBuster;
var tokenSplit = token.split(':');
token = tokenSplit[0];
if (tokenSplit.length > 1) {
cacheBuster = tokenSplit[1];
}
tokenSplit = token.split('@');
if ( tokenSplit.length > 1 ) {
signer = tokenSplit.shift();
if ( tokenSplit.length > 1 ) {
/*var template_hash = */tokenSplit.shift(); // unused
}
token = tokenSplit.shift();
}
return {
token: token,
signer: signer,
cacheBuster: cacheBuster
};
}
module.exports.parse = parse;

View File

@@ -0,0 +1,48 @@
var assert = require('assert');
var step = require('step');
var MapStoreMapConfigProvider = require('./map_store_provider');
/**
* @param {MapConfig} mapConfig
* @param {String} user
* @param {UserLimitsApi} userLimitsApi
* @param {Object} params
* @constructor
* @type {CreateLayergroupMapConfigProvider}
*/
function CreateLayergroupMapConfigProvider(mapConfig, user, userLimitsApi, params) {
this.mapConfig = mapConfig;
this.user = user;
this.userLimitsApi = userLimitsApi;
this.params = params;
this.cacheBuster = params.cache_buster || 0;
}
module.exports = CreateLayergroupMapConfigProvider;
CreateLayergroupMapConfigProvider.prototype.getMapConfig = function(callback) {
var self = this;
var context = {};
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);
context.limits = renderLimits;
return null;
},
function finish(err) {
return callback(err, self.mapConfig, self.params, context);
}
);
};
CreateLayergroupMapConfigProvider.prototype.getKey = MapStoreMapConfigProvider.prototype.getKey;
CreateLayergroupMapConfigProvider.prototype.getCacheBuster = MapStoreMapConfigProvider.prototype.getCacheBuster;
CreateLayergroupMapConfigProvider.prototype.filter = MapStoreMapConfigProvider.prototype.filter;
CreateLayergroupMapConfigProvider.prototype.createKey = MapStoreMapConfigProvider.prototype.createKey;

View File

@@ -0,0 +1,77 @@
var _ = require('underscore');
var assert = require('assert');
var dot = require('dot');
var step = require('step');
/**
* @param {MapStore} mapStore
* @param {String} user
* @param {UserLimitsApi} userLimitsApi
* @param {Object} params
* @constructor
* @type {MapStoreMapConfigProvider}
*/
function MapStoreMapConfigProvider(mapStore, user, userLimitsApi, params) {
this.mapStore = mapStore;
this.user = user;
this.userLimitsApi = userLimitsApi;
this.params = params;
this.token = params.token;
this.cacheBuster = params.cache_buster || 0;
}
module.exports = MapStoreMapConfigProvider;
MapStoreMapConfigProvider.prototype.getMapConfig = function(callback) {
var self = this;
var context = {};
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);
context.limits = renderLimits;
return null;
},
function loadMapConfig(err) {
assert.ifError(err);
self.mapStore.load(self.token, this);
},
function finish(err, mapConfig) {
return callback(err, mapConfig, self.params, context);
}
);
};
MapStoreMapConfigProvider.prototype.getKey = function() {
return this.createKey(false);
};
MapStoreMapConfigProvider.prototype.getCacheBuster = function() {
return this.cacheBuster;
};
MapStoreMapConfigProvider.prototype.filter = function(key) {
var regex = new RegExp('^' + this.createKey(true) + '.*');
return key && key.match(regex);
};
// Configure bases for cache keys suitable for string interpolation
var baseKey = '{{=it.dbname}}:{{=it.token}}';
var rendererKey = baseKey + ':{{=it.dbuser}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}';
var baseKeyTpl = dot.template(baseKey);
var rendererKeyTpl = dot.template(rendererKey);
MapStoreMapConfigProvider.prototype.createKey = function(base) {
var tplValues = _.defaults({}, this.params, {
dbname: '',
token: '',
dbuser: '',
format: '',
layer: '',
scale_factor: 1
});
return (base) ? baseKeyTpl(tplValues) : rendererKeyTpl(tplValues);
};

View File

@@ -0,0 +1,347 @@
var _ = require('underscore');
var assert = require('assert');
var crypto = require('crypto');
var dot = require('dot');
var step = require('step');
var MapConfig = require('windshaft').model.MapConfig;
var templateName = require('../../backends/template_maps').templateName;
var QueryTables = require('cartodb-query-tables');
/**
* @constructor
* @type {NamedMapMapConfigProvider}
*/
function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi,
namedLayersAdapter, overviewsAdapter, turboCartoAdapter, analysisMapConfigAdapter,
owner, templateId, config, authToken, params) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.namedLayersAdapter = namedLayersAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = analysisMapConfigAdapter;
this.overviewsAdapter = overviewsAdapter;
this.owner = owner;
this.templateName = templateName(templateId);
this.config = config;
this.authToken = authToken;
this.params = params;
this.cacheBuster = Date.now();
// use template after call to mapConfig
this.template = null;
this.affectedTablesAndLastUpdate = null;
// providing
this.err = null;
this.mapConfig = null;
this.rendererParams = null;
this.context = {};
this.analysesResults = [];
}
module.exports = NamedMapMapConfigProvider;
NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
if (!!this.err || this.mapConfig !== null) {
return callback(this.err, this.mapConfig, this.rendererParams, this.context);
}
var self = this;
var mapConfig = null;
var datasource = null;
var rendererParams;
var apiKey;
step(
function getTemplate() {
self.getTemplate(this);
},
function prepareDbParams(err, tpl) {
assert.ifError(err);
self.template = tpl;
rendererParams = _.extend({}, self.params, {
user: self.owner
});
self.setDBParams(self.owner, rendererParams, this);
},
function getUserApiKey(err) {
assert.ifError(err);
self.metadataBackend.getUserMapKey(self.owner, this);
},
function prepareParams(err, _apiKey) {
assert.ifError(err);
apiKey = _apiKey;
var templateParams = {};
if (self.config) {
try {
templateParams = _.isString(self.config) ? JSON.parse(self.config) : self.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(self.template, templateParams);
},
function prepareAnalysisLayers(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: rendererParams.dbhost,
port: rendererParams.dbport,
dbname: rendererParams.dbname,
user: rendererParams.dbuser,
pass: rendererParams.dbpassword
},
batch: {
username: self.owner,
apiKey: apiKey
}
};
var filters = {};
if (self.params.filters) {
try {
filters = JSON.parse(self.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
},
function prepareLayergroup(err, _mapConfig, analysesResults) {
assert.ifError(err);
var next = this;
self.analysesResults = analysesResults || [];
self.namedLayersAdapter.getLayers(self.owner, _mapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
}
);
},
function addOverviewsInformation(err, _mapConfig, datasource) {
assert.ifError(err);
var next = this;
self.overviewsAdapter.getLayers(self.owner, _mapConfig.layers, function(err, layers) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
});
},
function parseTurboCarto(err, _mapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
});
},
function prepareContextLimits(err, _mapConfig, _datasource) {
assert.ifError(err);
mapConfig = _mapConfig;
datasource = _datasource;
self.userLimitsApi.getRenderLimits(self.owner, this);
},
function cacheAndReturnMapConfig(err, renderLimits) {
self.err = err;
self.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, datasource);
self.rendererParams = rendererParams;
self.context.limits = renderLimits || {};
return callback(self.err, self.mapConfig, self.rendererParams, self.context);
}
);
};
NamedMapMapConfigProvider.prototype.getTemplate = function(callback) {
var self = this;
if (!!this.err || this.template !== null) {
return callback(this.err, this.template);
}
step(
function getTemplate() {
self.templateMaps.getTemplate(self.owner, self.templateName, this);
},
function checkExists(err, tpl) {
assert.ifError(err);
if (!tpl) {
var notFoundErr = new Error(
"Template '" + self.templateName + "' of user '" + self.owner + "' 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, self.authToken);
} 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 cacheAndReturnTemplate(err, template) {
self.err = err;
self.template = template;
return callback(self.err, self.template);
}
);
};
NamedMapMapConfigProvider.prototype.getKey = function() {
return this.createKey(false);
};
NamedMapMapConfigProvider.prototype.getCacheBuster = function() {
return this.cacheBuster;
};
NamedMapMapConfigProvider.prototype.reset = function() {
this.template = null;
this.affectedTablesAndLastUpdate = null;
this.err = null;
this.mapConfig = null;
this.cacheBuster = Date.now();
};
NamedMapMapConfigProvider.prototype.filter = function(key) {
var regex = new RegExp('^' + this.createKey(true) + '.*');
return key && key.match(regex);
};
// Configure bases for cache keys suitable for string interpolation
var baseKey = '{{=it.dbname}}:{{=it.owner}}:{{=it.templateName}}';
var rendererKey = baseKey + ':{{=it.authToken}}:{{=it.configHash}}:{{=it.format}}:{{=it.layer}}:{{=it.scale_factor}}';
var baseKeyTpl = dot.template(baseKey);
var rendererKeyTpl = dot.template(rendererKey);
NamedMapMapConfigProvider.prototype.createKey = function(base) {
var tplValues = _.defaults({}, this.params, {
dbname: '',
owner: this.owner,
templateName: this.templateName,
authToken: this.authToken || '',
configHash: configHash(this.config),
layer: '',
scale_factor: 1
});
return (base) ? baseKeyTpl(tplValues) : rendererKeyTpl(tplValues);
};
function configHash(config) {
if (!config) {
return '';
}
return crypto.createHash('md5').update(JSON.stringify(config)).digest('hex').substring(0,8);
}
module.exports.configHash = configHash;
NamedMapMapConfigProvider.prototype.setDBParams = function(cdbuser, params, callback) {
var self = this;
step(
function setAuth() {
self.pgConnection.setDBAuth(cdbuser, params, this);
},
function setConn(err) {
assert.ifError(err);
self.pgConnection.setDBConn(cdbuser, params, this);
},
function finish(err) {
callback(err);
}
);
};
NamedMapMapConfigProvider.prototype.getTemplateName = function() {
return this.templateName;
};
NamedMapMapConfigProvider.prototype.getAffectedTablesAndLastUpdatedTime = function(callback) {
var self = this;
if (this.affectedTablesAndLastUpdate !== null) {
return callback(null, this.affectedTablesAndLastUpdate);
}
step(
function getMapConfig() {
self.getMapConfig(this);
},
function getSql(err, mapConfig) {
assert.ifError(err);
return mapConfig.getLayers().map(function(layer) {
return layer.options.sql;
}).join(';');
},
function getAffectedTables(err, sql) {
assert.ifError(err);
step(
function getConnection() {
self.pgConnection.getConnection(self.owner, this);
},
function getAffectedTables(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
this
);
},
function finish(err, result) {
self.affectedTablesAndLastUpdate = result;
return callback(err, result);
}
);
};

View File

@@ -0,0 +1,124 @@
var queue = require('queue-async');
var _ = require('underscore');
var Datasource = require('windshaft').model.Datasource;
function MapConfigNamedLayersAdapter(templateMaps) {
this.templateMaps = templateMaps;
}
module.exports = MapConfigNamedLayersAdapter;
MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbMetadata, callback) {
var self = this;
if (!layers) {
return callback(null);
}
var adaptLayersQueue = queue(layers.length);
function adaptLayer(layer, done) {
if (isNamedTypeLayer(layer)) {
if (!layer.options.name) {
return done(new Error('Missing Named Map `name` in layer options'));
}
var templateName = layer.options.name;
var templateConfigParams = layer.options.config || {};
var templateAuthTokens = layer.options.auth_tokens;
self.templateMaps.getTemplate(username, templateName, function(err, template) {
if (err || !template) {
return done(new Error("Template '" + templateName + "' of user '" + username + "' not found"));
}
if (self.templateMaps.isAuthorized(template, templateAuthTokens)) {
var nestedNamedLayers = template.layergroup.layers.filter(function(layer) {
return layer.type === 'named';
});
if (nestedNamedLayers.length > 0) {
var nestedNamedMapsError = new Error('Nested named layers are not allowed');
// nestedNamedMapsError.http_status = 400;
return done(nestedNamedMapsError);
}
try {
var templateLayergroupConfig = self.templateMaps.instance(template, templateConfigParams);
return done(null, {
datasource: true,
layers: templateLayergroupConfig.layers
});
} catch (err) {
return done(err);
}
} else {
var unauthorizedError = new Error("Unauthorized '" + templateName + "' template instantiation");
unauthorizedError.http_status = 403;
return done(unauthorizedError);
}
});
} else {
return done(null, {
datasource: false,
layers: [layer]
});
}
}
var datasourceBuilder = new Datasource.Builder();
function layersAdaptQueueFinish(err, layersResults) {
if (err) {
return callback(err);
}
if (!layersResults || layersResults.length === 0) {
return callback(new Error('Missing layers array from layergroup config'));
}
var layers = [],
currentLayerIndex = 0;
layersResults.forEach(function(layersResult) {
layersResult.layers.forEach(function(layer) {
layers.push(layer);
if (layersResult.datasource) {
datasourceBuilder.withLayerDatasource(currentLayerIndex, {
user: dbAuth.dbuser
});
}
currentLayerIndex++;
});
});
return callback(null, layers, datasourceBuilder.build());
}
var dbAuth = {};
if (_.some(layers, isNamedTypeLayer)) {
// Lazy load dbAuth
dbMetadata.setDBAuth(username, dbAuth, function(err) {
if (err) {
return callback(err);
}
layers.forEach(function(layer) {
adaptLayersQueue.defer(adaptLayer, layer);
});
adaptLayersQueue.awaitAll(layersAdaptQueueFinish);
});
} else {
return callback(null, layers, datasourceBuilder.build());
}
};
function isNamedTypeLayer(layer) {
return layer.type === 'named';
}

View File

@@ -0,0 +1,53 @@
var queue = require('queue-async');
var _ = require('underscore');
function MapConfigOverviewsAdapter(overviewsMetadataApi) {
this.overviewsMetadataApi = overviewsMetadataApi;
}
module.exports = MapConfigOverviewsAdapter;
MapConfigOverviewsAdapter.prototype.getLayers = function(username, layers, callback) {
var self = this;
if (!layers || layers.length === 0) {
return callback(null, layers);
}
var augmentLayersQueue = queue(layers.length);
function augmentLayer(layer, done) {
if ( layer.type !== 'mapnik' && layer.type !== 'cartodb' ) {
return done(null, layer);
}
self.overviewsMetadataApi.getOverviewsMetadata(username, layer.options.sql, function(err, metadata){
if (err) {
done(err, layer);
} else {
if ( !_.isEmpty(metadata) ) {
layer = _.extend({}, layer);
layer.options = _.extend({}, layer.options, { query_rewrite_data: { overviews: metadata } });
}
done(null, layer);
}
});
}
function layersAugmentQueueFinish(err, layers) {
if (err) {
return callback(err);
}
if (!layers || layers.length === 0) {
return callback(new Error('Missing layers array from layergroup config'));
}
return callback(null, layers);
}
layers.forEach(function(layer) {
augmentLayersQueue.defer(augmentLayer, layer);
});
augmentLayersQueue.awaitAll(layersAugmentQueueFinish);
};

View File

@@ -1,90 +1,19 @@
var _ = require('underscore'),
dot = require('dot'),
fs = require('fs'),
path = require('path'),
Step = require('step');
var fs = require('fs');
function HealthCheck(metadataBackend, tilelive) {
this.metadataBackend = metadataBackend;
this.tilelive = tilelive;
function HealthCheck(disableFile) {
this.disableFile = disableFile;
}
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 = {
redis: {
ok: false
},
mapnik: {
ok: false
},
tile: {
ok: false
}
};
mapnikXmlParams = config;
Step(
function getDBParams() {
startTime = Date.now();
self.metadataBackend.getAllUserDBParams(config.username, 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 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;
}
callback(err, result);
HealthCheck.prototype.check = function(callback) {
fs.readFile(this.disableFile, function handleDisabledFile(err, data) {
var disabledError = null;
if (!err) {
disabledError = new Error(data || 'Unknown error');
disabledError.http_status = 503;
}
);
return callback(disabledError);
});
};

View File

@@ -1,4 +0,0 @@
<Map
background-color="#c33"
srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
</Map>

335
lib/cartodb/server.js Normal file
View File

@@ -0,0 +1,335 @@
var express = require('express');
var bodyParser = require('body-parser');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
var debug = require('debug')('windshaft:cartodb');
var controller = require('./controllers');
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');
var StatsClient = require('./stats/client');
var Profiler = require('./stats/profiler_proxy');
var RendererStatsReporter = require('./stats/reporter/renderer');
var windshaft = require('windshaft');
var mapnik = windshaft.mapnik;
var TemplateMaps = require('./backends/template_maps.js');
var OverviewsMetadataApi = require('./api/overviews_metadata_api');
var UserLimitsApi = require('./api/user_limits_api');
var AuthApi = require('./api/auth_api');
var LayergroupAffectedTablesCache = require('./cache/layergroup_affected_tables');
var NamedMapProviderCache = require('./cache/named_map_provider_cache');
var PgQueryRunner = require('./backends/pg_query_runner');
var PgConnection = require('./backends/pg_connection');
var AnalysisBackend = require('./backends/analysis');
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
var MapConfigOverviewsAdapter = require('./models/mapconfig_overviews_adapter');
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
var TurboCartoAdapter = require('./utils/style/turbo-carto-adapter');
module.exports = function(serverOptions) {
// Make stats client globally accessible
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
var redisPool = new RedisPool(_.defaults(global.environment.redis, {
name: 'windshaft-server',
unwatchOnRelease: false,
noReadyCheck: true
}));
redisPool.on('status', function(status) {
var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
global.statsClient.gauge(keyPrefix + 'count', status.count);
global.statsClient.gauge(keyPrefix + 'unused', status.unused);
global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
});
var metadataBackend = cartodbRedis({pool: redisPool});
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
var userLimitsApi = new UserLimitsApi(metadataBackend, {
limits: {
cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
render: serverOptions.renderer.mapnik.limits.render || 0
}
});
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
});
var surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends(serverOptions));
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) {
global.logger.warn(logMessage);
} else {
global.logger.info(logMessage);
}
});
}
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, invalidateNamedMap);
});
serverOptions.grainstore.mapnik_version = mapnikVersion(serverOptions);
validateOptions(serverOptions);
bootstrapFonts(serverOptions);
// initialize express server
var app = bootstrap(serverOptions);
// Extend windshaft with all the elements of the options object
_.extend(app, serverOptions);
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: serverOptions.grainstore.default_layergroup_ttl
});
var onTileErrorStrategy;
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(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);
}
};
}
var rendererFactory = new windshaft.renderer.Factory({
onTileErrorStrategy: onTileErrorStrategy,
mapnik: {
redisPool: redisPool,
grainstore: serverOptions.grainstore,
mapnik: serverOptions.renderer.mapnik
},
http: serverOptions.renderer.http
});
// initialize render cache
var rendererCacheOpts = _.defaults(serverOptions.renderCache || {}, {
ttl: 60000, // 60 seconds TTL by default
statsInterval: 60000 // reports stats every milliseconds defined here
});
var rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
var rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
rendererStatsReporter.start();
var attributesBackend = new windshaft.backend.Attributes();
var previewBackend = new windshaft.backend.Preview(rendererCache);
var tileBackend = new windshaft.backend.Tile(rendererCache);
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi);
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
var turboCartoAdapter = new TurboCartoAdapter(turboCartoParser);
var namedMapProviderCache = new NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
userLimitsApi,
overviewsAdapter,
turboCartoAdapter
);
['update', 'delete'].forEach(function(eventType) {
templateMaps.on(eventType, namedMapProviderCache.invalidate.bind(namedMapProviderCache));
});
var authApi = new AuthApi(pgConnection, metadataBackend, mapStore, templateMaps);
var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
new controller.Layergroup(
authApi,
pgConnection,
mapStore,
tileBackend,
previewBackend,
attributesBackend,
new windshaft.backend.Widget(),
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
analysisBackend
).register(app);
new controller.Map(
authApi,
pgConnection,
templateMaps,
mapBackend,
metadataBackend,
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
overviewsAdapter,
turboCartoAdapter,
analysisBackend
).register(app);
new controller.NamedMaps(
authApi,
pgConnection,
namedMapProviderCache,
tileBackend,
previewBackend,
surrogateKeysCache,
tablesExtentApi,
metadataBackend
).register(app);
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
new controller.ServerInfo().register(app);
/*******************************************************************************************************************
* END Routing
******************************************************************************************************************/
return app;
};
function validateOptions(opts) {
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
}
// Be nice and warn if configured mapnik version is != instaled mapnik version
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
}
}
function bootstrapFonts(opts) {
// Set carto renderer configuration for MMLStore
opts.grainstore.carto_env = opts.grainstore.carto_env || {};
var cenv = opts.grainstore.carto_env;
cenv.validation_data = cenv.validation_data || {};
if ( ! cenv.validation_data.fonts ) {
mapnik.register_system_fonts();
mapnik.register_default_fonts();
cenv.validation_data.fonts = _.keys(mapnik.fontFiles());
}
}
function bootstrap(opts) {
var app;
if (_.isObject(opts.https)) {
// use https if possible
app = express.createServer(opts.https);
} else {
// fall back to http by default
app = express();
}
app.enable('jsonp callback');
app.disable('x-powered-by');
app.disable('etag');
app.use(bodyParser.json());
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
req.context = req.context || {};
req.profiler = new Profiler({
statsd_client: global.statsClient,
profile: opts.useProfiler
});
if (global.environment && global.environment.api_hostname) {
res.set('X-Served-By-Host', global.environment.api_hostname);
}
next();
});
// temporary measure until we upgrade to newer version expressjs so we can check err.status
app.use(function(err, req, res, next) {
if (err) {
if (err.name === 'SyntaxError') {
res.status(400).json({ errors: [err.name + ': ' + err.message] });
} else {
next(err);
}
} else {
next();
}
});
setupLogger(app, opts);
return app;
}
function setupLogger(app, opts) {
if (global.log4js && opts.log_format) {
var loggerOpts = {
// Allowing for unbuffered logging is mainly
// used to avoid hanging during unit testing.
// TODO: provide an explicit teardown function instead,
// releasing any event handler or timer set by
// this component.
buffer: !opts.unbuffered_logging,
// optional log format
format: opts.log_format
};
app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level: 'info'})));
}
}
function surrogateKeysCacheBackends(serverOptions) {
var cacheBackends = [];
if (serverOptions.varnish_purge_enabled) {
cacheBackends.push(
new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
);
}
if (serverOptions.fastly &&
!!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
cacheBackends.push(
new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
);
}
return cacheBackends;
}
function mapnikVersion(opts) {
return opts.grainstore.mapnik_version || mapnik.versions.mapnik;
}

View File

@@ -1,856 +1,112 @@
var _ = require('underscore')
, Step = require('step')
, Cache = require('./cache_validator')
, QueryTablesApi = require('./api/query_tables_api')
, crypto = require('crypto')
, LZMA = require('lzma').LZMA
;
var os = require('os');
var _ = require('underscore');
var OverviewsQueryRewriter = require('./utils/overviews_query_rewriter');
// 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 overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
});
var rendererConfig = _.defaults(global.environment.renderer || {}, {
cache_ttl: 60000, // milliseconds
statsInterval: 60000,
mapnik: {
poolSize: 8,
metatile: 2,
bufferSize: 64,
snapToGrid: false,
clipByBox2d: false,
limits: {}
},
http: {}
});
rendererConfig.mapnik.queryRewriter = overviewsQueryRewriter;
// Perform keyword substitution in statsd
// See https://github.com/CartoDB/Windshaft-cartodb/issues/153
if (global.environment.statsd) {
if (global.environment.statsd.prefix) {
var host_token = os.hostname().split('.').reverse().join('.');
global.environment.statsd.prefix = global.environment.statsd.prefix.replace(/:host/, host_token);
}
}
// Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [
'sql',
'geom_type',
'cache_buster',
'cache_policy',
'callback',
'interactivity',
'map_key',
'api_key',
'auth_token',
'style',
'style_version',
'style_convert',
'config',
'scale_factor'
];
var analysisConfig = _.defaults(global.environment.analysis || {}, {
batch: {
inlineExecution: false,
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
});
module.exports = function(redisPool) {
var redisOpts = redisPool ? {pool: redisPool} : global.environment.redis;
var cartoData = require('cartodb-redis')(redisOpts),
lzmaWorker = new LZMA(),
queryTablesApi = new QueryTablesApi();
module.exports = {
bind: {
port: global.environment.port,
host: global.environment.host
},
// This is for inline maps and table maps
base_url: global.environment.base_url_legacy || '/tiles/:table',
var rendererConfig = _.defaults(global.environment.renderer || {}, {
cache_ttl: 60000, // milliseconds
metatile: 4,
bufferSize: 64,
statsInterval: 60000
});
/// @deprecated with Windshaft-0.17.0
///base_url_notable: '/tiles',
var me = {
// This is for inline maps and table maps
base_url: global.environment.base_url_legacy || '/tiles/:table',
// This is for Detached maps
//
// "maps" is the official, while
// "tiles/layergroup" is for backward compatibility up to 1.6.x
//
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
/// @deprecated with Windshaft-0.17.0
///base_url_notable: '/tiles',
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
// This is for Detached maps
//
// "maps" is the official, while
// "tiles/layergroup" is for backward compatibility up to 1.6.x
//
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
grainstore: {
map: {
grainstore: {
map: {
// TODO: allow to specify in configuration
srid: 3857
},
datasource: global.environment.postgres,
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
},
statsd: global.environment.statsd,
renderCache: {
ttl: rendererConfig.cache_ttl,
statsInterval: rendererConfig.statsInterval
},
renderer: {
http: rendererConfig.http
},
redis: global.environment.redis,
enable_cors: global.environment.enable_cors,
varnish_host: global.environment.varnish.host,
varnish_port: global.environment.varnish.port,
varnish_http_port: global.environment.varnish.http_port,
varnish_secret: global.environment.varnish.secret,
varnish_purge_enabled: global.environment.varnish.purge_enabled,
cache_enabled: global.environment.cache_enabled,
log_format: global.environment.log_format,
useProfiler: global.environment.useProfiler
};
// Do not send unwatch on release
// See http://github.com/CartoDB/Windshaft-cartodb/issues/161
me.redis.unwatchOnRelease = false;
// Re-use redisPool
me.redis.pool = redisPool;
/* This whole block is about generating X-Cache-Channel { */
// TODO: review lifetime of elements of this cache
// NOTE: by-token indices should only be dropped when
// the corresponding layegroup is dropped, because
// we have no SQL after layer creation.
me.channelCache = {};
me.buildCacheChannel = function (dbName, tableNames){
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 ];
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);
datasource: global.environment.postgres,
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
},
statsd: global.environment.statsd,
renderCache: {
ttl: rendererConfig.cache_ttl,
statsInterval: rendererConfig.statsInterval
},
renderer: {
mapnik: _.defaults(rendererConfig.mapnik, {
geojson: {
dbPoolParams: {
size: 16,
idleTimeout: 3000,
reapInterval: 1000
},
clipByBox2d: false
}
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;
}
}),
torque: rendererConfig.torque,
http: rendererConfig.http
},
if ( ! req.params.sql ) {
return null; // no sql
}
// We have sql, and no token...
// strip out windshaft/mapnik inserted sql if present
var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/);
sql = (sql != null) ? sql[1] : req.params.sql;
return sql;
},
function findAffectedTables(err, sql) {
if ( err ) throw err;
if ( ! sql ) {
if ( ! req.params.table ) {
throw new Error("this request doesn't need an X-Cache-Channel generated");
}
return [req.params.table];
}
var user, key;
var next = this;
Step (
function findUserKey() {
if ( req.params.hasOwnProperty('_authorizedBySigner') ) {
user = req.params._authorizedBySigner;
cartoData.getUserMapKey(user, this);
} else {
user = that.userByReq(req);
key = req.params.map_key || req.params.api_key;
return null;
}
},
function getAffected(err, data) {
if ( err ) throw err;
if ( data ) {
if ( req.profiler ) req.profiler.done('getSignerMapKey');
key = data;
}
queryTablesApi.getAffectedTablesInQuery(user, {
user: req.params.dbuser,
pass: req.params.dbpass,
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
api_key: key
}, sql, this); // in addCacheChannel
},
function finish(err, data) {
next(err,data);
}
);
},
function buildCacheChannel(err, tableNames) {
if ( err ) throw err;
if (req.profiler && ! req.params.table ) {
req.profiler.done('affectedTables');
}
var dbName = req.params.dbname;
var cacheChannel = me.buildCacheChannel(dbName,tableNames);
// store for caching from me.generateCacheChannel
// (not worth when table was specified in params)
if ( ! req.params.table ) {
me.channelCache[cacheKey] = cacheChannel;
}
return cacheChannel;
},
function finish(err, cacheChannel) {
callback(err, cacheChannel);
analysis: {
batch: {
inlineExecution: analysisConfig.batch.inlineExecution,
endpoint: analysisConfig.batch.endpoint,
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
}
);
};
// Set the cache chanel info to invalidate the cache on the frontend server
//
// @param req The request object.
// The function will have no effect unless req.res exists.
// It is expected that req.params contains 'table' and 'dbname'
//
// @param cb function(err, channel) will be called when ready.
// the channel parameter will be null if nothing was added
//
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');
var res = req.res;
var cache_policy = req.query.cache_policy;
if ( req.params.token ) cache_policy = 'persist';
if ( cache_policy == 'persist' ) {
res.header('Cache-Control', 'public,max-age=31536000'); // 1 year
} else {
var ttl = global.environment.varnish.ttl || 86400;
res.header('Cache-Control', 'no-cache,max-age='+ttl+',must-revalidate, public');
}
// Set Last-Modified header
var lastUpdated;
if ( req.params.cache_buster ) {
// Assuming cache_buster is a timestamp
// FIXME: store lastModified in the cache channel instead
lastUpdated = new Date(parseInt(req.params.cache_buster));
} else {
lastUpdated = new Date();
}
res.header('Last-Modified', lastUpdated.toUTCString());
me.generateCacheChannel(app, req, function(err, channel){
if (req.profiler) req.profiler.done('generateCacheChannel');
if (req.profiler) req.profiler.end();
if ( ! err ) {
res.header('X-Cache-Channel', channel);
cb(null, channel);
} else {
console.log('ERROR generating cache channel: ' + ( err.message ? err.message : err ));
// TODO: evaluate if we should bubble up the error instead
cb(null, 'ERROR');
}
});
};
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
var token = response.layergroupid;
var username = this.userByReq(req);
var tasksleft = 2; // redis key and affectedTables
var errors = [];
var done = function(err) {
if ( err ) {
errors.push('' + err);
}
if ( ! --tasksleft ) {
err = errors.length ? new Error(errors.join('\n')) : null;
callback(err);
}
};
// include in layergroup response the variables in serverMedata
// those variables are useful to send to the client information
// about how to reach this server or information about it
var serverMetadata = global.environment.serverMetadata;
if (serverMetadata) {
_.extend(response, serverMetadata);
}
// Don't wait for the mapview count increment to
// 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);
done();
});
var sql = [];
_.each(mapconfig.layers, function(lyr) {
sql.push(lyr.options.sql);
});
sql = sql.join(';');
var dbName = req.params.dbname;
var usr = this.userByReq(req);
var key = req.params.map_key || req.params.api_key;
var cacheKey = dbName + ':' + token;
Step(
function getAffectedTablesAndLastUpdatedTime() {
queryTablesApi.getAffectedTablesAndLastUpdatedTime(usr, {
user: req.params.dbuser,
pass: req.params.dbpass,
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
api_key: key
}, sql, this);
},
function handleAffectedTablesAndLastUpdatedTime(err, result) {
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
if ( err ) throw 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');
}
res.header('Last-Modified', (new Date()).toUTCString());
res.header('X-Cache-Channel', cacheChannel);
}
// last update for layergroup cache buster
response.layergroupid = response.layergroupid + ':' + result.lastUpdatedTime;
response.last_updated = new Date(result.lastUpdatedTime).toISOString();
return null;
},
function finish(err) {
done(err);
}
);
};
/* X-Cache-Channel generation } */
me.re_userFromHost = new RegExp(
global.environment.user_from_host ||
'^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com"
);
me.userByReq = function(req) {
var host = req.headers.host;
var mat = host.match(this.re_userFromHost);
if ( ! mat ) {
console.error("ERROR: user pattern '" + this.re_userFromHost
+ "' does not match hostname '" + host + "'");
return;
}
// console.log("Matches: "); console.dir(mat);
if ( ! mat.length === 2 ) {
console.error("ERROR: pattern '" + this.re_userFromHost
+ "' gave unexpected matches against '" + host + "': " + mat);
return;
}
return mat[1];
};
// Set db authentication parameters to those of the given username
//
// @param username the cartodb username, mapped to a database username
// via CartodbRedis metadata records
//
// @param params the parameters to set auth options into
// added params are: "dbuser" and "dbpassword"
//
// @param callback function(err)
//
me.setDBAuth = function(username, params, callback) {
var user_params = {};
var auth_user = global.environment.postgres_auth_user;
var auth_pass = global.environment.postgres_auth_pass;
Step(
function getId() {
cartoData.getUserId(username, this);
},
function(err, user_id) {
if (err) throw err;
user_params['user_id'] = user_id;
var dbuser = _.template(auth_user, user_params);
_.extend(params, {dbuser:dbuser});
// skip looking up user_password if postgres_auth_pass
// doesn't contain the "user_password" label
if (!auth_pass || ! auth_pass.match(/\buser_password\b/) ) return null;
cartoData.getUserDBPass(username, this);
},
function(err, user_password) {
if (err) throw err;
user_params['user_password'] = user_password;
if ( auth_pass ) {
var dbpass = _.template(auth_pass, user_params);
_.extend(params, {dbpassword:dbpass});
}
return true;
},
function finish(err) {
callback(err);
}
);
};
// Set db connection parameters to those for the given username
//
// @param dbowner cartodb username of database owner,
// mapped to a database username
// via CartodbRedis metadata records
//
// @param params the parameters to set connection options into
// added params are: "dbname", "dbhost"
//
// @param callback function(err)
//
me.setDBConn = function(dbowner, params, callback) {
// Add default database connection parameters
// if none given
_.defaults(params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
Step(
function getConnectionParams() {
cartoData.getUserDBConnectionParams(dbowner, this);
},
function extendParams(err, dbParams){
if (err) throw err;
// we don't want null values or overwrite a non public user
if (params.dbuser != 'publicuser' || !dbParams.dbuser) {
delete dbParams.dbuser;
}
if ( dbParams ) _.extend(params, dbParams);
return null;
},
function finish(err) {
callback(err);
}
);
};
// Check if a request is authorized by a signer
//
// @param req express request object
// @param callback function(err, signed_by) signed_by will be
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
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;
}
var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token);
callback(null, authorized ? signer : null);
});
};
// Check if a request is authorized by api_key
//
// @param req express request object
// @param callback function(err, authorized)
// NOTE: authorized is expected to be 0 or 1 (integer)
//
me.authorizedByAPIKey = function(req, callback)
{
var givenKey = req.query.api_key || req.query.map_key;
if ( ! givenKey && req.body ) {
// check also in request body
givenKey = req.body.api_key || req.body.map_key;
}
if ( ! givenKey ) {
callback(null, 0); // no api key, no authorization...
return;
}
//console.log("given ApiKey: " + givenKey);
var user = me.userByReq(req);
Step(
function (){
cartoData.getUserMapKey(user, this);
},
function checkApiKey(err, val){
if (err) throw err;
return ( val && givenKey == val ) ? 1 : 0;
},
function finish(err, authorized) {
callback(err, authorized);
}
);
};
/**
* Check access authorization
*
* @param req - standard req object. Importantly contains table and host information
* @param callback function(err, allowed) is access allowed not?
*/
me.authorize = function(req, callback) {
var that = this;
var user = me.userByReq(req);
Step(
function (){
that.authorizedByAPIKey(req, this);
},
function checkApiKey(err, authorized){
if (req.profiler) req.profiler.done('authorizedByAPIKey');
if (err) throw err;
// if not authorized by api_key, continue
if (authorized !== 1) {
// not authorized by api_key,
// check if authorized by signer
that.authorizedBySigner(req, this);
return;
}
_.extend(req.params, { _authorizedByApiKey: true });
// authorized by api key, login as the given username and stop
that.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 ( ! 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.
//
if ( ! req.params.signer ) {
callback(null, true); // authorized so far
return;
}
// if signer name was given, return no authorization
callback(null, false);
return;
}
// Authorized by "signed_by" !
_.extend(req.params, { _authorizedBySigner: signed_by });
that.setDBAuth(signed_by, req.params, function(err) {
if (req.profiler) req.profiler.done('setDBAuth');
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");
}
);
};
/**
* Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis
* @param req - standard express request obj. Should have host & table
* @param callback
*/
me.req2params = function(req, callback){
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 });
// Decompress
lzmaWorker.decompress(
lzma,
function(result) {
if (req.profiler) req.profiler.done('LZMA decompress');
try {
delete req.query.lzma;
_.extend(req.query, JSON.parse(result));
me.req2params(req, callback);
} 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);
_.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);
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];
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 + '"');
err.http_status = 403;
callback(err);
return;
}
if ( tksplit.length > 1 ) {
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);
}
}
// 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';
var that = this;
if (req.profiler) req.profiler.done('req2params.setup');
Step(
function getPrivacy(){
me.authorize(req, this);
},
function gatekeep(err, authorized){
if (req.profiler) req.profiler.done('authorize');
if(err) throw err;
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
throw err;
}
return null;
},
function getDatabase(err){
if(err) throw err;
that.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){
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, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
callback(null, req);
}
);
};
/**
* Little helper method to get the current list of infowindow variables and return to client
* @param req
* @param callback
*/
me.getInfowindow = function(req, callback){
var that = this;
var user = me.userByReq(req);
Step(
function(){
// TODO: if this step really needed ?
that.req2params(req, this);
},
function getDatabase(err){
if (err) throw err;
cartoData.getUserDBName(user, this);
},
function getInfowindow(err, dbname){
if (err) throw err;
cartoData.getTableInfowindow(dbname, req.params.table, this);
},
function(err, data){
callback(err, data);
}
);
};
/**
* Little helper method to get map metadata and return to client
* @param req
* @param callback
*/
me.getMapMetadata = function(req, callback){
var that = this;
var user = me.userByReq(req);
Step(
function(){
// TODO: if this step really needed ?
that.req2params(req, this);
},
function getDatabase(err){
if (err) throw err;
cartoData.getUserDBName(user, this);
},
function getMapMetadata(err, dbname){
if (err) throw err;
cartoData.getTableMapMetadata(dbname, req.params.table, this);
},
function(err, data){
callback(err, data);
}
);
};
/**
* Helper to clear out tile cache on request
* @param req
* @param callback
*/
me.flushCache = function(req, Cache, callback){
var that = this;
Step(
function getParams(){
// this is mostly to compute req.params.dbname
that.req2params(req, this);
},
function flushInternalCache(err){
// TODO: implement this, see
// http://github.com/Vizzuality/Windshaft-cartodb/issues/73
return true;
},
function flushVarnishCache(err){
if (err) { callback(err); return; }
if(Cache) {
Cache.invalidate_db(req.params.dbname, req.params.table);
}
callback(null, true);
}
);
};
return me;
},
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
enable_cors: global.environment.enable_cors,
varnish_host: global.environment.varnish.host,
varnish_port: global.environment.varnish.port,
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
};

View File

@@ -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);
});
};

View File

@@ -0,0 +1,73 @@
var _ = require('underscore');
var debug = require('debug')('windshaft:stats_client');
var StatsD = require('node-statsd').StatsD;
module.exports = {
/**
* Returns an StatsD instance or an stub object that replicates the StatsD public interface so there is no need to
* keep checking if the stats_client is instantiated or not.
*
* The first call to this method implies all future calls will use the config specified in the very first call.
*
* TODO: It's far from ideal to use make this a singleton, improvement desired.
* We proceed this way to be able to use StatsD from several places sharing one single StatsD instance.
*
* @param config Configuration for StatsD, if undefined it will return an stub
* @returns {StatsD|Object}
*/
getInstance: function(config) {
if (!this.instance) {
var instance;
if (config) {
instance = new StatsD(config);
instance.last_error = { msg: '', count: 0 };
instance.socket.on('error', function (err) {
var last_err = instance.last_error;
var last_msg = last_err.msg;
var this_msg = '' + err;
if (this_msg !== last_msg) {
debug("statsd client socket error: " + err);
instance.last_error.count = 1;
instance.last_error.msg = this_msg;
} else {
++last_err.count;
if (!last_err.interval) {
instance.last_error.interval = setInterval(function () {
var count = instance.last_error.count;
if (count > 1) {
debug("last statsd client socket error repeated " + count + " times");
instance.last_error.count = 1;
clearInterval(instance.last_error.interval);
instance.last_error.interval = null;
}
}, 1000);
}
}
});
} else {
var stubFunc = function (stat, value, sampleRate, callback) {
if (_.isFunction(callback)) {
callback(null, 0);
}
};
instance = {
timing: stubFunc,
increment: stubFunc,
decrement: stubFunc,
gauge: stubFunc,
unique: stubFunc,
set: stubFunc,
sendAll: stubFunc,
send: stubFunc
};
}
this.instance = instance;
}
return this.instance;
}
};

View File

@@ -0,0 +1,53 @@
var Profiler = require('step-profiler');
/**
* Proxy to encapsulate node-step-profiler module so there is no need to check if there is an instance
*/
function ProfilerProxy(opts) {
this.profile = !!opts.profile;
this.profiler = null;
if (!!opts.profile) {
this.profiler = new Profiler({statsd_client: opts.statsd_client});
}
}
ProfilerProxy.prototype.done = function(what) {
if (this.profile) {
this.profiler.done(what);
}
};
ProfilerProxy.prototype.end = function() {
if (this.profile) {
this.profiler.end();
}
};
ProfilerProxy.prototype.start = function(what) {
if (this.profile) {
this.profiler.start(what);
}
};
ProfilerProxy.prototype.add = function(what) {
if (this.profile) {
this.profiler.add(what || {});
}
};
ProfilerProxy.prototype.sendStats = function() {
if (this.profile) {
this.profiler.sendStats();
}
};
ProfilerProxy.prototype.toString = function() {
return this.profile ? this.profiler.toString() : "";
};
ProfilerProxy.prototype.toJSONString = function() {
return this.profile ? this.profiler.toJSONString() : "{}";
};
module.exports = ProfilerProxy;

View File

@@ -0,0 +1,82 @@
// - Reports stats about:
// * Total number of renderers
// * For mapnik renderers:
// - the mapnik-pool status: count, unused and waiting
// - the internally cached objects: png and grid
var _ = require('underscore');
function RendererStatsReporter(rendererCache, statsInterval) {
this.rendererCache = rendererCache;
this.statsInterval = statsInterval || 6e4;
this.renderersStatsIntervalId = null;
}
module.exports = RendererStatsReporter;
RendererStatsReporter.prototype.start = function() {
var self = this;
this.renderersStatsIntervalId = setInterval(function() {
var rendererCacheEntries = self.rendererCache.renderers;
if (!rendererCacheEntries) {
return null;
}
global.statsClient.gauge('windshaft.rendercache.count', _.keys(rendererCacheEntries).length);
var renderersStats = _.reduce(rendererCacheEntries, function(_rendererStats, cacheEntry) {
var stats = cacheEntry.renderer && cacheEntry.renderer.getStats && cacheEntry.renderer.getStats();
if (!stats) {
return _rendererStats;
}
_rendererStats.pool.count += stats.pool.count;
_rendererStats.pool.unused += stats.pool.unused;
_rendererStats.pool.waiting += stats.pool.waiting;
_rendererStats.cache.grid += stats.cache.grid;
_rendererStats.cache.png += stats.cache.png;
return _rendererStats;
},
{
pool: {
count: 0,
unused: 0,
waiting: 0
},
cache: {
png: 0,
grid: 0
}
}
);
global.statsClient.gauge('windshaft.mapnik-cache.png', renderersStats.cache.png);
global.statsClient.gauge('windshaft.mapnik-cache.grid', renderersStats.cache.grid);
global.statsClient.gauge('windshaft.mapnik-pool.count', renderersStats.pool.count);
global.statsClient.gauge('windshaft.mapnik-pool.unused', renderersStats.pool.unused);
global.statsClient.gauge('windshaft.mapnik-pool.waiting', renderersStats.pool.waiting);
}, this.statsInterval);
this.rendererCache.on('err', rendererCacheErrorListener);
this.rendererCache.on('gc', gcTimingListener);
};
function rendererCacheErrorListener() {
global.statsClient.increment('windshaft.rendercache.error');
}
function gcTimingListener(gcTime) {
global.statsClient.timing('windshaft.rendercache.gc', gcTime);
}
RendererStatsReporter.prototype.stop = function() {
this.rendererCache.removeListener('err', rendererCacheErrorListener);
this.rendererCache.removeListener('gc', gcTimingListener);
clearInterval(this.renderersStatsIntervalId);
this.renderersStatsIntervalId = null;
};

View File

@@ -0,0 +1,32 @@
function Timer() {
this.times = {};
}
module.exports = Timer;
Timer.prototype.start = function(label) {
this.timeIt(label, 'start');
};
Timer.prototype.end = function(label) {
this.timeIt(label, 'end');
};
Timer.prototype.timeIt = function(label, what) {
this.times[label] = this.times[label] || {};
this.times[label][what] = Date.now();
};
Timer.prototype.getTimes = function() {
var self = this;
var times = {};
Object.keys(this.times).forEach(function(label) {
var stat = self.times[label];
if (stat.start && stat.end) {
times[label] = Math.max(0, stat.end - stat.start);
}
});
return times;
};

View File

@@ -0,0 +1,194 @@
var TableNameParser = require('./table_name_parser');
function OverviewsQueryRewriter(options) {
this.options = options;
}
module.exports = OverviewsQueryRewriter;
// TODO: some names are introudced in the queries, and the
// '_vovw_' (for vector overviews) is used in them, but no check
// is performed for conflicts with existing identifiers in the query.
// Build UNION expression to replace table, using overviews metadata
// overviews metadata: { 1: 'table_ov1', ... }
// assume table and overview names include schema if necessary and are quoted as needed
function overviews_view_for_table(table, overviews_metadata, indent) {
var condition, i, len, ov_table, overview_layers, selects, z_hi, z_lo;
var parsed_table = TableNameParser.parse(table);
var sorted_overviews = []; // [[1, 'table_ov1'], ...]
indent = indent || ' ';
for (var z in overviews_metadata) {
if (overviews_metadata.hasOwnProperty(z) && z !== 'schema') {
sorted_overviews.push([z, overviews_metadata[z].table]);
}
}
sorted_overviews.sort(function(a, b){ return a[0]-b[0]; });
overview_layers = [];
z_lo = null;
for (i = 0, len = sorted_overviews.length; i < len; i++) {
z_hi = parseInt(sorted_overviews[i][0]);
ov_table = sorted_overviews[i][1];
overview_layers.push([overview_z_condition(z_lo, z_hi), ov_table]);
z_lo = z_hi;
}
overview_layers.push(["_vovw_z > " + z_lo, table]);
selects = overview_layers.map(function(condition_table) {
condition = condition_table[0];
ov_table = TableNameParser.parse(condition_table[1]);
ov_table.schema = ov_table.schema || parsed_table.schema;
var ov_identifier = TableNameParser.table_identifier(ov_table);
return indent + "SELECT * FROM " + ov_identifier + ", _vovw_scale WHERE " + condition;
});
return selects.join("\n"+indent+"UNION ALL\n");
}
function overview_z_condition(z_lo, z_hi) {
if (z_lo !== null) {
if (z_lo === z_hi - 1) {
return "_vovw_z = " + z_hi;
} else {
return "_vovw_z > " + z_lo + " AND _vovw_z <= " + z_hi;
}
} else {
if (z_hi === 0) {
return "_vovw_z = " + z_hi;
} else {
return "_vovw_z <= " + z_hi;
}
}
}
// name to be used for the view of the table using overviews
function overviews_view_name(table) {
var parsed_table = TableNameParser.parse(table);
parsed_table.table = '_vovw_' + parsed_table.table;
parsed_table.schema = null;
return TableNameParser.table_identifier(parsed_table);
}
// replace a table name in a query by anoter name
function replace_table_in_query(sql, old_table_name, replacement) {
var old_table = TableNameParser.parse(old_table_name);
var old_table_ident = TableNameParser.table_identifier(old_table);
// regular expression prefix (beginning) to match a table name
function pattern_prefix(schema, identifier) {
if ( schema ) {
// to match a table name including schema prefix
// name should not be part of another name, so we require
// to start a at a word boundary
if ( identifier[0] !== '"' ) {
return '\\b';
} else {
return '';
}
} else {
// to match a table name without schema
// name should not begin right after a dot (i.e. have a explicit schema)
// nor be part of another name
// since the pattern matches the first character of the table
// it must be put back in the replacement text
replacement = '$01'+replacement;
return '([^\.a-z0-9_]|^)';
}
}
// regular expression suffix (ending) to match a table name
function pattern_suffix(identifier) {
// name shouldn't be the prefix of a longer name
if ( identifier[identifier.length-1] !== '"' ) {
return '\\b';
} else {
return '';
}
}
// regular expression to match a table name
var regexp = pattern_prefix(old_table.schema, old_table_ident) +
old_table_ident +
pattern_suffix(old_table_ident);
// replace all occurrences of the table pattern
return sql.replace(new RegExp(regexp, 'g'), replacement);
}
function overviews_query(query, overviews, zoom_level_expression) {
var replaced_query = query;
var sql = "WITH\n _vovw_scale AS ( SELECT " + zoom_level_expression + " AS _vovw_z )";
var replacement;
for ( var table in overviews ) {
if (overviews.hasOwnProperty(table)) {
var table_overviews = overviews[table];
var table_view = overviews_view_name(table);
var schema = table_overviews.schema;
replacement = "(\n" + overviews_view_for_table(table, table_overviews) + "\n ) AS " + table_view;
replaced_query = replace_table_in_query(replaced_query, table, replacement);
var parsed_table = TableNameParser.parse(table);
if (!parsed_table.schema && schema) {
// replace also the qualified table name, if the table wasn't qualified
parsed_table.schema = schema;
table = TableNameParser.table_identifier(parsed_table);
replaced_query = replace_table_in_query(replaced_query, table, replacement);
}
}
}
if ( replaced_query !== query ) {
sql += "\n";
sql += replaced_query;
} else {
sql = query;
}
return sql;
}
// Transform an SQL query so that it uses overviews.
// overviews contains metadata about the overviews to be used:
// { 'table-name': {1: { table: 'overview-table-1' }, ... }, ... }
//
// For a given query `SELECT * FROM table`, if any of tables in it
// has overviews as defined by the provided metadat, the query will
// be transform into something similar to this:
//
// WITH _vovw_scale AS ( ... ), -- define scale level
// WITH _vovw_table AS ( ... ), -- define union of overviews and base table
// SELECT * FROM _vovw_table -- query with table replaced by _vovw_table
//
// This transformation can in principle be applied to arbitrary queries
// (except for the case of queries that include the name of tables with
// overviews inside text literals: at the current table name substitution
// doesnn't prevent substitution inside literals).
// But the transformation will currently only be applied to simple queries
// of the form detected by the overviews_supported_query function.
OverviewsQueryRewriter.prototype.query = function(query, data) {
var overviews = this.overviews_metadata(data);
if ( !overviews || !this.is_supported_query(query)) {
return query;
}
var zoom_level_expression = this.options.zoom_level || '0';
return overviews_query(query, overviews, zoom_level_expression);
};
OverviewsQueryRewriter.prototype.is_supported_query = function(sql) {
var basic_query =
/\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i;
var unwrapped_query = new RegExp("^"+basic_query.source+"$", 'i');
// queries for named maps are wrapped like this:
var wrapped_query = new RegExp(
"^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" +
basic_query.source +
"\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$",
'i'
);
return !!(sql.match(unwrapped_query) || sql.match(wrapped_query));
};
OverviewsQueryRewriter.prototype.overviews_metadata = function(data) {
return data && data.overviews;
};

View File

@@ -0,0 +1,55 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
function createTemplate(method) {
return dot.template([
'SELECT',
method,
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
].join('\n'));
}
var methods = {
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
};
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
methodTemplates[methodName] = createTemplate(methods[methodName]);
return methodTemplates;
}, {});
function PostgresDatasource (pgQueryRunner, username, query) {
this.pgQueryRunner = pgQueryRunner;
this.username = username;
this.query = query;
}
PostgresDatasource.prototype.getName = function () {
return 'PostgresDatasource';
};
PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
var methodName = methods.hasOwnProperty(method) ? method : 'quantiles';
var template = methodTemplates[methodName];
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
this.pgQueryRunner.run(this.username, query, function (err, result) {
if (err) {
return callback(err);
}
var ramp = result[0][methodName].sort(function(a, b) {
return a - b;
});
return callback(null, ramp);
});
};
module.exports = PostgresDatasource;

View File

@@ -0,0 +1,56 @@
'use strict';
var queue = require('queue-async');
function TurboCartoAdapter(turboCartoParser) {
this.turboCartoParser = turboCartoParser;
}
module.exports = TurboCartoAdapter;
TurboCartoAdapter.prototype.getLayers = function (username, layers, callback) {
var self = this;
if (!layers || layers.length === 0) {
return callback(null, layers);
}
var parseCartoQueue = queue(layers.length);
layers.forEach(function(layer) {
parseCartoQueue.defer(self._parseCartoCss.bind(self), username, layer);
});
parseCartoQueue.awaitAll(function (err, layers) {
if (err) {
return callback(err);
}
return callback(null, layers);
});
};
TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback) {
if (isNotLayerToParseCartocss(layer)) {
return process.nextTick(function () {
callback(null, layer);
});
}
this.turboCartoParser.process(username, layer.options.cartocss, layer.options.sql, function (err, cartocss) {
// Ignore turbo-carto errors and continue
if (!err && cartocss) {
layer.options.cartocss = cartocss;
}
callback(null, layer);
});
};
function isNotLayerToParseCartocss(layer) {
if (!layer || !layer.options || !layer.options.cartocss || !layer.options.sql) {
return true;
}
return false;
}

View File

@@ -0,0 +1,15 @@
'use strict';
var turboCarto = require('turbo-carto');
var PostgresDatasource = require('./postgres-datasource');
function TurboCartoParser (pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TurboCartoParser;
TurboCartoParser.prototype.process = function (username, cartocss, sql, callback) {
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
turboCarto(cartocss, datasource, callback);
};

View File

@@ -0,0 +1,106 @@
// Quote an PostgreSQL identifier if ncecessary
function quote_identifier_if_needed(txt) {
if ( txt && !txt.match(/^[a-z_][a-z_0-9]*$/)) {
return '"' + txt.replace(/\"/g, '""') + '"';
} else {
return txt;
}
}
// Parse PostgreSQL table name (possibly quoted and with optional schema).+
// Returns { schema: 'schema_name', table: 'table_name' }
function parse_table_name(table) {
function split_as_quoted_parts(table_name) {
// parse table into 'parts' that may be quoted, each part
// in the parts array being an object { part: 'text', quoted: false/true }
var parts = [];
var splitted = table_name.split(/\"/);
for (var i=0; i<splitted.length; i++ ) {
if ( splitted[i] === '' ) {
if ( parts.length > 0 && i < splitted.length-1 ) {
i++;
parts[parts.length - 1].part += '"' + splitted[i];
}
}
else {
var is_quoted = (i > 0 && splitted[i-1] === '') ||
(i < splitted.length - 1 && splitted[i+1] === '');
parts.push({ part: splitted[i], quoted: is_quoted });
}
}
return parts;
}
var parts = split_as_quoted_parts(table);
function split_single_part(part) {
var schema_part = null;
var table_part = null;
if ( part.quoted ) {
table_part = part.part;
} else {
var parts = part.part.split('.');
if ( parts.length === 1 ) {
schema_part = null;
table_part = parts[0];
} else if ( parts.length === 2 ) {
schema_part = parts[0];
table_part = parts[1];
} // else invalid table name
}
return {
schema: schema_part,
table: table_part
};
}
function split_two_parts(part1, part2) {
var schema_part = null;
var table_part = null;
if ( part1.quoted && !part2.quoted ) {
if ( part2.part[0] === '.' ) {
schema_part = part1.part;
table_part = part2.part.slice(1);
} // else invalid table name (missing dot)
} else if ( !part1.quoted && part2.quoted ) {
if ( part1.part[part1.part.length - 1] === '.' ) {
schema_part = part1.part.slice(0, -1);
table_part = part2.part;
} // else invalid table name (missing dot)
} // else invalid table name (missing dot)
return {
schema: schema_part,
table: table_part
};
}
if ( parts.length === 1 ) {
return split_single_part(parts[0]);
} else if ( parts.length === 2 ) {
return split_two_parts(parts[0], parts[1]);
} else if ( parts.length === 3 && parts[1].part === '.' ) {
return {
schema: parts[0].part,
table: parts[2].part
};
} // else invalid table name
}
function table_identifier(parsed_name) {
if ( parsed_name && parsed_name.table ) {
if ( parsed_name.schema ) {
return quote_identifier_if_needed(parsed_name.schema) + '.' + quote_identifier_if_needed(parsed_name.table);
} else {
return quote_identifier_if_needed(parsed_name.table);
}
} else {
return null;
}
}
module.exports = {
parse: parse_table_name,
quote: quote_identifier_if_needed,
table_identifier: table_identifier
};

4222
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +1,61 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "1.26.2",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
],
"url": "https://github.com/CartoDB/Windshaft-cartodb",
"licenses": [{
"type": "BSD",
"url": "https://github.com/CartoDB/Windshaft-cartodb/blob/master/LICENCE"
}],
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
},
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
"contributors": [
"Simon Tokumine <simon@vizzuality.com>",
"Javi Santana <jsantana@vizzuality.com>",
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
"underscore" : "~1.6.0",
"dot": "~1.0.2",
"windshaft": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
"step": "~0.0.5",
"request": "~2.9.203",
"cartodb-redis": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
"cartodb-psql": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
"redis-mpool": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
"lzma": "~1.3.7",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"rollbar": "~0.3.13"
},
"devDependencies": {
"mocha": "~1.21.4",
"redis": "~0.8.6",
"strftime": "~0.8.2",
"semver": "~1.1.4"
},
"scripts": {
"test": "make check"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=1.2.1"
}
"private": true,
"name": "windshaft-cartodb",
"version": "2.39.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
],
"url": "https://github.com/CartoDB/Windshaft-cartodb",
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/Windshaft-cartodb.git"
},
"author": "Vizzuality <contact@vizzuality.com> (http://vizzuality.com)",
"contributors": [
"Simon Tokumine <simon@vizzuality.com>",
"Javi Santana <jsantana@vizzuality.com>",
"Sandro Santilli <strk@vizzuality.com>"
],
"dependencies": {
"body-parser": "~1.14.0",
"camshaft": "0.6.0",
"cartodb-psql": "~0.6.1",
"cartodb-query-tables": "~0.1.0",
"cartodb-redis": "~0.13.0",
"debug": "~2.2.0",
"dot": "~1.0.2",
"express": "~4.13.3",
"fastly-purge": "~1.0.1",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"lru-cache": "2.6.5",
"lzma": "~1.3.7",
"node-statsd": "~0.0.7",
"queue-async": "~1.0.7",
"redis-mpool": "~0.4.0",
"request": "~2.62.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.7.1",
"underscore": "~1.6.0",
"windshaft": "1.19.0"
},
"devDependencies": {
"istanbul": "~0.4.3",
"jshint": "~2.6.0",
"mocha": "~1.21.4",
"nock": "~2.11.0",
"redis": "~0.8.6",
"semver": "~1.1.4",
"strftime": "~0.8.2"
},
"scripts": {
"preinstall": "make pre-install",
"test": "make test-all"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=2.14.16"
}
}

View File

@@ -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
@@ -23,7 +24,10 @@ cleanup() {
return;
fi
fi
redis-cli -p ${REDIS_PORT} info stats
redis-cli -p ${REDIS_PORT} info keyspace
echo "Killing test redis pid ${PID_REDIS}"
#kill ${PID_REDIS_MONITOR}
kill ${PID_REDIS}
fi
if test x"$OPT_DROP_PGSQL" = xyes; then
@@ -69,6 +73,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 +93,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 +121,16 @@ cd -
PATH=node_modules/.bin/:$PATH
echo "Running tests"
mocha -t 10000 -u tdd ${MOCHA_OPTS} ${TESTS}
#redis-cli -p ${REDIS_PORT} monitor > /tmp/windshaft-cartodb.redis.log &
#PID_REDIS_MONITOR=$!
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

View File

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

7
scripts/install.sh Normal file
View File

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

18
scripts/lzma2config.js Normal file
View File

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

View File

@@ -0,0 +1,148 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
var dot = require('dot');
describe('analysis-layers-dataviews', function() {
var multitypeStyleTemplate = dot.template([
"#points['mapnik::geometry_type'=1] {",
" marker-fill-opacity: {{=it._opacity}};",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: {{=it._opacity}};",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: {{=it._color}};",
" marker-allow-overlap: true;",
"}",
"#lines['mapnik::geometry_type'=2] {",
" line-color: {{=it._color}};",
" line-width: 2;",
" line-opacity: {{=it._opacity}};",
"}",
"#polygons['mapnik::geometry_type'=3] {",
" polygon-fill: {{=it._color}};",
" polygon-opacity: {{=it._opacity}};",
" line-color: #FFF;",
" line-width: 0.5;",
" line-opacity: {{=it._opacity}};",
"}"
].join('\n'));
function cartocss(color, opacity) {
return multitypeStyleTemplate({
_color: color || '#F11810',
_opacity: Number.isFinite(opacity) ? opacity : 1
});
}
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var DEFAULT_MULTITYPE_STYLE = cartocss();
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": DEFAULT_MULTITYPE_STYLE,
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
);
it('should get histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
testClient.getDataview('pop_max_histogram', function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 0);
testClient.drain(done);
});
});
it('should get a filtered histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
var params = {
filters: {
dataviews: {
pop_max_histogram: {
min: 2e6
}
}
}
};
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 2008000);
testClient.drain(done);
});
});
it('should skip the filter when sending own_filter=0 for histogram dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
var params = {
filters: {
dataviews: {
pop_max_histogram: {
min: 2e6
}
}
},
own_filter: 0
};
testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 0);
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,81 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('analysis-layers-dataviews-geojson', function() {
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var CARTOCSS = [
"#points {",
" marker-fill-opacity: 1.0;",
" marker-line-color: #FFF;",
" marker-line-width: 0.5;",
" marker-line-opacity: 1.0;",
" marker-placement: point;",
" marker-type: ellipse;",
" marker-width: 8;",
" marker-fill: red;",
" marker-allow-overlap: true;",
"}"
].join('\n');
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": CARTOCSS,
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
);
it('should get pop_max column from dataview', function(done) {
var testClient = new TestClient(mapConfig, 1234);
testClient.getTile(0, 0, 0, {format: 'geojson', layers: 0}, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(Array.isArray(geojson.features));
assert.ok(geojson.features.length > 0);
var feature = geojson.features[0];
assert.ok(feature.properties.hasOwnProperty('pop_max'), 'Missing pop_max property');
testClient.drain(done);
});
});
});

Some files were not shown because too many files have changed in this diff Show More